Programmēšana

Atklāj burvību, kas slēpjas apakštipa polimorfismā

Vārds polimorfisms nāk no grieķu valodas "daudzos veidos". Lielākā daļa Java izstrādātāju šo terminu saista ar objekta spēju maģiski izpildīt pareizu metodes uzvedību attiecīgajos programmas punktos. Tomēr šis uz ieviešanu vērstais skats drīzāk vedina uz burvjiem, nevis izpratni par pamatjēdzieniem.

Java polimorfisms vienmēr ir apakštipa polimorfisms. Cieši pārbaudot mehānismus, kas rada šo polimorfās uzvedības dažādību, ir jāizmet mūsu parastās ieviešanas problēmas un jādomā pēc veida. Šajā rakstā tiek pētīta objektu perspektīva, kas orientēta uz tipu, un kā šī perspektīva tiek atdalīta kas uzvedība, no kuras objekts var izteikties objekts faktiski pauž šo uzvedību. Atbrīvojot polimorfisma jēdzienu no ieviešanas hierarhijas, mēs arī atklājam, kā Java saskarnes atvieglo polimorfu uzvedību objektu grupās, kurām vispār nav kopīga ieviešanas koda.

Quattro polymorphi

Polimorfisms ir plašs uz objektu orientēts termins. Lai gan mēs parasti pielīdzinām vispārējo koncepciju apakštipa šķirnei, faktiski ir četri dažādi polimorfisma veidi. Pirms detalizēti aplūkojam apakštipa polimorfismu, nākamajā sadaļā ir sniegts vispārējs polimorfisma pārskats objektorientētās valodās.

Lukas Kardelli un Pīters Vegners, grāmatas “Par tipu, datu abstrakcijas un polimorfisma izpratni” (skat. Saites uz rakstu resursus) autori, polimorfismu iedala divās lielās kategorijās - ad hoc un universālā - un četrās šķirnēs: piespiešana, pārslodze, parametru un iekļaušanu. Klasifikācijas struktūra ir:

 | - piespiešana | - ad hoc - | | - polimorfisma pārslodze - | | - parametrisks | - universāls - | | - iekļaušana 

Šajā vispārējā shēmā polimorfisms atspoguļo entītijas spēju būt vairākās formās. Universāls polimorfisms attiecas uz tipa struktūras viendabīgumu, kurā polimorfisms darbojas bezgalīgi daudzos veidos, kuriem ir kopīga iezīme. Mazāk strukturēts ad hoc polimorfisms darbojas ierobežotā skaitā, iespējams, nesaistītu tipu. Četras šķirnes var raksturot kā:

  • Piespiešana: viena abstrakcija kalpo vairākiem veidiem, izmantojot netiešu tipa pārveidošanu
  • Pārslodze: viens identifikators apzīmē vairākas abstrakcijas
  • Parametrisks: abstrakcija darbojas vienmērīgi dažādos veidos
  • Iekļaušana: abstrakcija darbojas caur iekļaušanas saistību

Es īsi apspriedīšu katru šķirni, pirms pievērsīšos tieši apakštipa polimorfismam.

Piespiešana

Piespiešana ir netieša parametru tipa pārveidošana par veidu, ko paredz metode vai operators, tādējādi izvairoties no tipa kļūdām. Šādām izteiksmēm kompilatoram jānosaka, vai ir piemērots binārs + operators pastāv operandu tipiem:

 2.0 + 2.0 2.0 + 2 2.0 + "2" 

Pirmais izteiciens pievieno divus dubultā operandi; Java valoda īpaši definē šādu operatoru.

Tomēr otrajā izteiksmē tiek pievienots a dubultā un an int; Java nenosaka operatoru, kas pieņem šos operandu tipus. Par laimi kompilators netieši pārvērš otro operandu par dubultā un izmanto operatoru, kas definēts diviem dubultā operandi. Tas ir ārkārtīgi ērti izstrādātājam; bez netiešas konvertēšanas rastos kompilēšanas laika kļūda, vai arī programmētājam būtu skaidri jāizdara int uz dubultā.

Trešais izteiciens pievieno a dubultā un a Stīga. Kārtējo reizi Java valoda nenosaka šādu operatoru. Tātad sastādītājs piespiež dubultā operands uz a Stīga, un plus operators veic virkņu savienošanu.

