Programavimas

Atskleiskite potipio polimorfizmo magiją

Žodis polimorfizmas kilęs iš graikų kalbos „daugybė formų“. Dauguma „Java“ kūrėjų sieja šį terminą su objekto sugebėjimu stebuklingai atlikti teisingą metodo elgesį atitinkamuose programos taškuose. Tačiau tas į įgyvendinimą orientuotas požiūris veda į vedlių vaizdus, ​​o ne į pagrindinių sąvokų supratimą.

Java polimorfizmas visada yra polimorfizmo potipis. Atidžiai nagrinėjant mechanizmus, kurie sukuria tą polimorfinio elgesio įvairovę, reikia atsisakyti įprastų įgyvendinimo problemų ir galvoti apie tipą. Šiame straipsnyje tiriama į tipą orientuota objektų perspektyva ir kaip ta perspektyva skiriasi elgesys, iš kurio objektas gali išreikšti kaip objektas iš tikrųjų išreiškia tą elgesį. Atlaisvindami savo polimorfizmo sampratą iš įgyvendinimo hierarchijos, mes taip pat atrandame, kaip „Java“ sąsajos palengvina polimorfinį elgesį tarp objektų grupių, kurioms visiškai nėra bendro įgyvendinimo kodo.

Quattro polymorphi

Polimorfizmas yra platus objektyvus terminas. Nors paprastai sutapatiname bendrą sąvoką su potipio atmaina, iš tikrųjų yra keturios skirtingos polimorfizmo rūšys. Prieš išsamiai išnagrinėdami polipo polipus potipį, kitame skyriuje pateikiama bendra polimorfizmo apžvalga į objektą orientuotose kalbose.

Luca Cardelli ir Peteris Wegneris, knygos „Apie supratimą apie tipus, duomenų abstrakciją ir polimorfizmą“ (žr. Nuorodą į straipsnį šaltinius) autoriai polimorfizmą skirsto į dvi pagrindines kategorijas - ad hoc ir universalią - ir į keturias atmainas: prievarta, perkrova, parametrinis ir įtraukimas. Klasifikavimo struktūra yra:

 | - prievarta | - ad hoc - | | - perkrovos polimorfizmas - | | - parametrinis | - universalus - | | - įtraukimas 

Toje bendroje schemoje polimorfizmas reiškia subjekto gebėjimą turėti kelias formas. Visuotinis polimorfizmas reiškia tipo struktūros vienodumą, kai polimorfizmas veikia be galo daug tipų, turinčių bendrą bruožą. Mažiau struktūrizuotas ad hoc polimorfizmas veikia virš galimo skaičiaus galimai nesusijusių tipų. Šias keturias veisles galima apibūdinti kaip:

  • Prievarta: viena abstrakcija tarnauja keliems tipams per numanomą tipo konversiją
  • Perkrovimas: vienas identifikatorius žymi kelias abstrakcijas
  • Parametrinė: abstrakcija veikia vienodai įvairiuose tipuose
  • Įtraukimas: abstrakcija veikia per įtraukimo santykį

Trumpai aptarsiu kiekvieną veislę, prieš pradėdamas konkrečiai polimorfizmo potipį.

Prievarta

Prievarta reiškia numanomą parametro tipo konversiją į tipą, kurio tikimasi metodo ar operatoriaus, taip išvengiant tipo klaidų. Sudarytojas, norėdamas pateikti šias išraiškas, turi nustatyti, ar tinkamas dvejetainis + operatorius egzistuoja operandų tipams:

 2.0 + 2.0 2.0 + 2 2.0 + "2" 

Pirmoji išraiška prideda dvi dvigubai operandai; Java kalba konkrečiai apibrėžia tokį operatorių.

Tačiau antrasis posakis prideda a dvigubai ir an tarpt; „Java“ neapibrėžia operatoriaus, kuris priima tuos operandų tipus. Laimei, kompiliatorius netiesiogiai paverčia antrąjį operandą į dvigubai ir naudoja operatorių, apibrėžtą dviem dvigubai operandai. Tai nepaprastai patogu kūrėjui; be numanomo konversijos atsirastų kompiliavimo laiko klaida arba programuotojas turėtų aiškiai perduoti tarpt į dvigubai.

Trečia išraiška prideda a dvigubai ir a Stygos. Dar kartą „Java“ kalba neapibrėžia tokio operatoriaus. Taigi kompiliatorius priverčia dvigubai operandas a Stygosir „plus“ operatorius atlieka styginių sujungimą.

