Oppimateriaalin copyright © 2009 Arto Wikla.
Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille
opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin,
kuten kaupallisilla tai muilla kursseilla, on kielletty.
(Muutettu viimeksi 2.4.2009)
Timanttinen ongelma
Moniperiytyminen on voimakas ohjelmointikonstruktio, mutta
siihen katsotaan liittyvän myös ongelmia. Yksi on ns.
timanttiongelma:
class A
class B extends A
class C extends A
class D extends B, C
- A olkoon luokka Henkilö, B Opettaja ja C Opiskelija.
- D olkoon siten luontevasti OpettavaOpiskelija
(mikä ei ole meidänkään laitoksellamme mitenkään poikkeuksellista ;-)
- B ja C perivät nimi-kentän luokasta A. Ne eivät korvaa tätä
kenttää omallaan.
- OpettavaOpiskelija perii nimi-kentän kahta kautta. Onko hänellä vain
yksi nimi vai erikseen opiskelijanimi ja opettajanimi?
("Masi" ja "kandidaatti Möttönen")
- B ja C perivät (mahdollisesti abstraktin) syö-metodin luokasta A.
Kumpikin korvaa (override) tämän metodin omalla versiollaan;
opiskelija vaikkapa syö halvalla Unicafessa, opettaja syö hyvin jossain muualla... ;-)
- OpettavaOpiskelija perii kaksi erilaista syö-metodia. Kummalla hän syö?
- Esimerkkejä voisi jatkaa mm. perimällä ominaisuuden
yhtä reittiä suoraan A:sta,
toista rettiä kulkien perimällä korvaava, jne.
- Ja syklittömässäkin verkossa voi olla aika monta polkua...
Wikipedia
- Diamond problem (juttelee tosin vain metodien perimisestä):
Different programming languages have addressed this problem in different
ways:
- C++ by default follows each inheritance path separately, so a D
object would actually contain two separate A objects, and uses of A's
members have to be properly qualified. If the inheritance from A to B
and the inheritance from A to C are both marked "virtual" (for example,
"class B : virtual public A"), C++ takes special care to only create one
A object, and uses of A's members work correctly. If virtual inheritance
and nonvirtual inheritance are mixed, there is a single virtual A and a
nonvirtual A for each nonvirtual inheritance path to A.
- Common Lisp attempts to provide both reasonable default behavior
and the ability to override it. By default, the method with the most
specific argument classes is chosen; then in the order in which parent
classes are named in the subclass definition. However, the programmer
can override this, by giving a specific method resolution order or
stating a rule for combining methods.
- Eiffel handles this situation by select and rename directives,
where the ancestors' methods to use in a descendant are explicitly
specified. This allows the methods of the base class to be shared
between its descendants or to even give each of them a separate copy of
the base class.
- Perl and Io handle this by specifying the inheritance classes as
an ordered list. In the above ambiguity, class B and its ancestors would
be checked before class C and its ancestors, so the method in A would be
inherited through B.
- Python had to deal with this upon the introduction of new-style
classes, all of which have a common ancestor, object. Python creates a
list of the classes that would be searched in left-first depth-first
order (D, B, A, C, A) and then removes all but the last occurrence of
any repeated classes. Thus, the method resolution order is: D, B, C, A.
- Ruby resolves method names using a reverse-inclusion-order
depth-first search of included modules, before eliminating all but the
last occurrence of each module in the resulting list. So, the resolution
order is: [D, C, A, B, A], which reduces down to [D, C, B, A]
- Component Pascal does not allow multiple inheritance. This can be
circumvented easily by the use of so-called twin classes.
- Java does not allow multiple inheritance.
- C# does not allow multiple inheritance.
- Scala resolves method names using a right-first depth-first search
of extended 'traits', before eliminating all but the last occurrence of
each module in the resulting list. So, the resolution order is: [D, C,
A, B, A], which reduces down to [D, C, B, A]
Takaisin sisältösivulle.