Piespiešana notiek arī pie metodes izsaukšanas. Pieņemsim, ka klase Atvasināts pagarina klasi Bāze, un klase C ir metode ar parakstu m (bāze). Metodes izsaukšanai zemāk esošajā kodā kompilators netieši pārveido atvasināts atsauces mainīgais, kuram ir tips Atvasināts, uz Bāze tips, ko nosaka metodes paraksts. Šī netiešā pārveidošana ļauj m (bāze) metodes ieviešanas kodu, lai izmantotu tikai veida darbības, kuras definējis Bāze:

 C c = jauns C (); Atvasināts atvasināts = jauns Atvasināts (); c.m (atvasināts); 

Atkal, netieša piespiešana metodes izsaukšanas laikā novērš apgrūtinošu tipu vai nevajadzīgu sastādīšanas laika kļūdu. Protams, kompilators joprojām pārbauda, ​​vai visi tipa reklāmguvumi atbilst noteiktajai tipa hierarhijai.

Pārslodze

Pārslodze ļauj izmantot vienu un to pašu operatora vai metodes nosaukumu, lai apzīmētu vairākas, atšķirīgas programmas nozīmes. The + operators, kas izmantots iepriekšējā sadaļā, parādīja divas formas: vienu pievienošanai dubultā operandi, viens savienošanai Stīga objektiem. Citas formas pastāv divu veselu skaitļu, divu garumu un tā tālāk pievienošanai. Mēs piezvanām operatoram pārslogota un paļauties uz kompilatoru, lai izvēlētos atbilstošo funkcionalitāti, pamatojoties uz programmas kontekstu. Kā jau iepriekš minēts, ja nepieciešams, kompilators netieši konvertē operanda tipus, lai tie atbilstu operatora precīzajam parakstam. Lai gan Java norāda noteiktus pārslogotus operatorus, tā neatbalsta lietotāja definētu operatoru pārslodzi.

Java atļauj lietotāja definētu metožu nosaukumu pārslodzi. Klasei var būt vairākas metodes ar tādu pašu nosaukumu, ja metodes paraksti ir atšķirīgi. Tas nozīmē, ka parametru skaitam ir jābūt atšķirīgam, vai vismaz viena parametra pozīcijai jābūt atšķirīgam. Unikālie paraksti ļauj sastādītājam atšķirt metodes, kurām ir tāds pats nosaukums. Sastādītājs sajauc metožu nosaukumus, izmantojot unikālos parakstus, efektīvi izveidojot unikālus nosaukumus. Ņemot to vērā, jebkura acīmredzama polimorfā uzvedība iztvaiko, rūpīgāk pārbaudot.

Gan piespiešana, gan pārslodze tiek klasificēta kā ad hoc, jo katra no tām polimorfu uzvedību nodrošina tikai ierobežotā nozīmē. Lai gan uz tām attiecas plaša polimorfisma definīcija, šīs šķirnes galvenokārt ir izstrādātāja ērtības. Piespiešana novērš apgrūtinošus nepārprotamu veidu apmetumus vai nevajadzīgas kompilatora tipa kļūdas. Savukārt pārslodze nodrošina sintaktisko cukuru, ļaujot izstrādātājam izmantot to pašu nosaukumu atšķirīgām metodēm.

Parametrisks

Parametriskais polimorfisms ļauj izmantot vienu abstrakciju daudzos veidos. Piemēram, a Saraksts abstrakciju, kas atspoguļo viendabīgu objektu sarakstu, varētu nodrošināt kā vispārīgu moduli. Jūs atkārtoti izmantotu abstrakciju, norādot sarakstā esošo objektu tipus. Tā kā parametrizētais tips var būt jebkurš lietotāja definēts datu tips, vispārējai abstrakcijai ir potenciāli bezgalīgs skaits izmantošanas veidu, padarot šo neapšaubāmi visspēcīgāko polimorfisma veidu.