Priverstinė prievarta atsiranda ir metodo iškvietimo metu. Tarkime, klasė Išvestinė pratęsia klasę Bazėir klasė C turi metodą su parašu m (pagrindas). Metodo iškvietimui žemiau esančiame kode kompiliatorius netiesiogiai konvertuoja išvestinė pamatinis kintamasis, kuris turi tipą Išvestinė, į Bazė tipas, nurodytas metodo parašu. Tas numanomas konversija leidžia m (pagrindas) metodo įgyvendinimo kodas naudoti tik tipo apibrėžtas operacijas, apibrėžtas Bazė:

 C c = naujas C (); Išvestinė išvestinė = nauja Išvestinė (); c.m (išvestinė); 

Vėlgi, netiesioginė prievarta metodo iškvietimo metu panaikina sudėtingą tipą ar nereikalingą kompiliavimo laiko klaidą. Žinoma, kompiliatorius vis tiek patikrina, ar visos tipo konversijos atitinka apibrėžtą tipų hierarchiją.

Perkrovimas

Perkrovimas leidžia naudoti tą patį operatoriaus ar metodo pavadinimą žymėti kelias, skirtingas programos reikšmes. + operatorius, naudotas ankstesniame skyriuje, parodė dvi formas: vieną pridėjimui dvigubai operandai, vienas skirtas susieti Stygos objektai. Yra dviejų formų, įtraukiančių du sveikus skaičius, du ilgius ir kt. Formas. Skambiname operatoriui perkrautas ir pasikliaukite kompiliatoriumi, kuris pasirinks tinkamą funkcionalumą pagal programos kontekstą. Kaip jau buvo minėta anksčiau, kompiliatorius netiesiogiai konvertuoja operando tipus, kad jie atitiktų tikslų operatoriaus parašą. Nors „Java“ nurodo tam tikrus perkrautus operatorius, ji nepalaiko vartotojo nustatyto operatorių perkrovimo.

„Java“ leidžia vartotojo apibrėžtam metodų pavadinimų perkrovai. Klasė gali turėti kelis metodus tuo pačiu pavadinimu, jei metodo parašai yra skirtingi. Tai reiškia, kad parametrų skaičius turi skirtis, arba bent vieno parametro padėtis turi būti kitokio tipo. Unikalūs parašai leidžia kompiliatoriui atskirti metodus, turinčius tą patį pavadinimą. Kompiliatorius suskirsto metodo pavadinimus naudodamas unikalius parašus, efektyviai sukurdamas unikalius pavadinimus. Atsižvelgiant į tai, bet koks akivaizdus polimorfinis elgesys išgaruoja atidžiau apžiūrėjus.

Tiek prievarta, tiek perkrova priskiriama ad hoc, nes kiekviena jų polimorfiškai elgiasi tik ribotai. Nors šios veislės patenka į platų polimorfizmo apibrėžimą, šios veislės pirmiausia yra kūrėjo patogumai. Priverčiant išvengiama sudėtingų aiškiojo tipo perdavimų ar nereikalingų kompiliatoriaus tipo klaidų. Kita vertus, perkraunant gaunamas sintaksinis cukrus, leidžiantis kūrėjui atskiriems metodams naudoti tą patį pavadinimą.

Parametrinis

Parametrinis polimorfizmas leidžia naudoti vieną abstrakciją daugeliui tipų. Pavyzdžiui, a Sąrašas abstrakcija, vaizduojanti vienarūšių objektų sąrašą, galėtų būti pateikta kaip bendras modulis. Pakartotinai naudosite abstrakciją nurodydami sąraše esančių objektų tipus. Kadangi parametruojamas tipas gali būti bet koks vartotojo apibrėžtas duomenų tipas, yra potencialiai begalinis bendrosios abstrakcijos panaudojimo skaičius, todėl tai neabejotinai yra galingiausias polimorfizmo tipas.

