Кіріспе

Бағдарламалық жасақтауда бірнеше аталық кластарға ие болу. Көптік мұралау – бұл кейбір объектіге бағытталған компьютерлік бағдарламалау тілдерінің мүмкіндігі, онда объект немесе класс бірнеше аталық объектінен немесе аталық кластан қасиеттерді мұралай алады. Бұл бірлік мұралаудан өзгеше, онда объект немесе класс тек бір аталық объектінен немесе кластан мұра ала алады. Көптік мұралау көп жылдар бойы қызу талқылау тудырды, оған қарсылар оның күрделілігін және "алмаз тәрізді проблема" сияқты жағдайлардағы екіұштылығын атап көрсетеді. Мұндай жағдайларда, егер бірнеше аталық класс белгілі бір қасиетті іске асырса, қандай аталық кластан бұл қасиет мұраланғанын анықтау қиын болуы мүмкін. Бұл мәселені әртүрлі тәсілдермен, соның ішінде виртуалды мұрагерлік қолдану арқылы шешуге болады. Мұрагерлікке негізделмеген, мысалы, миксиндер мен белгілер сияқты объектіні құрастырудың баламалы әдістері де екіұштылықты жою үшін ұсынылған.

Егжей-тегжейлер

Объектіге бағытталған бағдарламалауда (ООП) мұрагерлік екі класс арасындағы қатынасты сипаттайды, онда бір класс (туынды класс) екінші классқа (бастапқы класс) кіші класс ретінде қосылады. Туынды класс бастапқы кластың әдістері мен атрибуттарын мұралайды, осы арқылы ортақ функционалдыққа қол жеткізеді. Мысалы, тамақтану, көбею сияқты қасиеттері бар Сүтқоректілер класын жасауға болады; содан кейін Мысық класын анықтауға болады, ол осы қасиеттерді тікелей бағдарламалау қажеттілігінсіз мұралайды, сонымен қатар тышқандарды қуу сияқты жаңа қасиеттерді қосады. Көптік мұрагерлік бағдарламашыларға бір уақытта бірнеше тәуелсіз иерархияларды пайдалануға мүмкіндік береді, мысалы, Мысық класының Мультфильм кейіпкері, Үй жануары және Сүтқоректі кластарынан мұрагерлік алып, барлық осы классдардың ішіндегі қасиеттерге қол жеткізуіне мүмкіндік береді.

Қолданылу

Көптік мұрагерлікті қолдайтын тілдерге мыналар жатады: 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 бірнеше ата-ана кластарын қолдайды; сынып декларациясындағы спецификация реті...