No pirmā acu uzmetiena iepriekš minētais Saraksts abstrakcija var šķist klases lietderība java.util.List. Tomēr Java neatbalsta patiesu parametru polimorfismu tipam drošā veidā, tieši tāpēc java.util.List un java.utilCitas kolekcijas klases ir rakstītas kā Java pirmatnējā klase, java.lang.Object. (Lai iegūtu sīkāku informāciju, skatiet manu rakstu "A Primordial Interface?"). Java vienas saknes ieviešanas mantojums piedāvā daļēju risinājumu, bet ne parametriskā polimorfisma patieso spēku. Ērika Alena izcilajā rakstā "Redziet parametru polimorfisma spēku" aprakstīta vajadzība pēc vispārīgiem Java veidiem un priekšlikumi, lai risinātu Sun Java specifikācijas pieprasījumu Nr. 000014 "Vispārīgu tipu pievienošana Java programmēšanas valodai". (Saiti skatiet resursos.)

Iekļaušana

Iekļaušanas polimorfisms panāk polimorfu uzvedību, izmantojot iekļaušanas saistību starp tipiem vai vērtību kopām. Daudzām objektorientētām valodām, ieskaitot Java, iekļaušanas saistība ir apakštipa relācija. Tātad Java valodā iekļaušanas polimorfisms ir apakštipa polimorfisms.

Kā jau iepriekš minēts, kad Java izstrādātāji parasti atsaucas uz polimorfismu, tie vienmēr nozīmē apakštipa polimorfismu. Lai gūtu stabilu polimorfisma apakštipa spēka novērtējumu, ir jāaplūko mehānismi, kas nodrošina polimorfu uzvedību no tipa orientēta viedokļa. Pārējā šī raksta daļa šo perspektīvu rūpīgi izskata. Īsuma un skaidrības labad es lietoju terminu polimorfisms, lai apzīmētu apakštipa polimorfismu.

Uz tipu orientēts skats

UML klases diagramma 1. attēlā parāda vienkāršo veidu un klases hierarhiju, ko izmanto, lai ilustrētu polimorfisma mehāniku. Modelis attēlo piecus veidus, četras klases un vienu saskarni. Lai gan modeli sauc par klases diagrammu, es to domāju kā tipa diagrammu. Kā sīki aprakstīts sadaļā "Pateicības veids un maiga klase", katra Java klase un saskarne deklarē lietotāja definētu datu tipu. Tātad no īstenošanas neatkarīga skata (t.i., uz tipu orientēta skata) katrs no pieciem attēlā redzamajiem taisnstūriem pārstāv tipu. No ieviešanas viedokļa četri no šiem veidiem tiek definēti, izmantojot klases konstrukcijas, un viens tiek definēts, izmantojot saskarni.

Šis kods nosaka un ievieš katru lietotāja definēto datu tipu. Es apzināti turu pēc iespējas vienkāršāku ieviešanu:

/ * Base.java * / public class Base {public String m1 () {return "Base.m1 ()"; } publiskā virkne m2 (virkne s) {atgriež "Base.m2 (" + s + ")"; }} / * IType.java * / interfeiss IType {String m2 (String s); Virkne m3 (); } / * Derived.java * / public class Derived paplašina Bāzes rīki IType {public String m1 () {return "Derived.m1 ()"; } public String m3 () {atgriešanās "Derived.m3 ()"; }} / * Derived2.java * / public class Derived2 paplašina Derived {public String m2 (String s) {return "Derived2.m2 (" + s + ")"; } publiskā virkne m4 () {atgriež "Derived2.m4 ()"; }} / * Separate.java * / public class Separate īsteno IType {public String m1 () {return "Separate.m1 ()"; } publiskā virkne m2 (virkne s) {atgriež "Separate.m2 (" + s + ")"; } public String m3 () {atgriešanās "Separate.m3 ()"; }} 

Izmantojot šīs tipa deklarācijas un klases definīcijas, 2. attēlā parādīts Java priekšraksta konceptuāls skats:

Atvasināts2 atvasināts2 = jauns Atvasināts2 (); 