Iš pirmo žvilgsnio aukščiau Sąrašas abstrakcija gali atrodyti klasės naudingumas java.util.Sąrašas. Tačiau „Java“ nepalaiko tikrojo parametrinio polimorfizmo saugiu tipu būdu, todėl java.util.Sąrašas ir java.utilKitos kolekcijos klasės yra parašytos pagal pradinę Java klasę, java.lang.Object. (Jei norite gauti daugiau informacijos, žr. Mano straipsnį „Pirminė sąsaja?“). „Java“ vieno šaknies diegimo paveldėjimas siūlo dalinį sprendimą, bet ne tikrąją parametrinio polimorfizmo galią. Puikiame Erico Alleno straipsnyje „Pažvelkime į parametrinio polimorfizmo galią“ aprašomas bendrųjų „Java“ tipų poreikis ir pasiūlymai, kaip spręsti „Sun“ „Java“ specifikacijos užklausą Nr. 000014 „Pridėti bendrų tipų į„ Java “programavimo kalbą“. (Žr. Nuorodą šaltiniuose.)

Įtraukimas

Įtraukimo polimorfizmas pasiekia polimorfinį elgesį per įtraukimo ryšį tarp tipų ar verčių rinkinių. Daugeliui į objektą orientuotų kalbų, įskaitant „Java“, įtraukimo ryšys yra potipio ryšys. Taigi Java kalboje inkliuzinis polimorfizmas yra polipo polipas.

Kaip pažymėta anksčiau, kai „Java“ kūrėjai paprastai nurodo polimorfizmą, jie visada reiškia polipo polipą. Norint gerai įvertinti polimorfizmo potipio galią, reikia pažvelgti į polimorfinį elgesį užtikrinančius mechanizmus iš tipo perspektyvos. Likusioje šio straipsnio dalyje atidžiai nagrinėjama ši perspektyva. Siekdamas trumpumo ir aiškumo, polimorfizmo terminą vartoju polimorfizmo potipiui apibūdinti.

Orientacija į tipą

1 paveiksle pavaizduota UML klasės diagrama rodo paprastą tipą ir klasių hierarchiją, naudojamą polimorfizmo mechanikai iliustruoti. Modelyje vaizduojami penki tipai, keturios klasės ir viena sąsaja. Nors modelis vadinamas klasės diagrama, aš galvoju apie tai kaip tipo diagramą. Kaip išsamiai aprašyta skyrelyje „Ačiū tipas ir švelni klasė“, kiekviena „Java“ klasė ir sąsaja skelbia vartotojo apibrėžtą duomenų tipą. Taigi, žiūrint nuo įgyvendinimo nepriklausomo požiūrio (t. Y. Orientuoto į tipą), kiekvienas iš penkių paveiksle esančių stačiakampių reiškia tipą. Įgyvendinimo požiūriu, keturi iš šių tipų yra apibrėžti naudojant klasės konstrukcijas, o vienas - naudojant sąsają.

Šis kodas apibrėžia ir įgyvendina kiekvieną vartotojo apibrėžtą duomenų tipą. Aš tyčia palaikau kuo paprastesnį įgyvendinimą:

/ * Base.java * / public class Base {public String m1 () {return "Base.m1 ()"; } public String m2 (String s) {grąžinti "Base.m2 (" + s + ")"; }} / * IType.java * / sąsaja IType {String m2 (String s); Stygos m3 (); } / * Išvestinė.java * / viešoji klasė Išvestinė pratęsia Baziniai įrankiai IT tipas {public String m1 () {return "Išvestas.m1 ()"; } public String m3 () {return "Išvestas.m3 ()"; }} / * Išvestinė2.java * / viešoji klasė Išvestinė2 tęsiasi Išvestinė {viešoji eilutė m2 (String s) {grįžti "Išvestinė2.m2 (" + s + ")"; } public String m4 () {return "Išvestas2.m4 ()"; }} / * Separate.java * / public class Separate įgyvendina IType {public String m1 () {return "Separate.m1 ()"; } public String m2 (String s) {return "Separate.m2 (" + s + ")"; } public String m3 () {return "Separate.m3 ()"; }} 

Naudojant šias tipo deklaracijas ir klasių apibrėžimus, 2 paveiksle pavaizduotas konceptualus „Java“ sakinio vaizdas:

Išvestinis2 išvestas2 = naujas Išvestinis2 (); 

