Кіріспе
Бағдарламалық жасақтауда бірнеше аталық кластарға ие болу. Көптік мұралау – бұл кейбір объектіге бағытталған компьютерлік бағдарламалау тілдерінің мүмкіндігі, онда объект немесе класс бірнеше аталық объектінен немесе аталық кластан қасиеттерді мұралай алады. Бұл бірлік мұралаудан өзгеше, онда объект немесе класс тек бір аталық объектінен немесе кластан мұра ала алады. Көптік мұралау көп жылдар бойы қызу талқылау тудырды, оған қарсылар оның күрделілігін және "алмаз тәрізді проблема" сияқты жағдайлардағы екіұштылығын атап көрсетеді. Мұндай жағдайларда, егер бірнеше аталық класс белгілі бір қасиетті іске асырса, қандай аталық кластан бұл қасиет мұраланғанын анықтау қиын болуы мүмкін. Бұл мәселені әртүрлі тәсілдермен, соның ішінде виртуалды мұрагерлік қолдану арқылы шешуге болады. Мұрагерлікке негізделмеген, мысалы, миксиндер мен белгілер сияқты объектіні құрастырудың баламалы әдістері де екіұштылықты жою үшін ұсынылған.
Multiple inheritance is a feature of some object oriented computer programming languages in which an object or class can inherit features from more than one parent object or parent class. It is distinct from single inheritance, where an object or class may only inherit from one particular object or class. Multiple inheritance has been a controversial issue for many years, with opponents pointing to its increased complexity and ambiguity in situations such as the "diamond problem", where it may be ambiguous as to which parent class a particular feature is inherited from if more than one parent class implements said feature. This can be addressed in various ways, including using virtual inheritance. Alternate methods of object composition not based on inheritance such as mixins and traits have also been proposed to address the ambiguity.
Егжей-тегжейлер
Объектіге бағытталған бағдарламалауда (ООП) мұрагерлік екі класс арасындағы қатынасты сипаттайды, онда бір класс (туынды класс) екінші классқа (бастапқы класс) кіші класс ретінде қосылады. Туынды класс бастапқы кластың әдістері мен атрибуттарын мұралайды, осы арқылы ортақ функционалдыққа қол жеткізеді. Мысалы, тамақтану, көбею сияқты қасиеттері бар Сүтқоректілер класын жасауға болады; содан кейін Мысық класын анықтауға болады, ол осы қасиеттерді тікелей бағдарламалау қажеттілігінсіз мұралайды, сонымен қатар тышқандарды қуу сияқты жаңа қасиеттерді қосады. Көптік мұрагерлік бағдарламашыларға бір уақытта бірнеше тәуелсіз иерархияларды пайдалануға мүмкіндік береді, мысалы, Мысық класының Мультфильм кейіпкері, Үй жануары және Сүтқоректі кластарынан мұрагерлік алып, барлық осы классдардың ішіндегі қасиеттерге қол жеткізуіне мүмкіндік береді.
Қолданылу
Көптік мұрагерлікті қолдайтын тілдерге мыналар жатады: C++, Common Lisp (Common Lisp Object System (CLOS) арқылы), EuLisp (The EuLisp Object System TELOS арқылы), Curl, Dylan, Eiffel, Logtalk, Object REXX, Scala (mixin кластарын пайдалану арқылы), OCaml, Perl, POP 11, Python, R, Raku және Tcl (8.6 нұсқасынан бастап немесе Incremental Tcl (Incr Tcl) арқылы бұрынғы нұсқаларда). IBM System Object Model (SOM) орындалу ортасы көптік мұрагерлікті қолдайды, және SOM-ға бағытталған кез келген бағдарламалау тілі бірнеше негізден мұрагерлік алған жаңа SOM сыныптарын құруға мүмкіндік береді. Swift, Java, Fortran (2003 жылғы жаңартудан бастап), C# және Ruby сияқты кейбір объектіге бағытталған тілдер жалғыз мұрагерлікті қолданады, бірақ протоколдар немесе интерфейстер нақты көптік мұрагерлік функционалының бір бөлігін қамтамасыз етеді. PHP әдістерді мұралау үшін қасиеттер кластарын пайдаланады. Ruby бірнеше әдістерді мұралау үшін модульдерді қолданады.
Алмаз мәселесі
"Алмас проблемасы" (кейде "Өлімге алып келетін алмаз" деп те аталады) – екі класс B және C класс А-дан, ал класс D класс B және C-ден мұрагерлік алғанда туындайтын екіұштылық. Егер А класында B және C өзгертіп жазған әдіс болса, ал D оны өзгертіп жазбаса, онда D қай нұсқаны мұрагерлік етеді: B-нікі ме, әлде C-нікі ме? Мысалы, GUI бағдарламалық құралдамасын жасау контекстінде, Button класы Rectangle (көрінісі үшін) және Clickable (функционалдығы/кіріспен жұмыс істеу үшін) класс-тарынан мұрагерлік алуы мүмкін, ал Rectangle және Clickable класс-тары Object класынан мұрагерлік алады. Егер Button нысаны үшін equals әдісі шақырылса және Button класында мұндай әдіс болмаса, бірақ Rectangle немесе Clickable (немесе екеуінде де) equals әдісі болса, қай әдіс шақырылуы керек? Бұл "алмас проблемасы" деп аталады, себебі осы жағдайда класс мұрагерлік диаграммасы алмас пішінін құрайды. Бұл жағдайда А класы жоғарыда, B және C оның астында бөлек орналасқан, ал D екеуін төменде біріктіріп алмас пішінін жасайды.
Жеңілдіктер
Тілдерде осы қайталанатын мұрагерлік проблемаларын шешудің әртүрлі жолдары бар. C# (C# 8.0 нұсқасынан бастап) интерфейстің әдепкі әдісін жүзеге асыруға мүмкіндік береді, бұл A класының Ia және Ib интерфейстерін ұқсас әдістермен жүзеге асыруына, олардың әдепкі жүзеге асырылымдары бар, бірдей қолтаңбасы бар екі "мұрагерлік" әдіске ие болуына мүмкіндік береді, бұл алмаз тәрізді проблеманы тудырады. Бұл A класының әдісті өзі жүзеге асыруын талап ету арқылы, яғни екіұштылықты жою арқылы немесе шақырушыны A нысанын тиісті интерфейске осы әдістің әдепкі жүзеге асырылуын пайдалану үшін түрлендіруге мәжбүрлеу арқылы шешіледі (мысалы, ((Ia) aInstance).Әдіс();). C++ әдепкі бойынша әрбір мұрагерлік жолын жеке-жеке қадағалайды, сондықтан D нысаны екі бөлек A нысанын қамтиды, ал A мүшелерін пайдалану дұрыс білікті болуы керек. Егер A-дан B-ға және A-дан C-ға мұрагерлік "виртуалды" деп белгіленсе (мысалы, "class B : virtual public A"), C++ тек бір A нысанын құруға ерекше көңіл бөледі және A мүшелерін дұрыс пайдалануға мүмкіндік береді. Виртуалды және виртуалды емес мұрагерлік аралас болса, онда A-ға әр виртуалды емес мұрагерлік жолы үшін бір виртуалды және виртуалды емес A нысаны болады. C++ пайдалану үшін қандай ата-ана класынан шақырылатынын нақты көрсетуді талап етеді, мысалы, Worker::Human.Жасы. C++ эксплицитті қайталанатын мұрагерлікті қолдамайды, өйткені қай суперкласты пайдалану керектігін анықтаудың жолы болмайды (яғни, бір кластың бір туынды тізімінде бірнеше рет пайда болуы [class Dog : public Animal, Animal]). C++ сонымен қатар виртуалды мұрагерлік механизмі арқылы бірнеше кластың бір данасын жасауға мүмкіндік береді (мысалы, Worker::Human және Musician::Human бір нысанға сілтеме жасайды). Common Lisp CLOS ақылға қонымды әдепкі мінез-құлықты және оны басып тастау мүмкіндігін қамтамасыз етуге тырысады. Әдетте, қарапайым айтқанда, әдістер D, B, C, A ретімен жіктеледі, егер B класының анықтамасында C класынан бұрын жазылған болса. Аргумент кластары ең нақты әдіс таңдалады (D>(B,C)>A); содан кейін ата-ана кластар кіші сыныптың анықтамасындағы ретімен аталады (B>C). Алайда, бағдарламашы бұл ретті нақты әдіс шешімі ретін беру немесе әдістерді біріктіру ережесін белгілеу арқылы өзгерте алады. Бұл әдіс толыққанды бақыланатын әдіс комбинациясы деп аталады. MOP (метаобъект протоколы) мұрагерлікті, динамикалық диспетчерлікті, сыныпты инстанциялауды және басқа да ішкі механизмдерді жүйе тұрақтылығына әсер етпей өзгертудің құралдарын ұсынады. Curl тек ортақ деп белгіленген кластарға ғана қайталанатын мұрагерлікке мүмкіндік береді. Ортақ кластар кластағы әрбір тұрақты конструктор үшін қосымша конструкторды анықтауы керек. Тұрақты конструктор ортақ кластың күйі бірінші рет субклас конструкторы арқылы инициализацияланған кезде шақырылады, ал екінші конструктор барлық басқа субкластар үшін шақырылады. Eiffel ата-бабалардың мүмкіндіктерін таңдау және қайта атау директивалары арқылы нақты таңдайды. Бұл базалық кластың мүмкіндіктерін оның ұрпақтары арасында бөлісуге немесе олардың әрқайсысына базалық кластың жеке көшірмесін беруге мүмкіндік береді. Eiffel ата-баба кластарынан мұрагерлік алған мүмкіндіктерді тікелей біріктіруге немесе ажыратуға мүмкіндік береді. Eiffel мүмкіндіктердің атауы мен жүзеге асырылуы бірдей болса, оларды автоматты түрде біріктіреді. Сынып жазушысы мұрагерлік мүмкіндіктерді бөлу үшін оларды қайта атауға мүмкіндігі бар. Көптік мұрагерлік - Eiffel дамуында жиі кездесетін жағдай; мысалы, кеңінен қолданылатын EiffelBase деректер құрылымдары мен алгоритмдер кітапханасындағы тиімді кластардың көпшілігі екі немесе одан да көп ата-анаға ие. Go компиляция кезінде алмаз тәрізді проблеманы болдырмайды. Егер D құрылымы F әдісі бар B және C екі құрылымын еншілесе, осылайша A интерфейсін қанағаттандырса, D.F шақырылса немесе D нысаны A түріндегі айнымалыға тағайындалса, компилятор "аңғарсыз таңдаушы" туралы шағым жасайды. B және C әдістерін D.B.F немесе D.C.F арқылы ашық түрде шақыруға болады. Java 8 интерфейстерде әдепкі әдістерді енгізеді. Егер A, B, C интерфейстер болса, B, C әрқайсысы A-ның абстрактілік әдісін әртүрлі жүзеге асыра алады, бұл алмаз тәрізді проблеманы тудырады. D класы әдісті қайта іске асыруы керек (оның денесі шақыруды супер-жүзеге асырулардың біріне жібере алады), немесе екіұштылық компиляция қатесі ретінде қабылданбайды. Java 8-ден бұрын Java Diamond проблемасының тәуекеліне ұшыраған жоқ, өйткені ол бірнеше мұрагерлікті қолдамады және интерфейстің әдепкі әдістері қол жетімді емес еді. JavaFX Script 1.2 нұсқасында миксиндерді пайдалану арқылы көптік мұрагерлікке мүмкіндік береді. Қайшылық туған жағдайда компилятор екіұшты айнымалыны немесе функцияны тікелей пайдалануға тыйым салады. Әрбір мұрагерлік мүшеге нысанды қызығушылық танытқан миксинге түрлендіру арқылы қол жеткізуге болады, мысалы, (individual as Person).printInfo;. Kotlin интерфейстердің көптік мұрагерлігіне мүмкіндік береді, бірақ алмаз тәрізді проблема туған жағдайда, балама сынып мұрагерлік қақтығысын тудырған әдісті қайта іске асыруы керек және қай ата-ана класының жүзеге асырылуын пайдалану керектігін көрсетуі керек. мысалы, super<ТаңдалғанАта-анаИнтерфейсі>.кейбірӘдіс(). Logtalk интерфейс және жүзеге асыру көптік мұрагерлігін қолдайды, бұл әдепкі қақтығысты шешу механизмімен маскировкаланатын әдістерге атау беруге және қол жеткізуге мүмкіндік беретін әдіс псевдонимдерін жариялауға мүмкіндік береді. OCaml-де ата-ана кластары сынып анықтамасының ішінде жеке-жеке көрсетіледі. Әдістер (және атрибуттар) сол ретпен мұрагерлікке алынады, әрбір жаңадан мұрагерлік алған әдіс кез келген бұрынғы әдістерді жояды. OCaml екіұштылық жағдайында пайдалануға болатын әдіс жүзеге асырылуын шешу үшін сынып мұрагерлік тізімінің соңғы анықтамасын таңдайды. Әдепкі мінез-құлықты басып тастау үшін әдіс шақырылымын қалаған сынып анықтамасымен білікті ету жеткілікті. Perl мұрагерлікке алынатын кластар тізімін реттелген тізім ретінде пайдаланады. Компилятор суперкластар тізімін тереңдік бойынша іздеу арқылы немесе кластың иерархиясының C3 линеаризациясын пайдалану арқылы табылған алғашқы әдісті пайдаланады. Әртүрлі кеңейтімдер балама кластық құрастыру схемаларын ұсынады. Мұрагерлік реті сынып семантикасына әсер етеді. Жоғарыдағы екіұштылықта B класы және оның ата-бабалары C класы мен оның ата-бабаларынан бұрын тексеріледі, сондықтан A-дағы әдіс B арқылы мұрагерлікке алынады. Бұл Io және Picolisp-пен ортақ. Perl-де бұл мінез-құлықты C3 линеаризациясын немесе басқа алгоритмдерді пайдалану үшін mro немесе басқа модульдерді пайдалану арқылы өзгертуге болады. Python Perl сияқты бірдей құрылымға ие, бірақ Perl-ден айырмашылығы, ол оны тілдің синтаксисіне қосады. Мұрагерлік реті сынып семантикасына әсер етеді. Python жаңа стильді кластар енгізілген кезде осы мәселені шешуге тура келді, олардың барлығы object жалпы ата-бабасына ие. Python C3 линеаризациясы (немесе Әдіс шешу тәртібі (MRO)) алгоритмін пайдалану арқылы кластар тізімін жасайды. Бұл алгоритм екі шектеуді сақтайды: балалар өз ата-аналарынан бұрын келеді және егер кластың бірнеше кластан мұрагерлік алса, олар базалық кластар түйіндісінде көрсетілген ретпен сақталады (бірақ бұл жағдайда мұрагерлік графигінің жоғары жағындағы кейбір кластар төменгі жағындағы кластардан бұрын келуі мүмкін). Осылайша, әдіс шешу тәртібі: D, B, C, A. Ruby кластарының дәл бір ата-анасы бар, бірақ сонымен қатар бірнеше модульдерден мұрагерлікке алуға болады; ruby сынып анықтамалары орындалады және әдістің (қайта) анықталуы орындалу кезінде кез келген бұрынғы анықтаманы жояды. Runtime метапрограммалау болмаған жағдайда, бұл оң жақтан тереңдік бойынша шешуге шамамен бірдей семантикаға ие. Scala белгілі бір кластың бірнеше данасын жасауға мүмкіндік береді, бұл көптік мұрагерлікке мүмкіндік береді, себебі кластың иерархиясы мен белгінің иерархиясы арасында айырмашылық жасалады. Клас тек бір кластан мұрагерлікке ала алады, бірақ қалаған белгілерді қосуға болады. Scala әдіс атауларын кеңейтілген "белгілердің" оңнан тереңдік бойынша іздеуін пайдаланып шешеді, содан кейін нәтижедегі тізімде әр модульдің соңғы пайда болуын жояды. Осылайша, шешу тәртібі: [D, C, A, B, A], бұл [D, C, B, A] дейін азаяды. Tcl бірнеше ата-ана кластарын қолдайды; сынып декларациясындағы спецификация реті...
Logtalk supports both interface and implementation multi inheritance, allowing the declaration of method aliases that provide both renaming and access to methods that would be masked out by the default conflict resolution mechanism. In OCaml, parent classes are specified individually in the body of the class definition. Methods (and attributes) are inherited in the same order, with each newly inherited method overriding any existing methods. OCaml chooses the last matching definition of a class inheritance list to resolve which method implementation to use under ambiguities. To override the default behavior, one simply qualifies a method call with the desired class definition. Perl uses the list of classes to inherit from as an ordered list. The compiler uses the first method it finds by depth first searching of the superclass list or using the C3 linearization of the class hierarchy. Various extensions provide alternative class composition schemes. The order of inheritance affects the class semantics. 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. This is shared with Io and Picolisp. In Perl, this behavior can be overridden using the mro or other modules to use C3 linearization or other algorithms. Python has the same structure as Perl, but, unlike Perl, includes it in the syntax of the language. The order of inheritance affects the class semantics. 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 classes using the C3 linearization (or Method Resolution Order (MRO)) algorithm. That algorithm enforces two constraints: children precede their parents and if a class inherits from multiple classes, they are kept in the order specified in the tuple of base classes (however in this case, some classes high in the inheritance graph may precede classes lower in the graph). Thus, the method resolution order is: D, B, C, A.
Ruby classes have exactly one parent but may also inherit from multiple modules; ruby class definitions are executed, and the (re)definition of a method obscures any previously existing definition at the time of execution. In the absence of runtime metaprogramming this has approximately the same semantics as rightmost depth first resolution. Scala allows multiple instantiation of traits, which allows for multiple inheritance by adding a distinction between the class hierarchy and the trait hierarchy. A class can only inherit from a single class, but can mix in as many traits as desired. 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]. Tcl allows multiple parent classes; the order of specification in the class declaration affects the name resolution for members using the C3 linearization algorithm. Languages that allow only single inheritance, where a class can only derive from one base class, do not have the diamond problem. The reason for this is that such languages have at most one implementation of any method at any level in the inheritance chain regardless of the repetition or placement of methods. Typically these languages allow classes to implement multiple protocols, called interfaces in Java. These protocols define methods but do not provide concrete implementations. This strategy has been used by ActionScript, C#, D, Java, Nemerle, Object Pascal, Objective C, Smalltalk, Swift and PHP. All these languages allow classes to implement multiple protocols. Moreover, Ada, C#, Java, Object Pascal, Objective C, Swift and PHP allow multiple inheritance of interfaces (called protocols in Objective C and Swift). Interfaces are like abstract base classes that specify method signatures without implementing any behaviour. ("Pure" interfaces such as the ones in Java up to version 7 do not permit any implementation or instance data in the interface.) Nevertheless, even when several interfaces declare the same method signature, as soon as that method is implemented (defined) anywhere in the inheritance chain, it overrides any implementation of that method in the chain above it (in its superclasses). Hence, at any given level in the inheritance chain, there can be at most one implementation of any method. Thus, single inheritance method implementation does not exhibit the Diamond Problem even with multiple inheritance of interfaces. With the introduction of default implementation for interfaces in Java 8 and C# 8, it is still possible to generate a Diamond Problem, although this will only appear as a compile time error.