Iepriekš minētais paziņojums deklarē skaidri ierakstītu atsauces mainīgo, atvasināts2un pievieno atsauci uz jaunizveidoto Atvasināts2 klases objekts. Augšējais panelis 2. attēlā attēlo Atvasināts2 atsauce kā iluminatoru kopums, caur kuru pamatā esošais Atvasināts2 objektu var apskatīt. Katram ir viena bedre Atvasināts2 tipa darbība. Īstais Atvasināts2 objekts kartē katru Atvasināts2 darbība atbilstoši atbilstošam ieviešanas kodam, kā to nosaka ieviešanas hierarhija, kas definēta iepriekš minētajā kodā. Piemēram, Atvasināts2 objektu kartes m1 () klasē definētajam ieviešanas kodam Atvasināts. Turklāt šis ieviešanas kods ignorē m1 () metode klasē Bāze. A Atvasināts2 atsauces mainīgais nevar piekļūt ignorētajam m1 () ieviešana klasē Bāze. Tas nenozīmē, ka faktiskais ieviešanas kods klasē Atvasināts nevar izmantot Bāze klases ieviešana, izmantojot super.m1 (). Bet, ciktāl tas attiecas uz mainīgo atvasināts2 ir noraizējies, ka kods nav pieejams. Otra kartēšana Atvasināts2 operācijas līdzīgi parāda katra tipa operācijai izpildīto ieviešanas kodu.

Tagad, kad jums ir Atvasināts2 objektu, varat atsaukties uz to ar jebkuru mainīgo, kas atbilst tipam Atvasināts2. Tipa hierarhija 1. attēla UML diagrammā to atklāj Atvasināts, Bāze, un IT tips visi ir super veidi Atvasināts2. Tā, piemēram, a Bāze objektam var pievienot atsauci. 3. attēlā ir parādīts šī Java paziņojuma konceptuālais skats:

Bāzes bāze = atvasināts2; 

Pamatā nav nekādu izmaiņu Atvasināts2 objektu vai kādu no operāciju kartējumiem, kaut arī metodes m3 () un m4 () vairs nav pieejami, izmantojot Bāze atsauce. Zvanīšana m1 () vai m2 (virkne) izmantojot jebkuru mainīgo atvasināts2 vai bāze rezultātā tiek izpildīts viens un tas pats ieviešanas kods:

Stīgu tmp; // Iegūtā2 atsauce (2. attēls) tmp = atvasināts2.m1 (); // tmp ir "Atvasināts.m1 ()" tmp = atvasināts2.m2 ("Labdien"); // tmp ir "Derived2.m2 (sveiki)" // Bāzes atsauce (3. attēls) tmp = base.m1 (); // tmp ir "Atvasināts.m1 ()" tmp = bāze.m2 ("Labdien"); // tmp ir "Derived2.m2 (sveiki)" 

Realizēt identisku uzvedību, izmantojot abas atsauces, ir jēga, jo Atvasināts2 objekts nezina, kā sauc katru metodi. Objekts zina tikai to, ka, izsaukts, tas izpilda ieviešanas hierarhijā noteiktos soļošanas rīkojumus. Šie rīkojumi nosaka, ka metode m1 (), Atvasināts2 objekts izpilda kodu klasē Atvasinātsun metodei m2 (virkne), tas izpilda kodu klasē Atvasināts2. Darbība, ko veic pamatā esošais objekts, nav atkarīga no atsauces mainīgā veida.

Tomēr, lietojot atsauces mainīgos, viss nav vienāds atvasināts2 un bāze. Kā parādīts 3. attēlā, a Bāze tipa atsauce var redzēt tikai Bāze tipa objekta darbības. Tātad, lai gan Atvasināts2 ir metožu kartēšana m3 () un m4 (), mainīgs bāze nevar piekļūt šīm metodēm:

Stīgu tmp; // Iegūtā2 atsauce (2. attēls) tmp = atvasināts2.m3 (); // tmp ir "Atvasināts.m3 ()" tmp = atvasināts2.m4 (); // tmp ir "Atvasināts2.m4 ()" // Bāzes atsauce (3. attēls) tmp = bāze.m3 (); // Kompilēšanas laika kļūda tmp = bāze.m4 (); // Kompilēšanas laika kļūda 

Izpildlaiks

Atvasināts2

objekts joprojām ir pilnībā spējīgs pieņemt vai nu

m3 ()

vai

m4 ()

metodes izsaukumi. Tipa ierobežojumi, kas neatļauj zvanīšanas mēģinājumus, izmantojot

Bāze

$config[zx-auto] not found$config[zx-overlay] not found