Ankstesniame sakinyje deklaruojamas aiškiai suvestas pamatinis kintamasis, gautas2ir prideda nuorodą į naujai sukurtą Išvestinė2 klasės objektas. Viršutiniame skydelyje 2 paveiksle pavaizduota Išvestinė2 nuoroda kaip iliuminatorių rinkinys, per kurį pagrindinė Išvestinė2 objektą galima peržiūrėti. Kiekvienam yra viena skylė Išvestinė2 tipo operacija. Aktualus Išvestinė2 objektų žemėlapiai Išvestinė2 operaciją pagal tinkamą įgyvendinimo kodą, kaip numatyta aukščiau pateiktame kode apibrėžtoje įgyvendinimo hierarchijoje. Pavyzdžiui, Išvestinė2 objektų žemėlapiai m1 () prie klasėje apibrėžto įgyvendinimo kodo Išvestinė. Be to, šis įgyvendinimo kodas nepaiso m1 () metodas klasėje Bazė. A Išvestinė2 referencinis kintamasis negali pasiekti nepaisomo m1 () įgyvendinimas klasėje Bazė. Tai nereiškia, kad tikrasis diegimo kodas klasėje Išvestinė negali naudoti Bazė klasės įgyvendinimas per super.m1 (). Bet kiek atskaitos kintamasis gautas2 susirūpinęs, tas kodas yra neprieinamas. Kito atvaizdavimas Išvestinė2 operacijos panašiai rodo kiekvienos operacijos tipo vykdymo kodą.

Dabar, kai turite Išvestinė2 objektą, galite jį nurodyti naudodami bet kokį kintamąjį, kuris atitinka tipą Išvestinė2. Tipo hierarchija 1 paveikslo UML diagramoje tai parodo Išvestinė, Bazėir IT tipas yra visi super tipai Išvestinė2. Taigi, pavyzdžiui, a Bazė nuorodą galima pridėti prie objekto. 3 paveiksle pavaizduotas šio „Java“ sakinio koncepcinis vaizdas:

Pagrindo pagrindas = išvestas2; 

Nėra visiškai jokio pagrindinio pakitimo Išvestinė2 objektą ar bet kurį iš operacijų susiejimų, nors ir metodus m3 () ir m4 () nėra prieinami per Bazė nuoroda. Skambinama m1 () arba m2 (eilutė) naudojant bet kurį kintamąjį gautas2 arba bazė to paties diegimo kodo vykdymas:

Styginių tmp; // Išvestinė2 nuoroda (2 pav.) Tmp = išvesta2.m1 (); // tmp yra „Išvestinis.m1 ()“ tmp = išvestas2.m2 („Sveiki“); // tmp yra "Išvestinis2.m2 (Sveiki)" // Pagrindinė nuoroda (3 pav.) tmp = pagrindas.m1 (); // tmp yra „Išvestinė.m1 ()“ tmp = bazė.m2 („Sveiki“); // tmp yra „Išvestinis2.m2 (sveiki)“ 

Suprasti identišką elgesį naudojant abi nuorodas yra prasminga, nes Išvestinė2 objektas nežino, kas vadina kiekvieną metodą. Objektas žino tik tai, kad pakviestas jis vykdo žygio tvarką, apibrėžtą įgyvendinimo hierarchijoje. Tuose užsakymuose numatyta, kad metodas m1 (), Išvestinė2 objektas vykdo kodą klasėje Išvestinėir metodui m2 (eilutė), jis vykdo kodą klasėje Išvestinė2. Veiksmas, kurį atlieka pagrindinis objektas, nepriklauso nuo pamatinio kintamojo tipo.

Tačiau viskas nėra lygi, kai naudojate pamatinius kintamuosius gautas2 ir bazė. Kaip pavaizduota 3 paveiksle, a Bazė tipo nuoroda gali matyti tik Bazė tipo pagrindinio objekto operacijos. Taigi nors Išvestinė2 turi metodų atvaizdavimą m3 () ir m4 (), kintamasis bazė negaliu pasiekti šių metodų:

Styginių tmp; // Išvestinė2 nuoroda (2 pav.) Tmp = išvesta2.m3 (); // tmp yra "Išvestinis.m3 ()" tmp = išvestas2.m4 (); // tmp yra „Išvestinis2.m4 ()“ // Pagrindo nuoroda (3 pav.) tmp = pagrindas.m3 (); // Kompiliavimo laiko klaida tmp = bazė.m4 (); // Kompiliavimo laiko klaida 

Vykdymo laikas

Išvestinė2

objektas išlieka visiškai pajėgus priimti arba

m3 ()

arba

m4 ()

metodo iškvietimai. Tipo apribojimai, neleidžiantys bandyti skambinti per

Bazė