Parimet bazë të programimit: shtypja statike dhe dinamike. Shtypja e gjuhëve të programimit Llojet e shtypjes

Ky artikull përmban minimumin e gjërave që thjesht duhet të dini rreth shkrimit, në mënyrë që të mos e quani të keqe shtypjen dinamike, Lisp një gjuhë të pashtypshme dhe C një gjuhë të shtypur fort.

Versioni i plotë përmban një përshkrim të detajuar të të gjitha llojeve të shtypjes, të kalitur me shembuj kodesh, lidhje me gjuhët e njohura të programimit dhe foto demonstruese.

Ju rekomandoj që fillimisht të lexoni versionin e shkurtër të artikullit dhe më pas, nëse dëshironi, atë të plotë.

Version i shkurtër

Gjuhët e programimit të shtypjes zakonisht ndahen në dy kampe të mëdha - të shtypura dhe të pashtypura (të pashtypura). E para përfshin C, Python, Scala, PHP dhe Lua, ndërsa e dyta përfshin gjuhën e asamblesë, Forth dhe Brainfuck.

Meqenëse "shtypja e pashtypshme" është në thelb po aq e thjeshtë sa një tapë, ajo nuk ndahet më tej në lloje të tjera. Por gjuhët e shtypura ndahen në disa kategori më të mbivendosura:

  • Shtypja statike / dinamike. Static përcaktohet nga fakti se llojet përfundimtare të variablave dhe funksioneve vendosen në kohën e kompilimit. ato. tashmë përpiluesi është 100% i sigurt se cili lloj është ku. Në shtypjen dinamike, të gjitha llojet përcaktohen në kohën e ekzekutimit.

    Shembuj:
    Statike: C, Java, C#;
    Dinamik: Python, JavaScript, Ruby.

  • Shkrimi i fortë / i dobët (gjithashtu nganjëherë quhet i fortë / i lirë). Shtypja e fortë dallohet nga fakti se gjuha nuk lejon përzierjen e llojeve të ndryshme në shprehje dhe nuk kryen konvertime automatike të nënkuptuara, për shembull, nuk mund të zbritësh një grup nga një varg. Gjuhët e shtypura dobët kryejnë shumë konvertime të nënkuptuara automatikisht, edhe nëse humbja e saktësisë ose konvertimit mund të jetë e paqartë.

    Shembuj:
    E fortë: Java, Python, Haskell, Lisp;
    E dobët: C, JavaScript, Visual Basic, PHP.

  • Shtypje e qartë / e nënkuptuar. Gjuhët e shtypura në mënyrë eksplicite ndryshojnë në atë që lloji i variablave / funksioneve / argumenteve të tyre të reja duhet të vendosen në mënyrë eksplicite. Prandaj, gjuhët me shtypje të nënkuptuar e zhvendosin këtë detyrë te përpiluesi / interpretuesi.

    Shembuj:
    E qartë: C++, D, C#
    Implicite: PHP, Lua, JavaScript

Duhet gjithashtu të theksohet se të gjitha këto kategori kryqëzohen, për shembull, gjuha C ka një shtypje statike të dobët eksplicite, dhe gjuha Python ka një shtypje implicite dinamike të fortë.

Sidoqoftë, nuk ka gjuhë me shtypje statike dhe dinamike në të njëjtën kohë. Edhe pse shikoj përpara, do të them që jam shtrirë këtu - ato ekzistojnë vërtet, por më shumë për këtë më vonë.

version i detajuar

Nëse versioni i shkurtër nuk është i mjaftueshëm për ju, mirë. Nuk është çudi që shkrova në detaje? Gjëja kryesore është se në versionin e shkurtër ishte thjesht e pamundur të përshtateshin të gjitha informacionet e dobishme dhe interesante, dhe versioni i detajuar ndoshta do të jetë shumë i gjatë që të gjithë ta lexojnë pa u lodhur.

Shtypje e pashtypshme

Në gjuhët e programimit të pashtypura, të gjitha entitetet konsiderohen të jenë vetëm sekuenca të bitave me gjatësi të ndryshme.

Shkrimi i pashtypshëm është zakonisht i natyrshëm në gjuhët e nivelit të ulët (gjuhë asembler, Forth) dhe gjuhë ezoterike (Brainfuck, HQ9, Piet). Megjithatë, së bashku me disavantazhet e tij, ajo ka edhe disa avantazhe.

Përparësitë
  • Ju lejon të shkruani në një nivel jashtëzakonisht të ulët dhe përpiluesi / interpretuesi nuk do të ndërhyjë në asnjë lloj kontrolli. Ju jeni të lirë të kryeni çdo operacion mbi çdo lloj të dhënash.
  • Kodi që rezulton është zakonisht më efikas.
  • Transparenca e udhëzimeve. Me njohuri të gjuhës, zakonisht nuk ka dyshim se çfarë është ky apo ai kod.
Të metat
  • Kompleksiteti. Shpesh ekziston nevoja për të përfaqësuar vlera komplekse si listat, vargjet ose strukturat. Kjo mund të shkaktojë bezdi.
  • Asnjë kontroll. Çdo veprim i pakuptimtë, si zbritja e një treguesi në një grup nga një karakter, do të konsiderohet krejtësisht normale, e cila është e mbushur me gabime delikate.
  • Niveli i ulët i abstraksionit. Puna me çdo lloj të dhënash komplekse nuk ndryshon nga puna me numra, gjë që sigurisht do të krijojë shumë vështirësi.
Shtypje e fortë pa tip?

Po, kjo ekziston. Për shembull, në gjuhën e asamblesë (për arkitekturën x86 / x86-64, nuk i njoh të tjerët) nuk mund të montoni një program nëse përpiqeni të ngarkoni të dhëna nga regjistri rax (64 bit) në regjistrin cx (16 copa).

mov cx, eax ; gabimi i kohës së montimit

Pra, rezulton se asembleri ka ende shtypje? Mendoj se këto kontrolle nuk janë të mjaftueshme. Dhe mendimi juaj, natyrisht, varet vetëm nga ju.

Shtypje statike dhe dinamike

Gjëja kryesore që dallon shtypjen statike (statike) nga dinamike (dinamike) është se i gjithë kontrolli i tipit kryhet në kohën e kompilimit, dhe jo në kohën e ekzekutimit.

Disa njerëz mund të mendojnë se shtypja statike është shumë kufizuese (në fakt, është, por prej kohësh është hequr qafe me ndihmën e disa teknikave). Për disa, gjuhët e shtypura në mënyrë dinamike po luajnë me zjarrin, por cilat veçori i bëjnë ato të dallohen? A kanë mundësi të ekzistojnë të dyja speciet? Nëse jo, pse ka kaq shumë gjuhë të shtypura në mënyrë statike dhe dinamike?

Le ta kuptojmë.

Përfitimet e shtypjes statike
  • Kontrollet e tipit ndodhin vetëm një herë, në kohën e përpilimit. Dhe kjo do të thotë se nuk do të kemi nevojë të zbulojmë vazhdimisht nëse po përpiqemi të ndajmë një numër me një varg (dhe ose të hedhim një gabim ose të kryejmë një konvertim).
  • Shpejtësia e ekzekutimit. Është e qartë nga pika e mëparshme se gjuhët e shtypura në mënyrë statike janë pothuajse gjithmonë më të shpejta se ato të shtypura në mënyrë dinamike.
  • Në disa kushte shtesë, ju lejon të zbuloni gabime të mundshme tashmë në fazën e përpilimit.
Përfitimet e shtypjes dinamike
  • Lehtësia e krijimit të koleksioneve universale - grumbuj gjithçkaje dhe gjithçkaje (rrallë lind një nevojë e tillë, por kur të lindë shtypja dinamike do të ndihmojë).
  • Lehtësia e përshkrimit të algoritmeve të përgjithësuara (për shembull, renditja e grupeve, e cila do të funksionojë jo vetëm në një listë numrash të plotë, por edhe në një listë numrash realë dhe madje edhe në një listë vargjesh).
  • Lehtë për t'u mësuar - Gjuhët e shtypura në mënyrë dinamike janë zakonisht shumë të mira për të filluar programimin.

Programim gjenerik

Epo, argumenti më i rëndësishëm për shtypjen dinamike është lehtësia e përshkrimit të algoritmeve gjenerike. Le të imagjinojmë një problem - na duhet një funksion kërkimi për disa vargje (ose lista) - një grup numrash të plotë, një grup real dhe një grup karakteresh.

Si do ta zgjidhim? Le ta zgjidhim në 3 gjuhë të ndryshme: një me shtypje dinamike dhe dy me shtypje statike.

Unë do të marr një nga algoritmet më të thjeshta të kërkimit - numërimin. Funksioni do të marrë elementin e kërkuar, vetë grupin (ose listën) dhe do të kthejë indeksin e elementit, ose nëse elementi nuk gjendet - (-1).

Zgjidhje dinamike (Python):

Def find(required_element, list): për (indeks, element) në enumerate(list): nëse elementi == elementi_i kërkuar: kthen indeksin (-1)

Siç mund ta shihni, gjithçka është e thjeshtë dhe nuk ka probleme me faktin se lista mund të përmbajë numra çift, lista çift, edhe pse nuk ka vargje të tjera. Shume mire. Le të shkojmë më tej - zgjidhni të njëjtin problem në C!

Zgjidhja statike (C):

int i panënshkruar find_int(int kërkohet_element, grup int, madhësi int e panënshkruar) ( for (int i panënshkruar i = 0; i< size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_float(float required_element, float array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_char(char required_element, char array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); }

Epo, çdo funksion individualisht është i ngjashëm me versionin Python, por pse janë tre? A ka humbur programimi statik?

Po dhe jo. Ekzistojnë disa teknika programimi, një prej të cilave do ta shqyrtojmë tani. Quhet Generic Programming, dhe gjuha C++ e mbështet mjaft mirë. Le të hedhim një vështrim në versionin e ri:

Zgjidhje statike (programim gjenerik, C++):

shabllon i panënshkruar int find(T kërkohet_element, std::vektor grup) ( për (int i panënshkruar i = 0; i< array.size(); ++i) if (required_element == array[i]) return i; return (-1); }

Mirë! Nuk duket shumë më i komplikuar se versioni Python, dhe nuk u desh shumë shkrim. Për më tepër, ne morëm zbatimin për të gjitha grupet, jo vetëm 3 që kërkohen për të zgjidhur problemin!

Ky version duket se është pikërisht ajo që na nevojitet - ne marrim avantazhet e shtypjes statike dhe disa nga avantazhet e dinamikës.

Është mirë që është edhe e mundur, por mund të jetë edhe më mirë. Së pari, programimi gjenerik mund të jetë më i përshtatshëm dhe më i bukur (për shembull, në Haskell). Së dyti, përveç programimit gjenerik, mund të përdorni edhe polimorfizëm (rezultati do të jetë më i keq), mbingarkesa e funksionit (në mënyrë të ngjashme) ose makro.

Statike në dinamikë

Duhet të përmendet gjithashtu se shumë gjuhë statike lejojnë shtypjen dinamike, për shembull:

  • C# mbështet pseudo-tipin dinamik.
  • F# mbështet sheqerin sintaksor në formën e operatorit ?, i cili mund të përdoret për të imituar shtypjen dinamike.
  • Haskell - shtypja dinamike ofrohet nga moduli Data.Dynamic.
  • Delphi - përmes një Varianti të tipit të veçantë.

Gjithashtu, disa gjuhë të shtypura në mënyrë dinamike ju lejojnë të përfitoni nga shtypja statike:

  • Common Lisp - deklaratat e tipit.
  • Perl - që nga versioni 5.6, mjaft i kufizuar.

Shkrimi i fortë dhe i dobët

Gjuhët e shtypura fort nuk lejojnë përzierjen e entiteteve të llojeve të ndryshme në shprehje dhe nuk kryejnë asnjë konvertim automatik. Ato quhen edhe "gjuhë të shtypura fort". Termi anglisht për këtë është shtypja e fortë.

Gjuhët e shtypura dobët, përkundrazi, inkurajojnë programuesin të përziejë lloje të ndryshme në një shprehje, dhe vetë përpiluesi do të konvertojë gjithçka në një lloj të vetëm. Ato quhen edhe "gjuhë të shtypura dobët". Termi anglisht për këtë është shtypja e dobët.

Shkrimi i dobët shpesh ngatërrohet me shtypjen dinamike, gjë që është krejtësisht e gabuar. Një gjuhë e shtypur në mënyrë dinamike mund të jetë e shtypur dobët dhe fort.

Megjithatë, pak njerëz i kushtojnë rëndësi rreptësisë së shtypjes. Shpesh pretendohet se nëse një gjuhë shtypet në mënyrë statike, atëherë mund të kapni shumë gabime të mundshme përpilimi. Ata ju gënjejnë!

Gjuha gjithashtu duhet të ketë shtypje të fortë. Në të vërtetë, nëse përpiluesi në vend që të raportojë një gabim thjesht shton një varg në një numër, ose, edhe më keq, zbret një tjetër nga një grup, çfarë dobie ka për ne që të gjitha "kontrollet" e llojeve të jenë në fazën e përpilimit? Kjo është e drejtë - shtypja e dobët statike është edhe më e keqe se shtypja e fortë dinamike! (Epo, ky është mendimi im)

Pra, pse shtypja e dobët nuk ka fare përparësi? Mund të duket kështu, por pavarësisht se unë jam një mbështetës i fortë i të shkruarit të fortë, duhet të pajtohem që edhe shtypja e dobët ka përparësi.

Dëshironi të dini cilat prej tyre?

Përfitimet e shtypjes së fortë
  • Besueshmëria - Do të merrni një përjashtim ose gabim përpilimi në vend që të silleni keq.
  • Shpejtësia - në vend të konvertimeve të nënkuptuara, të cilat mund të jenë mjaft të shtrenjta, me shtypje të fortë, duhet t'i shkruani ato në mënyrë eksplicite, gjë që e bën programuesin të paktën të vetëdijshëm se kjo pjesë e kodit mund të jetë e ngadaltë.
  • Të kuptuarit se si funksionon programi - përsëri, në vend të hedhjes së tipit të nënkuptuar, programuesi shkruan gjithçka vetë, që do të thotë se ai e kupton afërsisht se krahasimi i një vargu dhe një numri nuk ndodh vetvetiu dhe jo me magji.
  • Siguria - kur shkruani transformime me dorë, ju e dini saktësisht se çfarë po transformoni dhe në çfarë. Gjithashtu, gjithmonë do të kuptoni se konvertime të tilla mund të çojnë në humbje të saktësisë dhe rezultate të pasakta.
Përfitimet e shtypjes së dobët
  • Lehtësia e përdorimit të shprehjeve të përziera (për shembull, nga numra të plotë dhe numra realë).
  • Abstragimi nga shtypja dhe fokusimi në detyrë.
  • Shkurtësia e rekordit.

Mirë, e kuptuam, rezulton se shtypja e dobët ka edhe avantazhe! A ka ndonjë mënyrë për të transferuar avantazhet e shtypjes së dobët në shtypje të fortë?

Rezulton se ka edhe dy.

Transmetim i tipit implicit, në situata të paqarta dhe pa humbje të të dhënave

Wow… Një paragraf mjaft i gjatë. Më lejoni ta shkurtoj më tej në "konvertim të nënkuptuar të kufizuar" Pra, çfarë do të thotë situata e paqartë dhe humbja e të dhënave?

Një situatë e paqartë është një transformim ose operacion në të cilin thelbi është menjëherë i qartë. Për shembull, mbledhja e dy numrave është një situatë e paqartë. Por konvertimi i një numri në një grup nuk është (ndoshta do të krijohet një grup me një element, ndoshta një grup me një gjatësi të tillë, i mbushur me elementë si parazgjedhje, dhe ndoshta një numër do të konvertohet në një varg dhe më pas në një grup të personazheve).

Humbja e të dhënave është edhe më e lehtë. Nëse e konvertojmë numrin real 3.5 në një numër të plotë, do të humbasim disa nga të dhënat (në fakt, edhe ky veprim është i paqartë - si do të bëhet rrumbullakimi? lart? poshtë? Hedhja e pjesës thyesore?).

Konvertimet në situata të paqarta dhe konvertimet me humbje të të dhënave janë shumë, shumë të këqija. Nuk ka asgjë më të keqe se kjo në programim.

Nëse nuk më besoni, mësoni gjuhën PL/I ose thjesht kërkoni specifikimet e saj. Ka rregulla konvertimi midis TË GJITHA llojeve të të dhënave! Është vetëm ferr!

Mirë, le të kujtojmë për konvertimin e kufizuar të nënkuptuar. A ka gjuhë të tilla? Po, për shembull në Pascal mund të konvertohet një numër i plotë në një numër real, por jo anasjelltas. Ekzistojnë gjithashtu mekanizma të ngjashëm në C#, Groovy dhe Common Lisp.

Mirë, thashë se ka një mënyrë tjetër për të marrë disa avantazhe të shtypjes së dobët në një gjuhë të fortë. Dhe po, ekziston dhe quhet polimorfizëm konstruktor.

Do ta shpjegoj duke përdorur gjuhën e mrekullueshme Haskell si shembull.

Konstruktorët polimorfikë u krijuan si rezultat i vëzhgimit se konvertimet e sigurta të nënkuptuara nevojiten më shpesh kur përdoren literale numerike.

Për shembull, në shprehjen pi + 1, ju nuk dëshironi të shkruani pi + 1.0 ose pi + float(1). Unë dua të shkruaj vetëm pi + 1!

Dhe kjo bëhet në Haskell, falë faktit se literali 1 nuk ka një tip konkret. Nuk është as e tërë, as reale, as e ndërlikuar. Është vetëm një numër!

Si rezultat, kur shkruajmë një shumë të thjeshtë funksioni x y që shumëzon të gjithë numrat nga x në y (me një rritje prej 1), marrim disa versione në të njëjtën kohë - shumën për numrat e plotë, shumën për realet, shumën për racionalët, shumën për numrat kompleks. , dhe madje shuma për të gjitha ato lloje numerike që ju vetë keni përcaktuar.

Sigurisht, kjo teknikë kursen vetëm kur përdorni shprehje të përziera me fjalë për fjalë numerike, dhe kjo është vetëm maja e ajsbergut.

Kështu, mund të themi se mënyra më e mirë për të dalë do të jetë balancimi në prag, midis shtypjes së fortë dhe të dobët. Por deri më tani, asnjë gjuhë nuk ka një ekuilibër të përsosur, kështu që unë anoj më shumë drejt gjuhëve të shtypura fort (të tilla si Haskell, Java, C#, Python) sesa gjuhë të shtypura dobët (si C, JavaScript, Lua, PHP) .

Shtypje eksplicite dhe implicite

Një gjuhë e shtypur në mënyrë eksplicite kërkon që programuesi të specifikojë llojet e të gjitha variablave dhe funksioneve që ai deklaron. Termi anglisht për këtë është shtypja e qartë.

Një gjuhë e shtypur në mënyrë implicite, nga ana tjetër, ju fton të harroni llojet dhe t'ia lini detyrën e konkluzionit të tipit kompajlerit ose interpretuesit. Termi anglisht për këtë është shtypja e nënkuptuar.

Në fillim, dikush mund të mendojë se shtypja e nënkuptuar është ekuivalente me shtypjen dinamike, dhe shtypja eksplicite është e barabartë me shtypjen statike, por do të shohim më vonë se nuk është kështu.

A ka përparësi për secilin lloj, dhe përsëri, a ka kombinime të tyre dhe a ka ndonjë gjuhë që mbështet të dyja metodat?

Përfitimet e shtypjes së qartë
  • Të kesh një nënshkrim për çdo funksion (për shembull, int add(int, int)) e bën të lehtë përcaktimin se çfarë bën funksioni.
  • Programuesi menjëherë shkruan se çfarë lloj vlerash mund të ruhen në një variabël të veçantë, gjë që eliminon nevojën për ta mbajtur mend këtë.
Përfitimet e shtypjes së nënkuptuar
  • Shkurtorja - def add(x, y) është qartësisht më e shkurtër se int add(int x, int y).
  • Rezistenca ndaj ndryshimit. Për shembull, nëse në një funksion ndryshorja e përkohshme ishte e të njëjtit lloj si argumenti hyrës, atëherë në një gjuhë të shtypur në mënyrë eksplicite, kur ndryshon lloji i argumentit të hyrjes, do të duhet të ndryshohet edhe lloji i ndryshores së përkohshme.

Epo, duket se të dyja qasjet kanë të mirat dhe të këqijat (dhe kush priste ndonjë gjë tjetër?), kështu që le të kërkojmë mënyra për të kombinuar këto dy qasje!

Shkrimi i qartë sipas zgjedhjes

Ka gjuhë me shtypje të nënkuptuar si parazgjedhje dhe aftësi për të specifikuar llojin e vlerave nëse është e nevojshme. Përpiluesi do të konkludojë automatikisht llojin e vërtetë të shprehjes. Një gjuhë e tillë është Haskell, më lejoni t'ju jap një shembull të thjeshtë për ta ilustruar:

Nuk ka tip eksplicit add (x, y) = x + y -- Lloji eksplicit add:: (Integer, Integer) -> Integer add (x, y) = x + y

Shënim: Kam përdorur qëllimisht një funksion të pandërprerë, dhe gjithashtu kam shkruar qëllimisht një nënshkrim privat në vend të shtimit më të përgjithshëm:: (Num a) -> a -> a -> a, sepse Doja të tregoja idenë, pa shpjeguar sintaksën e Haskell-it.

Hm. Siç mund ta shohim, është shumë e bukur dhe e shkurtër. Hyrja e funksionit merr vetëm 18 karaktere për rresht, duke përfshirë hapësirat!

Sidoqoftë, përfundimi automatik i tipit është mjaft i ndërlikuar, dhe madje edhe në një gjuhë të lezetshme si Haskell, ndonjëherë dështon. (një shembull është kufizimi i monomorfizmit)

A ka gjuhë me shkrim të qartë si parazgjedhje dhe shtypje të nënkuptuar sipas nevojës? Kon
me siguri.

Shkrimi i nënkuptuar sipas zgjedhjes

Standardi i ri i gjuhës C++, i quajtur C++11 (i quajtur më parë C++0x), prezantoi fjalën kyçe automatike, e cila i lejon përpiluesit të konkludojë llojin nga konteksti:

Le të krahasojmë: // Specifikimi i tipit manual i panënshkruar int a = 5; i panënshkruar int b = a + 3; // Konkluzionet automatike të tipit të panënshkruara int a = 5; auto b = a + 3;

Jo keq. Por rekordi nuk është zvogëluar shumë. Le të shohim një shembull me përsëritësit (nëse nuk e kuptoni, mos kini frikë, gjëja kryesore që duhet të theksohet është se rekordi është zvogëluar shumë për shkak të daljes automatike):

// Lloji manual std::vector vec = Vektor i rastësishëm(30); për (std::vector::const_iterator it = vec.cbegin(); ...) ( ... ) // Lloji automatik i konkluzionit automatik vec = i rastësishëmVektor (tridhjetë); për (auto it = vec.cbegin (); ...) ( ... )

Uau! Këtu është shkurtesa. Mirë, por a është e mundur të bëhet diçka në frymën e Haskell, ku lloji i kthimit do të varet nga llojet e argumenteve?

Përsëri, përgjigjja është po, falë fjalës kyçe decltype në kombinim me auto:

// Lloji manual int divide(int x, int y) ( ... ) // Automatic type deduction auto divide (int x, int y) -> decltype(x / y) ( ... )

Kjo formë shënimi mund të mos tingëllojë e shkëlqyeshme, por kur kombinohet me gjenerikë (shabllone / gjenerikë), shtypja e nënkuptuar ose përfundimi automatik i tipit bën mrekulli.

Disa gjuhë programimi sipas këtij klasifikimi

Unë do të jap një listë të shkurtër të gjuhëve të njohura dhe do të shkruaj se si ato kategorizohen në secilën kategori "shtypje".

JavaScript - Ruby dinamike / e dobët / e nënkuptuar - Dynamic / e fortë / Python e nënkuptuar - Java dinamike / e fortë / e nënkuptuar - PHP statike / e fortë / e qartë - Dinamike / e dobët / e nënkuptuar C - statike / e dobët / e qartë C++ - Statike / gjysmë e fortë / Perl eksplicite - Dynamic / Dob / Implicit Objective-C - Static / Dob / Explicit C# - Statike / e fortë / Haskell eksplicite - Statike / e fortë / e nënkuptuar Common Lisp - Dinamike / e fortë / e nënkuptuar

Ndoshta kam bërë një gabim diku, veçanërisht me CL, PHP dhe Obj-C, nëse keni një mendim tjetër për ndonjë gjuhë, shkruani në komente.

Thjeshtësia e shtypjes në qasjen OO është pasojë e thjeshtësisë së modelit të llogaritjes së objektit. Duke lënë mënjanë detajet, mund të themi se gjatë ekzekutimit të një sistemi OO, ndodh vetëm një lloj ngjarjeje - një thirrje tipare:


duke treguar operacionin f sipër objektit të bashkangjitur x, me kalimin e argumentit arg(ndoshta argumente të shumta ose asnjë fare). Programuesit e Smalltalk flasin në këtë rast për "kalimin e një objekti x mesazhe f me një argument arg", por ky është vetëm një ndryshim në terminologji, dhe për këtë arsye nuk është i rëndësishëm.

Se gjithçka bazohet në këtë Konstruksion Bazë shpjegon një pjesë të ndjenjës së bukurisë në idetë OO.

Nga Konstruksioni Bazë ndiqni ato situata jonormale që mund të lindin në procesin e ekzekutimit:

Përkufizimi: shkelje e tipit

Një shkelje e tipit të kohës së ekzekutimit, ose shkurt një shkelje e tipit, ndodh në momentin e telefonatës. x.f(arg), ku x ngjitur me një objekt OBJ nëse:

[x]. nuk ka asnjë komponent që përputhet f dhe të zbatueshme për OBJ,

[x]. ka një komponent të tillë, megjithatë, argumenti arg e papranueshme për të.

Problemi i shtypjes është të shmangni situata si kjo:

Problemi i shtypjes për sistemet OO

Kur gjejmë se mund të ndodhë një shkelje e tipit në ekzekutimin e një sistemi OO?

Fjala kyçe është kur. Herët a vonë do të kuptoni se ka një shkelje të llojit. Për shembull, përpjekja për të ekzekutuar komponentin "Torpedo Launch" në një objekt "Employee" nuk do të funksionojë dhe ekzekutimi do të dështojë. Sidoqoftë, mund të preferoni të gjeni gabime sa më shpejt që të jetë e mundur dhe jo më vonë.

Shtypje statike dhe dinamike

Megjithëse opsionet e ndërmjetme janë të mundshme, dy qasje kryesore janë paraqitur këtu:

[x]. Shtypja dinamike: prisni që çdo telefonatë të përfundojë dhe më pas merrni një vendim.

[x]. Shtypja statike: duke pasur parasysh një sërë rregullash, përcaktoni nga teksti burimor nëse shkeljet e tipit janë të mundshme gjatë ekzekutimit. Sistemi ekzekutohet nëse rregullat garantojnë që nuk ka gabime.

Këto terma janë të lehta për t'u shpjeguar: me shtypjen dinamike, kontrolli i tipit ndodh gjatë funksionimit të sistemit (në mënyrë dinamike), ndërsa me shtypjen statike, kontrollimi i tipit kryhet në tekst në mënyrë statike (para ekzekutimit).

Shtypja statike përfshin kontrollin automatik, i cili zakonisht është përgjegjësi e përpiluesit. Si rezultat, ne kemi një përkufizim të thjeshtë:

Përkufizimi: gjuhë e shtypur në mënyrë statike

Një gjuhë OO shtypet në mënyrë statike nëse vjen me një sërë rregullash të qëndrueshme, të verifikuara nga përpiluesi, që sigurojnë që ekzekutimi i sistemit të mos çojë në shkelje të tipit.

Termi " të fortë duke shtypur" ( të fortë). Ai korrespondon me natyrën ultimatum të përkufizimit, që kërkon mungesë të plotë të shkeljes së llojit. E mundur dhe i dobët (i dobët) forma të shtypjes statike, në të cilat rregullat eliminojnë shkelje të caktuara pa i eliminuar ato plotësisht. Në këtë kuptim, disa gjuhë OO janë statikisht të shtypura dobët. Do të luftojmë për shtypjen më të fortë.

Në gjuhët e shtypura në mënyrë dinamike, të njohura si gjuhë të pashtypura, nuk ka deklarata të tipit dhe çdo vlerë mund t'i bashkëngjitet entiteteve në kohën e ekzekutimit. Kontrollimi i tipit statik nuk është i mundur në to.

Rregullat e shtypjes

Shënimi ynë OO është i shtypur në mënyrë statike. Rregullat e saj të tipit u prezantuan në leksionet e mëparshme dhe përfundojnë në tre kërkesa të thjeshta.

[x]. Gjatë deklarimit të çdo entiteti ose funksioni, lloji i tij duhet të specifikohet, për shembull, acc: LLOGARIA. Çdo nënprogram ka 0 ose më shumë argumente formale, lloji i të cilave duhet të jepet, për shembull: vendos (x: G; i: INTEGER).

[x]. Në çdo detyrë x:= y dhe në çdo thirrje nënprograme në të cilën yështë argumenti aktual për argumentin formal x, lloji i burimit y duhet të jetë në përputhje me llojin e synuar x. Përkufizimi i përputhshmërisë bazohet në trashëgiminë: B i përputhshëm me A, nëse është pasardhës i tij, - plotësuar me rregulla për parametrat gjenerikë (shih Leksionin 14).

[x]. Thirrni x.f(arg) e kërkon atë f ishte një komponent i klasës bazë për llojin e synuar x, dhe f duhet të eksportohet në klasën në të cilën shfaqet thirrja (shih 14.3).

Realizmi

Megjithëse përkufizimi i një gjuhe të shtypur statikisht është mjaft i saktë, ai nuk mjafton - nevojiten kritere joformale kur krijohen rregullat e shtypjes. Le të shqyrtojmë dy raste ekstreme.

[x]. Gjuhë krejtësisht e saktë, në të cilin çdo sistem sintaksorisht i saktë është gjithashtu i saktë. Rregullat e deklarimit të tipit nuk janë të nevojshme. Gjuhë të tilla ekzistojnë (mendoni për shënimin polak për mbledhjen dhe zbritjen e numrave të plotë). Fatkeqësisht, asnjë gjuhë e vërtetë universale nuk e plotëson këtë kriter.

[x]. Gjuhë krejtësisht e gabuar, e cila është e lehtë për t'u krijuar duke marrë çdo gjuhë ekzistuese dhe duke shtuar një rregull shtypjeje që bën ndonjë sistemi është i pasaktë. Sipas përkufizimit, kjo gjuhë është e shtypur: meqenëse nuk ka sisteme që përputhen me rregullat, asnjë sistem nuk do të shkaktojë shkelje të tipit.

Mund të themi se gjuhët e tipit të parë përshtatet, por të padobishme, kjo e fundit mund të jetë e dobishme, por jo e përshtatshme.

Në praktikë, ne kemi nevojë për një sistem tipi që është i dobishëm dhe i dobishëm në të njëjtën kohë: mjaft i fuqishëm për të përmbushur nevojat e llogaritjeve dhe mjaftueshëm i përshtatshëm për të mos na detyruar të shkojmë te ndërlikimet për të kënaqur rregullat e shtypjes.

Do të themi se gjuha realiste nëse është i përdorshëm dhe i dobishëm në praktikë. Në ndryshim nga përkufizimi i shtypjes statike, i cili i jep një përgjigje të detyrueshme pyetjes: " A është X i shtypur në mënyrë statike?“, përkufizimi i realizmit është pjesërisht subjektiv.

Në këtë leksion, ne do të sigurohemi që shënimi që propozojmë të jetë realist.

Pesimizmi

Shkrimi statik çon nga natyra e tij në një politikë "pesimiste". Një përpjekje për të garantuar këtë të gjitha llogaritjet nuk çojnë në dështime, refuzon llogaritjet që mund të përfundonin pa gabime.

Konsideroni një gjuhë të rregullt, jo objektive, të ngjashme me Paskalin me lloje të ndryshme REAL dhe I PLOTË. Kur përshkruani n: INTEGER; r: Real operatori n:=r do të refuzohet si shkelje e rregullave. Kështu, përpiluesi do të refuzojë të gjitha deklaratat e mëposhtme:


Nëse i lejojmë të ekzekutojnë, do të shohim se [A] do të funksionojë gjithmonë, pasi çdo sistem numrash ka një paraqitje të saktë të numrit real 0.0, i cili përkthehet pa mëdyshje në 0 numra të plotë. [B] pothuajse me siguri do të funksionojë gjithashtu. Rezultati i veprimit [C] nuk është i dukshëm (duam ta marrim rezultatin duke rrumbullakosur apo hedhur poshtë pjesën thyesore?). [D] do të bëjë punën, ashtu si edhe operatori:


nëse ^ 2< 0 then n:= 3.67 end [E]

e cila përfshin një detyrë të paarritshme ( n^2është katrori i numrit n). Pas zëvendësimit n^2n vetëm një seri vrapimesh do të japë rezultatin e duhur. Detyra n një vlerë e madhe reale që nuk mund të përfaqësohet si një numër i plotë do të rezultojë në një dështim.

Në gjuhët e shtypura, të gjithë këta shembuj (punojnë, nuk funksionojnë, ndonjëherë punojnë) trajtohen pa mëshirë si shkelje të rregullave për përshkrimin e llojeve dhe refuzohen nga çdo përpilues.

Pyetja nuk është ne do nëse jemi pesimistë, dhe në fakt, sa shumë ne mund të lejojmë të jemi pesimistë. Kthimi te kërkesa e realizmit: nëse rregullat e tipit janë aq pesimiste saqë e pengojnë llogaritjen të jetë e lehtë për t'u shkruar, ne do t'i refuzojmë ato. Por nëse arritja e sigurisë së tipit arrihet me një humbje të vogël të fuqisë shprehëse, ne do t'i pranojmë ato. Për shembull, në një mjedis zhvillimi që ofron funksione për rrumbullakimin dhe nxjerrjen e një pjese të plotë - rrumbullakët dhe cungoj, operator n:=r konsiderohet e pasaktë, me të drejtë, sepse ju detyron të shkruani në mënyrë eksplicite konvertimin real në numër të plotë, në vend që të përdorni konvertimet e paqarta të paracaktuara.

Shtypja statike: si dhe pse

Ndërsa përfitimet e shtypjes statike janë të dukshme, është mirë të flasim përsëri për to.

Përparësitë

Ne renditëm arsyet e përdorimit të shtypjes statike në teknologjinë e objekteve në fillim të leksionit. Është besueshmëria, lehtësia e të kuptuarit dhe efikasiteti.

Besueshmëria për shkak të zbulimit të gabimeve që përndryshe mund të shfaqeshin vetëm gjatë operimit dhe vetëm në disa raste. E para nga rregullat, e cila detyron deklarimin e entiteteve si dhe funksioneve, prezanton tepricë në tekstin e programit, i cili lejon kompajlerin, duke përdorur dy rregullat e tjera, të zbulojë mospërputhjet midis përdorimit të synuar dhe atij aktual të entiteteve, komponentëve dhe shprehjet.

Zbulimi i hershëm i gabimeve është gjithashtu i rëndësishëm sepse sa më shumë të vonojmë gjetjen e tyre, aq më shumë do të rritet kostoja e korrigjimit. Kjo veti, e cila është intuitivisht e kuptueshme për të gjithë programuesit profesionistë, konfirmohet në mënyrë sasiore nga veprat e njohura gjerësisht të Boehm. Varësia e kostos së korrigjimit nga koha e gjetjes së gabimeve tregohet në grafik, e ndërtuar sipas të dhënave të një numri projektesh të mëdha industriale dhe eksperimente të kryera me një projekt të vogël të kontrolluar:

Oriz. 17.1. Kostot krahasuese të rregullimit të gabimeve (publikuar me leje)

Lexueshmëria ose Lehtësia e të kuptuarit(lexueshmëria) ka avantazhet e veta. Në të gjithë shembujt në këtë libër, shfaqja e një tipi në një ent i jep lexuesit informacion për qëllimin e tij. Lexueshmëria është jashtëzakonisht e rëndësishme në fazën e mirëmbajtjes.

Së fundi, efikasiteti mund të përcaktojë suksesin ose dështimin e teknologjisë së objektit në praktikë. Në mungesë të shtypjes statike gjatë ekzekutimit x.f(arg) mund të marrë çdo sasi kohe. Arsyeja për këtë është se në kohën e ekzekutimit, mos gjetja f në klasën e synuar bazë x, kërkimi do të vazhdojë te pasardhësit e tij dhe kjo është një rrugë e sigurt drejt joefikasitetit. Ju mund ta lehtësoni problemin duke përmirësuar kërkimin e një komponenti në hierarki. Autorët e gjuhës së vetvetes kanë bërë shumë punë duke u përpjekur të gjenerojnë kodin më të mirë për një gjuhë të shtypur në mënyrë dinamike. Por ishte shtypja statike që lejoi që një produkt i tillë OO të afrohej ose të barazohej në efikasitet me softuerin tradicional.

Çelësi i shtypjes statike është ideja e deklaruar tashmë se përpiluesi që gjeneron kodin për konstruktin x.f(arg), njeh llojin x. Për shkak të polimorfizmit, nuk është e mundur të përcaktohet në mënyrë unike versioni i duhur i komponentit f. Por deklarata ngushton grupin e llojeve të mundshme, duke lejuar përpiluesin të ndërtojë një tabelë që ofron qasje në të saktën f me kosto minimale, me konstante të kufizuar vështirësia e aksesit. U kryen optimizime shtesë lidhje statike dhe zëvendësime (rreshtim)- lehtësohen gjithashtu nga shtypja statike, duke eliminuar plotësisht shpenzimet e sipërme, aty ku është e mundur.

Argumentet për shtypjen dinamike

Përkundër gjithë kësaj, shtypja dinamike nuk i humbet ndjekësit e saj, veçanërisht në mesin e programuesve të Smalltalk. Argumentet e tyre bazohen kryesisht në realizmin e diskutuar më sipër. Ata besojnë se shtypja statike është shumë kufizuese, duke i penguar ata të shprehin lirshëm idetë e tyre krijuese, ndonjëherë duke e quajtur atë një "rrip dëlirësie".

Dikush mund të pajtohet me këtë argumentim, por vetëm për gjuhët e shtypura në mënyrë statike që nuk mbështesin një numër karakteristikash. Vlen të përmendet se të gjitha konceptet që lidhen me konceptin e llojit dhe të prezantuara në leksionet e mëparshme janë të nevojshme - refuzimi i ndonjërit prej tyre është i mbushur me kufizime serioze, dhe futja e tyre, përkundrazi, i jep fleksibilitet veprimeve tona dhe jep na jepet mundësia për të shijuar plotësisht prakticitetin e një shtypjeje statike.

Tipizimi: komponentët e suksesit

Cilat janë mekanizmat për shtypjen realiste statike? Të gjitha u prezantuan në ligjëratat e mëparshme, prandaj na mbetet vetëm t'i rikujtojmë shkurtimisht. Numërimi i tyre i përbashkët tregon koherencën dhe fuqinë e bashkimit të tyre.

Sistemi ynë i tipit bazohet tërësisht në koncept klasës. Klasat janë madje lloje të tilla themelore si I PLOTË, dhe për këtë arsye, nuk kemi nevojë për rregulla të veçanta për përshkrimin e llojeve të paracaktuara. (Kjo është vendi ku shënimi ynë ndryshon nga gjuhët "hibride" si Object Pascal, Java dhe C++, ku sistemi i tipit të gjuhëve të vjetra kombinohet me teknologjinë e objekteve të bazuara në klasë.)

Llojet e zgjeruara na jep më shumë fleksibilitet duke lejuar lloje, vlerat e të cilave tregojnë objekte, si dhe lloje, vlerat e të cilave tregojnë referenca.

Fjala vendimtare në krijimin e një sistemi të tipit fleksibël i përket trashëgimisë dhe koncepti përkatës pajtueshmërinë. Kjo kapërcen kufizimin kryesor të gjuhëve klasike të shtypura, për shembull, Pascal dhe Ada, në të cilat operatori x:= y kërkon që lloji x dhe y ishte e njejta. Ky rregull është shumë i rreptë: ndalon përdorimin e subjekteve që mund të tregojnë objekte të llojeve të lidhura ( LLOGARIA E KURSIMEVE dhe CHECKING_LOGARIA). Në trashëgimi, ne kërkojmë vetëm përputhshmëri të tipit y me llojin x, për shembull, x ka lloj LLOGARI, y- LLOGARIA E KURSIMEVE, dhe klasa e dytë është pasardhëse e së parës.

Në praktikë, një gjuhë e shtypur në mënyrë statike ka nevojë për mbështetje trashëgimi e shumëfishtë. Akuzat themelore të njohura për shtypjen statike se nuk jep mundësinë e interpretimit të objekteve në mënyra të ndryshme. Po, objekti DOKUMENT(dokumenti) mund të transmetohet përmes rrjetit, dhe për këtë arsye ka nevojë për praninë e komponentëve të lidhur me llojin MESAZH(mesazh). Por kjo kritikë është e vërtetë vetëm për gjuhët që janë të kufizuara në trashëgimi njëjës.

Oriz. 17.2. Trashëgimia e shumëfishtë

Shkathtësi e nevojshme, për shembull, për të përshkruar struktura fleksibël, por të sigurta të të dhënave të kontejnerëve (për shembull LISTA e klasës[G]...). Pa këtë mekanizëm, shtypja statike do të kërkonte deklarimin e klasave të ndryshme për listat me lloje të ndryshme elementesh.

Në disa raste, kërkohet shkathtësi kufizojnë, i cili ju lejon të përdorni operacione që zbatohen vetëm për subjektet e një lloji gjenerik. Nëse klasa gjenerike LISTË_RENDIMTARE mbështet renditjen, kërkon entitete të llojit G, ku G- një parametër gjenerik, prania e një operacioni krahasimi. Kjo arrihet duke u lidhur me G klasa që përcakton kufizimin e përgjithshëm - TË KRAHASUAR:


klasa SORTABLE_LIST ...

Çdo parametër gjenerik aktual LISTË_RENDIMTARE duhet të jetë fëmijë i një klase TË KRAHASUAR, e cila ka komponentin e kërkuar.

Një mekanizëm tjetër thelbësor është përpjekje për detyrë- organizon aksesin në ato objekte, llojin e të cilave softueri nuk e kontrollon. Nese nje yështë një objekt i bazës së të dhënave ose një objekt i marrë përmes rrjetit, pastaj deklarata x ?= y caktoj x kuptimi y, nëse yështë i një lloji të përputhshëm, ose nëse nuk është, do të japë x kuptimi E pavlefshme.

Deklaratat, të lidhura, si pjesë e idesë Dizajn sipas Kontratës, me klasat dhe përbërësit e tyre në formën e parakushteve, kushteve të pasme dhe invarianteve të klasës, bëjnë të mundur përshkrimin e kufizimeve semantike që nuk mbulohen nga një specifikim tipi. Gjuhë të tilla si Pascal dhe Ada kanë lloje të diapazonit që mund të kufizojnë vlerën e një entiteti midis 10 dhe 20, për shembull, por ju nuk mund t'i përdorni ato për të detyruar vlerën i ishte negative, gjithmonë dyfishi j. Klasat e pandryshuara vijnë në shpëtim, të krijuar për të pasqyruar me saktësi kufizimet e vendosura, pavarësisht sa komplekse janë ato.

Reklamat e gozhduara janë të nevojshme për të shmangur dyfishimin e kodit në praktikë. duke shpallur y: si x, ju merrni garancinë se y do të ndryshojë pas çdo deklarate të llojit të përsëritur x në një pasardhës. Në mungesë të këtij mekanizmi, zhvilluesit do të rideklaronin vazhdimisht, duke u përpjekur t'i mbanin llojet e ndryshme të qëndrueshme.

Deklaratat ngjitëse janë një rast i veçantë i mekanizmit të fundit gjuhësor që na nevojitet - kovarianca, e cila do të diskutohet më në detaje më vonë.

Kur zhvilloni sisteme softuerësh, në fakt, nevojitet një pronë më shumë që është e natyrshme në vetë mjedisin e zhvillimit - rikompilim i shpejtë në rritje. Kur shkruani ose modifikoni një sistem, dëshironi të shihni efektin e ndryshimeve sa më shpejt të jetë e mundur. Me shtypjen statike, duhet t'i jepni kohë përpiluesit për të rishikuar llojet. Rutinat tradicionale të përpilimit kërkojnë ripërpilimin e të gjithë sistemit (dhe kuvendi i saj), dhe ky proces mund të jetë shumë i gjatë, veçanërisht me kalimin në sisteme në shkallë të gjerë. Ky fenomen është kthyer në një argument në favor të duke interpretuar sisteme, të tilla si mjediset e hershme Lisp ose Smalltalk, që drejtonin sistemin me pak ose aspak përpunim, pa kontroll të llojit. Tani ky argument është harruar. Një përpilues i mirë modern zbulon se si kodi ka ndryshuar që nga përpilimi i fundit dhe përpunon vetëm ndryshimet që gjen.

“A është bebi i tipizuar”?

Qellimi jone - i rreptë shtypja statike. Kjo është arsyeja pse ne duhet të shmangim çdo boshllëk në "luajtjen sipas rregullave", të paktën t'i identifikojmë ato me saktësi nëse ekzistojnë.

Zbrazëtira më e zakonshme në gjuhët e shtypura në mënyrë statike është ekzistenca e konvertimeve që ndryshojnë llojin e një entiteti. Në C dhe gjuhët e derivateve të saj, ato quhen "tip casting" ose casting. Regjistrimi (OTHER_TYPE) x tregon se vlera x perceptohet nga kompajleri se ka llojin OTHER_TYPE, subjekt i kufizimeve të caktuara për llojet e mundshme.

Mekanizmat si ky anashkalojnë kufizimet e kontrollit të tipit. Casting është e zakonshme në programimin C, duke përfshirë dialektin ANSI C. Edhe në C++, lloji i hedhjes, megjithëse më pak i zakonshëm, mbetet i zakonshëm dhe ndoshta i nevojshëm.

Respektimi i rregullave të shtypjes statike nuk është aq i lehtë, nëse në çdo kohë ato mund të anashkalohen duke hedhur.

Shtypja dhe lidhja

Edhe pse, si lexues i këtij libri, me siguri do të dalloni shtypjen statike nga statike detyruese Epo, ka njerëz që nuk mund ta bëjnë këtë. Kjo mund të jetë pjesërisht për shkak të ndikimit të gjuhës Smalltalk, e cila mbron një qasje dinamike ndaj të dy problemeve dhe mund të çojë në keqkuptimin se ato kanë të njëjtën zgjidhje. (Ne argumentojmë në këtë libër se është e dëshirueshme të kombinohen shtypja statike dhe lidhjet dinamike për të krijuar programe të fuqishme dhe fleksibël.)

Si shtypja ashtu edhe lidhja merren me semantikën e Konstruksionit Bazë x.f(arg) por përgjigjuni dy pyetjeve të ndryshme:

Shtypja dhe lidhja

[x]. Një pyetje rreth shtypjes: kur duhet të dimë me siguri se një operacion korrespondon me f E zbatueshme për objektin e bashkangjitur njësisë ekonomike x(me parametër arg)?

[x]. Pyetje lidhëse: Kur duhet të dimë se çfarë operacioni fillon një telefonatë e caktuar?

Shtypja i përgjigjet pyetjes së disponueshmërisë të paktën një operacionet, lidhja është përgjegjëse për zgjedhjen e nevojshme.

Në kuadër të qasjes së objektit:

[x]. Problemi me shtypjen lidhet me polimorfizëm: sepse xnë kohën e ekzekutimit mund të shënojë objekte të disa llojeve të ndryshme, duhet të jemi të sigurt se operacioni që përfaqëson f, në dispozicion në secilin prej këtyre rasteve;

[x]. problem detyrues i shkaktuar njoftime të përsëritura: meqenëse një klasë mund të ndryshojë komponentët e trashëguar, mund të ketë dy ose më shumë operacione që pretendojnë të përfaqësojnë f në këtë thirrje.

Të dy problemet mund të zgjidhen si në mënyrë dinamike ashtu edhe statike. Gjuhët ekzistuese ofrojnë të katër zgjidhjet.

[x]. Një numër i gjuhëve jo objektive, të tilla si Pascal dhe Ada, zbatojnë shtypjen statike dhe lidhjen statike. Çdo entitet përfaqëson objekte të vetëm një lloji të përcaktuar statikisht. Kjo siguron besueshmërinë e zgjidhjes, çmimi për të cilin është fleksibiliteti i saj.

[x]. Smalltalk dhe gjuhët e tjera OO përmbajnë lidhje dinamike dhe shtypje dinamike. Preferenca i jepet fleksibilitetit në kurriz të besueshmërisë së gjuhës.

[x]. Disa gjuhë jo objektive mbështesin shtypjen dinamike dhe lidhjen statike. Midis tyre janë gjuhët e asamblesë dhe një numër i gjuhëve të shkrimit.

[x]. Idetë e shtypjes statike dhe lidhjes dinamike janë mishëruar në shënimin e propozuar në këtë libër.

Vëmë re veçorinë e gjuhës C ++, e cila mbështet shtypjen statike, megjithëse jo e rreptë për shkak të pranisë së hedhjes së tipit, lidhjes statike (si parazgjedhje), lidhjes dinamike kur specifikohet në mënyrë eksplicite virtuale ( Virtual) reklama.

Arsyeja për zgjedhjen e shtypjes statike dhe lidhjes dinamike është e qartë. Pyetja e parë është: "Kur do të dimë për ekzistencën e komponentëve?" - sugjeron një përgjigje statike: " Sa më herët aq më mirë", që do të thotë: në kohën e përpilimit. Pyetja e dytë, "Cili komponent të përdoret?" sugjeron një përgjigje dinamike: " ai që ju nevojitet", - që korrespondon me llojin dinamik të objektit të përcaktuar në kohën e ekzekutimit. Kjo është e vetmja zgjidhje e pranueshme nëse lidhja statike dhe dinamike prodhojnë rezultate të ndryshme.

Shembulli i mëposhtëm i një hierarkie të trashëgimisë do të ndihmojë në sqarimin e këtyre koncepteve:

Oriz. 17.3. Llojet e avionëve

Merrni parasysh thirrjen:


my_aircraft.lower_landing_gear

Një pyetje rreth shtypjes: kur të siguroheni që një komponent do të jetë këtu pajisjet e ulëta të uljes("lirimi i mjeteve të uljes"), i zbatueshëm për një objekt (për KOPTER nuk do të jetë fare) Çështja e lidhjes: cilin nga disa versione të mundshme të zgjedhësh.

Lidhja statike do të thotë që ne shpërfillim llojin e objektit të bashkangjitur dhe mbështetemi në deklaratën e entitetit. Si rezultat, kur kemi të bëjmë me një Boeing 747-400, do të kërkojmë një version të projektuar për avionët konvencionalë të serisë 747, dhe jo për modifikimin e tyre 747-400. Lidhja dinamike zbaton funksionimin e kërkuar nga objekti dhe kjo është qasja e duhur.

Me shtypjen statike, përpiluesi nuk do të refuzojë një thirrje nëse mund të garantohet që gjatë ekzekutimit të programit te entiteti my_avion do t'i bashkëngjitet objekti i furnizuar me komponentin përkatës pajisjet e ulëta të uljes. Teknika bazë për marrjen e garancive është e thjeshtë: me një deklaratë të detyrueshme my_avion Klasa bazë e llojit të saj kërkohet të përfshijë një komponent të tillë. Kjo është arsyeja pse my_avion nuk mund të deklarohet si AVION, pasi kjo e fundit nuk ka pajisjet e ulëta të uljes në këtë nivel; helikopterët, të paktën në shembullin tonë, nuk dinë të lëshojnë mjetet e uljes. Nëse e deklarojmë një subjekt si AEROPLAN, - klasa që përmban komponentin e kërkuar - gjithçka do të jetë mirë.

Shtypja dinamike në stilin Smalltalk kërkon që ju të prisni për thirrjen dhe në momentin e ekzekutimit të saj, të kontrolloni praninë e komponentit të dëshiruar. Kjo sjellje është e mundur për prototipet dhe zhvillimet eksperimentale, por e papranueshme për sistemet industriale - në kohën e fluturimit është tepër vonë për të pyetur nëse keni një pajisje uljeje.

Kovarianca dhe Fshehja e Fëmijëve

Nëse bota do të ishte e thjeshtë, atëherë biseda rreth shtypjes mund të kishte përfunduar. Ne identifikuam qëllimet dhe përfitimet e shtypjes statike, eksploruam kufizimet që sistemet e tipit realist duhet të plotësojnë dhe verifikuam që metodat e propozuara të shtypjes plotësonin kriteret tona.

Por bota nuk është e thjeshtë. Kombinimi i shtypjes statike me disa kërkesa të inxhinierisë softuerike krijon probleme më komplekse nga sa duket. Problemet shkaktohen nga dy mekanizma: kovarianca- ndryshimi i llojeve të parametrave gjatë ripërcaktimit, fshehja e pasardhësve- aftësia e një klase pasardhëse për të kufizuar statusin e eksportit të komponentëve të trashëguar.

kovarianca

Çfarë ndodh me argumentet e një komponenti kur lloji i tij ripërcaktohet? Ky është një problem madhor dhe ne kemi parë tashmë një sërë shembujsh të tij: pajisje dhe printera, lista të lidhura të vetme dhe dyfish, e kështu me radhë (shih seksionet 16.6, 16.7).

Këtu është një shembull tjetër për të ndihmuar në sqarimin e natyrës së problemit. Dhe megjithëse është larg realitetit dhe metaforik, afërsia e tij me skemat programore është e dukshme. Përveç kësaj, kur e analizojmë, shpesh do t'u kthehemi problemeve nga praktika.

Imagjinoni një ekip skijimi universitar që përgatitet për një kampionat. Klasa VAJZE përfshin skiatorë që janë pjesë e ekipit të femrave, DJALI- skiatorë. Janë renditur një sërë pjesëmarrësish të të dyja skuadrave, duke treguar rezultate të mira në garat e mëparshme. Kjo është e rëndësishme për ta, sepse tani ata do të vrapojnë të parët, duke fituar një avantazh ndaj të tjerëve. (Ky rregull, i cili privilegjon tashmë të privilegjuarit, është ndoshta ajo që e bën sllallomin dhe skijimin në vend kaq tërheqës në sytë e shumë njerëzve, duke qenë një metaforë e mirë për vetë jetën.) Pra, kemi dy klasa të reja: RANKED_GIRL dhe RANKED_BOY.

Oriz. 17.4. Klasifikimi i skiatorëve

Janë rezervuar një sërë dhomash për akomodimin e sportistëve: vetëm për meshkuj, vetëm për vajza, vetëm për femra fituese. Për të shfaqur këtë, ne përdorim një hierarki paralele të klasës: DHOMA, GIRL_ROOM dhe RANKED_GIRL_ROOM.

Këtu është skica e klasës SKIER:


- Shok dhome.
... Komponentët e tjerë të mundshëm të hequr në këtë dhe klasat pasuese...

Ne jemi të interesuar për dy komponentë: atribut shok dhome dhe procedurën ndajnë, e cila e "vendos" këtë skiator në të njëjtën dhomë me skiatorin aktual:


Me rastin e deklarimit të një subjekti tjera ju mund të hiqni llojin SKIER në favor të tipit fiks si shoku i dhomës(ose si Rryma për shok dhome dhe tjera njëkohësisht). Por le të harrojmë për një çast fiksimin e tipit (do të kthehemi tek ata më vonë) dhe të shohim problemin e kovariancës në formën e tij origjinale.

Si të prezantohet mbivendosja e tipit? Rregullat kërkojnë vendbanim të veçantë për djemtë dhe vajzat, fituesit dhe pjesëmarrësit e tjerë. Për të zgjidhur këtë problem, gjatë ripërcaktimit, ne do të ndryshojmë llojin e komponentit shok dhome, siç tregohet më poshtë (në tekstin e mëtejmë, elementët e anashkaluar janë nënvizuar).


- Shok dhome.

Ripërcaktoni, përkatësisht, argumentin e procedurës ndajnë. Një version më i plotë i klasës tani duket kështu:


- Shok dhome.
-- Zgjidhni një tjetër si fqinj.

Në mënyrë të ngjashme, ju duhet të ndryshoni të gjitha të krijuara nga SKIER klasa (nuk përdorim fiksimin e tipit tani). Si rezultat, ne kemi një hierarki:

Oriz. 17.5. Hierarkia e anëtarëve dhe ripërkufizimet

Meqenëse trashëgimia është një specializim, rregullat e tipit kërkojnë që kur mbizotërojnë rezultatin e një komponenti, në këtë rast shok dhome, tipi i ri ishte një fëmijë i origjinalit. E njëjta gjë vlen edhe për ripërcaktimin e llojit të argumentit. tjera rutinat ndajnë. Kjo strategji, siç e dimë, quhet kovariancë, ku parashtesa "ko" tregon një ndryshim të përbashkët në llojet e parametrit dhe rezultatit. Strategjia e kundërt quhet kontravarianca.

Të gjithë shembujt tanë demonstrojnë bindshëm domosdoshmërinë praktike të kovariancës.

[x]. Elementi i listës i lidhur vetëm E LIDHSHME duhet të shoqërohet me një element tjetër të ngjashëm, dhe shembullin BI_LINKE MUNDSHME- me një të ngjashme. Bashkëvarianti do të duhet të anashkalohet dhe argumenti të futet vë_e drejtë.

[x].Çdo nënprogram në LINKED_LIST me argumentin e tipit E LIDHSHME kur lëvizni në TWO_WAY_LIST do të kërkojë një argument BI_LINKE MUNDSHME.

[x]. Procedura vendos_alternativ pranon PAJISJE-argumenti në klasë PAJISJE dhe PRINTER-argument - në klasë PRINTER.

Ripërcaktimi i bashkëvariantit është veçanërisht i popullarizuar sepse fshehja e informacionit çon në krijimin e procedurave të formularit


-- Cakto atributin në v.

për të punuar me atribut lloji SOME_TYPE. Procedura të tilla janë, natyrisht, bashkëvariante, pasi çdo klasë që ndryshon llojin e një atributi duhet të ripërcaktojë argumentin në përputhje me rrethanat. set_attrib. Megjithëse shembujt e paraqitur përshtaten në një skemë, kovarianca është shumë më e përhapur. Mendoni, për shembull, për një procedurë ose funksion që kryen lidhjen e listave të lidhura veçmas ( LINKED_LIST). Argumenti i tij duhet të ripërcaktohet si një listë e lidhur dyfish ( TWO_WAY_LIST). Operacioni universal i shtimit infix "+" pranon NUMERIK-argumenti në klasë NUMERIK, REAL- në klasë REAL dhe I PLOTË- në klasë I PLOTË. Në hierarkitë e shërbimit telefonik paralel, procedura filloni në klasë PHONE_SERVICE mund të kërkohet argument ADRESË, që përfaqëson adresën e pajtimtarit, (për faturim), ndërsa e njëjta procedurë në klasë CORPORATE_SERVICE kërkohet argumenti i llojit ADRESA_KORPORATE.

Oriz. 17.6. Shërbimet e Komunikimit

Çfarë mund të thuhet për zgjidhjen kundërthënëse? Në shembullin e skiatorit, do të nënkuptohej se nëse, duke kaluar në klasë RANKED_GIRL, lloji i rezultatit shok dhome ripërcaktuar si RANKED_GIRL, pastaj, për shkak të kundërvarësisë, lloji i argumentit ndajnë mund të ripërcaktohet për të shtypur VAJZE ose SKIER. I vetmi lloj që nuk lejohet sipas zgjidhjes kundërthënëse është RANKED_GIRL! Mjaft për të ngjallur dyshimet më të këqija te prindërit e vajzave.

Hierarkitë paralele

Për të mos lënë gur pa lëvizur, merrni parasysh një variant të shembullit SKIER me dy hierarki paralele. Kjo do të na lejojë të simulojmë një situatë që tashmë është hasur në praktikë: TWO_WAY_LIST > LINKED_LIST dhe BI_LINK MUNDSHME > LINK MUNDSHME; ose hierarki me sherbim telefonik PHONE_SERVICE.

Le të ketë një hierarki me një klasë DHOMA, pasardhës i të cilit është GIRL_ROOM(Klasa DJALI të hequra):

Oriz. 17.7. Skiatorë dhe dhoma

Klasat tona të skiatorëve në këtë hierarki paralele në vend të shok dhome dhe ndajnë do të ketë komponentë të ngjashëm akomodimi (akomodimi) dhe akomoduar (vend):


Përshkrimi: "Variant i ri me hierarki paralele"
akomodoj (r: DHOMA) është ... kërkoj ... bëj

Këtu nevojiten edhe mbivendosjet e bashkëvarianteve: në klasë VAJZË 1 si akomodimi, dhe argumenti i nënprogramit akomoduar duhet të zëvendësohet nga lloji GIRL_ROOM, në klasë DJALI 1- lloji DHOMA_DJALI etj. (Mos harroni, ne jemi ende duke punuar pa fiksim të tipit.) Ashtu si me versionin e mëparshëm të shembullit, kontravarianca është e padobishme këtu.

Drejtësia e polimorfizmit

A nuk ka shembuj të mjaftueshëm që konfirmojnë prakticitetin e kovariancës? Pse dikush do ta konsideronte kontravariancën, e cila bie ndesh me atë që nevojitet në praktikë (përveç sjelljes së disa të rinjve)? Për ta kuptuar këtë, merrni parasysh problemet që lindin kur kombinoni polimorfizmin dhe strategjinë e kovariancës. Ardhja me një skemë sabotimi nuk është e vështirë dhe mund ta keni krijuar tashmë vetë:


krijoj b; krijoni g;-- Krijo objekte BOY dhe GIRL.

Rezultati i thirrjes së fundit, mbase i pëlqyeshëm për të rinjtë, është pikërisht ai që ne po përpiqeshim të parandalonim me mbivendosjen e tipit. Thirrni ndajnëçon në faktin se objekti DJALI, i njohur si b dhe falë polimorfizmit mori një pseudonim s lloji SKIER, bëhet fqinj i objektit VAJZE i njohur me emrin g. Megjithatë, thirrja, edhe pse në kundërshtim me rregullat e bujtinës, është mjaft korrekte në tekstin e programit, pasi ndajnë-komponent i eksportuar në përbërje SKIER, a VAJZE, lloji i argumentit g, i përputhshëm me SKIER, lloji i parametrit formal ndajnë.

Skema e hierarkisë paralele është po aq e thjeshtë: zëvendëso SKIERSKIER1, sfidë ndajnë- në Thirrje s.akomoduar(gr), ku gr- lloj entiteti GIRL_ROOM. Rezultati është i njëjtë.

Me një zgjidhje kontravariane të këtyre problemeve, nuk do të kishte: specializim të objektivit të thirrjes (në shembullin tonë s) do të kërkonte një përgjithësim të argumentit. Si rezultat, kontravarianca çon në një model më të thjeshtë matematikor të mekanizmit: trashëgimi - ripërcaktim - polimorfizëm. Ky fakt përshkruhet në një sërë artikujsh teorikë që propozojnë këtë strategji. Argumenti nuk është shumë bindës, sepse, siç tregojnë shembujt tanë dhe botimet e tjera, kontravarianca nuk ka përdorim praktik.

Prandaj, pa u përpjekur për të tërhequr rrobat kundërthënëse në trupin shoqërues, duhet pranuar realiteti kovariant dhe të kërkohen mënyra për të eliminuar efektin e padëshiruar.

Fshehja sipas pasardhësve

Përpara se të kërkoni një zgjidhje për problemin e kovariancës, merrni parasysh një mekanizëm tjetër që mund të çojë në shkelje të tipit në kushtet e polimorfizmit. Fshehja e pasardhësve është aftësia e një klase për të mos eksportuar një komponent të marrë nga prindërit e saj.

Oriz. 17.8. Fshehja sipas pasardhësve

Një shembull tipik është komponenti shtoj_kulm(shto kulm) të eksportuar nga klasa POLIGONI, por e fshehur nga pasardhësi i saj DREJTKËNDËSH(duke pasur parasysh shkeljen e mundshme të invariantit - klasa dëshiron të mbetet një drejtkëndësh):


Shembull joprogramues: klasa "Struc" fsheh metodën "Fly", marrë nga prindi "Zog".

Le ta marrim këtë skemë ashtu siç është për një moment dhe të pyesim nëse një kombinim i trashëgimisë dhe fshehjes do të ishte legjitim. Roli modelues i fshehjes, si kovarianca, cenohet nga truket që janë të mundshme për shkak të polimorfizmit. Dhe këtu nuk është e vështirë të ndërtosh një shembull keqdashës që lejon, pavarësisht nga fshehja e komponentit, ta thërrasësh atë dhe të shtosh një kulm në drejtkëndësh:


krijues; -- Krijoni një objekt RECTANGLE.
p:=r; -- Detyrë polimorfike.

Që nga objekti r duke u fshehur nën esencë fq klasës POLIGONI, a shtoj_kulm komponent i eksportuar POLIGONI, pastaj thirrja e saj nga entiteti fq e saktë. Si rezultat i ekzekutimit, një kulm tjetër do të shfaqet në drejtkëndësh, që do të thotë se do të krijohet një objekt i pavlefshëm.

Korrektësia e sistemeve dhe klasave

Për të diskutuar problemet e kovariancës dhe fshehjes së pasardhësve, na duhen disa terma të rinj. Ne do të thërrasim class-correct (class-valid) një sistem që plotëson tre rregullat për përshkrimin e llojeve të dhëna në fillim të leksionit. Kujtojini ato: çdo ent ka llojin e vet; lloji i argumentit aktual duhet të jetë në përputhje me llojin e argumentit formal, situata është e ngjashme me caktimin; komponenti i thirrur duhet të deklarohet në klasën e tij dhe të eksportohet në klasën që përmban thirrjen.

Sistemi quhet sistemi-korrekt (sistemi i vlefshëm), nëse gjatë ekzekutimit të tij nuk ndodh shkelje e llojit.

Idealisht, të dy konceptet duhet të përputhen. Megjithatë, ne kemi parë tashmë se një sistem i saktë sipas klasës në kushtet e trashëgimisë, kovariancës dhe fshehjes nga një pasardhës mund të mos jetë i saktë sipas sistemit. Le ta quajmë këtë gabim gabimi i vlefshmërisë së sistemit.

Aspekti praktik

Thjeshtësia e problemit krijon një lloj paradoksi: një fillestar kërkues mund të ndërtojë një kundërshembull brenda pak minutash, në praktikën reale gabimet e korrektësisë së klasës së sistemeve ndodhin ditë pas dite, por shkeljet e korrektësisë së sistemit, madje edhe në masë të madhe, shumë- projektet e vitit, ndodhin jashtëzakonisht rrallë.

Sidoqoftë, kjo nuk na lejon t'i injorojmë ato, dhe për këtë arsye ne fillojmë të studiojmë tre mënyra të mundshme për të zgjidhur këtë problem.

Më tej, do të prekim aspekte shumë delikate dhe jo aq shpesh të dukshme të qasjes së objektit. Nëse po e lexoni këtë libër për herë të parë, mund të dëshironi të kapërceni pjesët e mbetura të këtij leksioni. Nëse jeni i ri në teknologjinë OO, atëherë do ta kuptoni më mirë këtë material pasi të studioni leksionet 1-11 të lëndës "Bazat e dizajnit të orientuar nga objekti", kushtuar metodologjisë së trashëgimisë, dhe në veçanti leksionin 6 të lëndës "Bazat i Projektimit të Orientuar në Objekt”, kushtuar trashëgimisë së metodologjisë.

Korrektësia e sistemeve: përafrimi i parë

Le të përqendrohemi së pari te problemi i kovariancës, më i rëndësishmi nga të dy. Ekziston një literaturë e gjerë kushtuar kësaj teme, duke ofruar një sërë zgjidhjesh të ndryshme.

Kontravarianca dhe pandryshueshmëria

Kontravarianca eliminon problemet teorike që lidhen me shkeljen e korrektësisë së sistemit. Megjithatë, kjo e humbet realizmin e sistemit të tipit, për këtë arsye, nuk ka nevojë të shqyrtohet më tej kjo qasje.

Origjinaliteti i gjuhës C++ është se ajo përdor strategjinë novacioni, duke ju penguar të ndryshoni llojin e argumenteve në nënprogramet e anashkaluara! Nëse C++ do të ishte një gjuhë e shtypur fort, sistemi i tipit të saj do të ishte i vështirë për t'u përdorur. Zgjidhja më e thjeshtë e problemit në këtë gjuhë, si dhe anashkalimi i kufizimeve të tjera të C ++ (të themi, mungesa e universalitetit të kufizuar), është përdorimi i hedhjes - lloji i hedhjes, i cili ju lejon të injoroni plotësisht mekanizmin ekzistues të shtypjes. Kjo zgjidhje nuk duket tërheqëse. Megjithatë, vini re se një numër i propozimeve të diskutuara më poshtë do të bazohen në pandryshueshmërinë, kuptimi i së cilës do të jepet nga futja e mekanizmave të rinj për të punuar me llojet në vend të ripërcaktimit bashkëvariant.

Përdorimi i parametrave gjenerikë

Universaliteti është në zemër të një ideje interesante të propozuar për herë të parë nga Franz Weber. Le të shpallim një klasë SKIER1, duke kufizuar gjenerikizimin e parametrave të përgjithshëm në klasë DHOMA:


tipar i klasës SKIER1
akomodoj (r: G) është ... kërkoj ... bëj akomodim:= r fund

Pastaj klasa VAJZË 1 do të jetë trashëgimtari SKIER1 etj. E njëjta teknikë, sado e çuditshme të duket në shikim të parë, mund të përdoret në mungesë të një hierarkie paralele: Klasa SKIER.

Kjo qasje zgjidh problemin e kovariancës. Çdo përdorim i klasës duhet të specifikojë parametrin aktual të përgjithshëm DHOMA ose GIRL_ROOM, kështu që kombinimi i gabuar thjesht bëhet i pamundur. Gjuha bëhet pa variante dhe sistemi plotëson plotësisht nevojat e kovariancës për shkak të parametrave gjenerikë.

Fatkeqësisht, kjo teknikë është e papranueshme si një zgjidhje e përgjithshme, pasi çon në një listë në rritje të parametrave gjenerikë, një për çdo lloj argumenti të mundshëm bashkëvariant. Më keq, shtimi i një nënprogrami kovariant me një argument, lloji i të cilit nuk është në listë, do të kërkonte shtimin e një parametri të përgjithshëm të klasës, dhe për këtë arsye ndryshoni ndërfaqen e klasës, ndryshoni të gjithë klientët e klasës, gjë që është e papranueshme.

Lloji variablat

Një numër autorësh, duke përfshirë Kim Bruce, David Shang dhe Tony Simons, kanë propozuar një zgjidhje të bazuar në variablat e tipit, vlerat e të cilave janë tipe. Ideja e tyre është e thjeshtë:

[x]. në vend të zëvendësimeve të bashkëvarianteve, lejo deklaratat e tipit që përdorin variabla të tipit;

[x]. të zgjerojë rregullat e përputhshmërisë së tipit për të menaxhuar variabla të tillë;

[x]. ofrojnë mundësinë për të caktuar variabla të tipit si vlera për llojet e gjuhëve.

Lexuesit mund të gjejnë një prezantim të detajuar të këtyre ideve në një numër artikujsh mbi këtë temë, si dhe në botime nga Cardelli, Castagna, Weber dhe të tjerë. Ne nuk do të merremi me këtë problem, dhe ja pse.

[x]. Një mekanizëm variabël i implementuar siç duhet bën pjesë në kategorinë e lejimit të përdorimit të një lloji pa e specifikuar plotësisht atë. E njëjta kategori përfshin shkathtësinë dhe ankorimin e reklamave. Ky mekanizëm mund të zëvendësojë mekanizmat e tjerë të kësaj kategorie. Në fillim, kjo mund të interpretohet në favor të variablave të tipit, por rezultati mund të jetë katastrofik, pasi nuk është e qartë nëse ky mekanizëm gjithëpërfshirës mund t'i trajtojë të gjitha detyrat me lehtësinë dhe thjeshtësinë që është e natyrshme në përgjithësimin dhe fiksimin e tipit.

[x]. Le të supozojmë se është zhvilluar një mekanizëm variabël i tipit që mund të kapërcejë problemet e kombinimit të kovariancës dhe polimorfizmit (duke injoruar ende problemin e fshehjes së pasardhësve). Atëherë zhvilluesi i klasës do të ketë nevojë intuitë e jashtëzakonshme në mënyrë që të vendoset paraprakisht se cili nga komponentët do të jetë i disponueshëm për ripërcaktimin e llojeve në klasat e derivuara dhe cilët jo. Më poshtë do të diskutojmë këtë problem, i cili zhvillohet në praktikën e krijimit të programeve dhe, mjerisht, vë në dyshim zbatueshmërinë e shumë skemave teorike.

Kjo na detyron t'u kthehemi mekanizmave të konsideruar tashmë: universaliteti i kufizuar dhe i pakufizuar, fiksimi i tipit dhe, natyrisht, trashëgimia.

Duke u mbështetur në fiksimin e tipit

Ne do të gjejmë një zgjidhje pothuajse të gatshme për problemin e kovariancës duke parë mekanizmin e njohur të deklaratave të fiksuara.

Kur përshkruani klasa SKIER dhe SKIER1 nuk mund të mos të vizitonte dëshira, duke përdorur njoftimet fikse, për të hequr qafe shumë ripërcaktime. Gërmimi është një mekanizëm tipik kovariant. Ja se si do të dukej shembulli ynë (të gjitha ndryshimet janë të nënvizuara):


share (të tjera: si Aktual) është ... kërkoj ... bëj
akomodoj (r: si akomodim) është ... kërkoj ... bëj

Tani fëmijët mund të largohen nga klasa SKIER e pandryshuar, dhe SKIER1 ata do të duhet vetëm të anashkalojnë atributin akomodimi. Subjektet e gozhduara: atribut shok dhome dhe argumentet e nënrutinës ndajnë dhe akomoduar- do të ndryshojë automatikisht. Kjo thjeshton shumë punën dhe konfirmon faktin se në mungesë të ankorimit (ose mekanizmave të tjerë të ngjashëm, si variablat e tipit), është e pamundur të shkruhet një produkt OO me shtypje realiste.

Por a keni arritur të eliminoni shkeljet e korrektësisë së sistemit? Jo! Ne, si më parë, mund të tejkalojmë kontrollin e tipit duke bërë detyra polimorfike që shkaktojnë shkelje të korrektësisë së sistemit.

Vërtetë, versionet origjinale të shembujve do të refuzohen. Le të:


krijoj b;krijoj g;-- Krijo objekte BOY dhe GIRL.
s:=b; -- Detyrë polimorfike.

Argumenti g të transmetuara ndajnë, tani është e pasaktë sepse kërkon një objekt të llojit si s, dhe klasën VAJZE nuk është i pajtueshëm me këtë lloj, pasi sipas rregullit të tipeve fikse asnjë lloj nuk është i pajtueshëm si s përveç vetes së tij.

Megjithatë, ne nuk gëzohemi për shumë kohë. Nga ana tjetër, ky rregull thotë se si s në përputhje me llojin s. Pra, duke përdorur polimorfizmin jo vetëm të objektit s, por edhe parametri g, ne mund të anashkalojmë përsëri sistemin e kontrollit të tipit:


s: SKIER; b:DJALI; g: pëlqime; aktuale_g:VAJZË;
krijoj b; krijo actual_g -- Krijo objekte BOY dhe GIRL.
s:= aktuale_g; g:= s -- Shtoji g te GIRL nëpërmjet s.
s:= b -- Detyrë polimorfike.

Si rezultat, thirrja e paligjshme kalon.

Ka një rrugëdalje. Nëse jemi serioz në lidhje me përdorimin e fiksimit të deklaratave si mekanizmin e vetëm të kovariancës, atëherë mund të shpëtojmë nga shkeljet e korrektësisë sistemike duke ndaluar plotësisht polimorfizmin e entitetit të fiksuar. Kjo do të kërkojë një ndryshim në gjuhë: futni një fjalë kyçe të re spirancë(ne kemi nevojë për këtë ndërtim hipotetik vetëm për ta përdorur atë në këtë diskutim):


Le të lejojmë deklarata si si s vetem kur s përshkruar si spirancë. Le të ndryshojmë rregullat e përputhshmërisë për të siguruar: s dhe elementet e tipit si s mund t'i bashkëngjiten vetëm njëra-tjetrës (në detyra ose kalim argumentesh).

Me këtë qasje, ne heqim nga gjuha mundësinë e ripërcaktimit të llojit të ndonjë argumenti nënrutinë. Përveç kësaj, ne mund të ndalojmë ripërcaktimin e llojit të rezultatit, por kjo nuk është e nevojshme. Mundësia për të anashkaluar llojin e atributit, natyrisht, ruhet. Të gjitha ripërkufizimet e llojeve të argumenteve tani do të bëhen në mënyrë implicite përmes mekanizmit të fiksimit të shkaktuar nga kovarianca. Ku, me qasjen e mëparshme, klasa D anashkaloi komponentin e trashëguar si:


ndërsa klasa C- prind D dukej


ku Y korrespondonte X, tani duke ripërcaktuar komponentin r do të duket kështu:


Mbetet në klasë D lloji i anashkalimit spiranca jote.

Kjo zgjidhje për problemin e kovariancës - polimorfizmit do të quhet qasje Ankorimi. Do të ishte më e saktë të thoshim: "Kovarianca vetëm përmes fiksimit". Karakteristikat e qasjes janë tërheqëse:

[x]. Mbyllja bazohet në idenë e ndarjes së rreptë bashkëvariant dhe elementë potencialisht polimorfikë (ose, shkurt, polimorfikë). Të gjitha subjektet e deklaruara si spirancë ose si ca_spirancë bashkëvariant; të tjerat janë polimorfike. Në secilën nga dy kategoritë, çdo bashkëngjitje lejohet, por nuk ka asnjë ent ose shprehje që shkel kufirin. Ju nuk mund, për shembull, t'i caktoni një burim polimorfik një objektivi kovariant.

[x]. Kjo zgjidhje e thjeshtë dhe elegante është e lehtë për t'u shpjeguar edhe për fillestarët.

[x]. Ai eliminon plotësisht mundësinë e shkeljes së korrektësisë së sistemit në sistemet e ndërtuara në mënyrë bashkëvariante.

[x]. Ai ruan kuadrin konceptual të përcaktuar më sipër, duke përfshirë konceptet e universalitetit të kufizuar dhe të pakufizuar. (Si rezultat, kjo zgjidhje, për mendimin tim, preferohet nga variablat e tipit që zëvendësojnë mekanizmat e kovariancës dhe universalitetit, të krijuar për të zgjidhur probleme të ndryshme praktike.)

[x]. Kërkon një ndryshim të vogël në gjuhë - duke shtuar një fjalë kyçe të vetme të pasqyruar në rregullin e përputhjes - dhe nuk përfshin ndonjë vështirësi të dukshme në zbatim.

[x].Është realiste (të paktën në teori): çdo sistem i mëparshëm i mundshëm mund të rishkruhet duke zëvendësuar mbivendosjet bashkëvariante me rideklarime të ankoruara. Vërtetë, disa bashkëngjitje do të bëhen të pavlefshme si rezultat, por ato korrespondojnë me raste që mund të çojnë në shkelje të tipit, dhe për këtë arsye ato duhet të zëvendësohen me përpjekje caktimi dhe të merren me situatën në kohën e ekzekutimit.

Duket se diskutimi mund të përfundojë këtu. Pra, pse qasja e Ankorimit nuk është plotësisht sipas dëshirës sonë? Së pari, ne nuk e kemi prekur ende çështjen e fshehjes së fëmijëve. Për më tepër, arsyeja kryesore për vazhdimin e diskutimit është problemi i shprehur tashmë në përmendjen e shkurtër të variablave të tipit. Ndarja e sferave të ndikimit në një pjesë polimorfike dhe bashkëvariante është disi e ngjashme me rezultatin e Konferencës së Jaltës. Supozon se projektuesi i klasës ka një intuitë të jashtëzakonshme, se ai është në gjendje, për çdo entitet që prezanton, veçanërisht për çdo argument, të zgjedhë një nga dy mundësitë njëherë e përgjithmonë:

[x]. Një entitet është potencialisht polimorfik: tani ose më vonë (duke kaluar parametra ose me caktim) ai mund t'i bashkëngjitet një objekti, lloji i të cilit ndryshon nga ai i deklaruar. Lloji origjinal i entitetit nuk mund të ndryshohet nga asnjë pasardhës i klasës.

[x]. Një njësi ekonomike është subjekt i mbizotërimit të llojit, që do të thotë se ose është i ankoruar ose është në vetvete një strumbullar.

Por si mund ta parashikojë një zhvillues gjithë këtë? E gjithë atraktiviteti i metodës OO, i shprehur në shumë aspekte në parimin e hapur-mbyllur, është pikërisht për shkak të mundësisë së ndryshimeve që ne kemi të drejtë të bëjmë në punën e bërë më parë, si dhe nga fakti që zhvilluesi i zgjidhjeve universale jo duhet të ketë mençuri të pafund, të kuptojë se si produkti i tij mund të përshtatet me nevojat e tyre nga pasardhësit.

Me këtë qasje, ripërcaktimi i llojeve dhe fshehja nga pasardhësit është një lloj "valvule sigurie" që bën të mundur ripërdorimin e një klase ekzistuese që është pothuajse e përshtatshme për qëllimet tona:

[x]. Duke përdorur ripërcaktimin e tipit, ne mund të ndryshojmë deklaratat në klasën e prejardhur pa ndikuar në origjinalin. Në këtë rast, një zgjidhje thjesht bashkëvariante do të kërkojë redaktimin e origjinalit sipas transformimeve të përshkruara.

[x]. Fshehja nga një pasardhës mbron nga shumë dështime kur krijon një klasë. Është e mundur të kritikohet një projekt në të cilin DREJTKËNDËSH, duke përdorur faktin seështë pasardhës POLIGONI, përpiqet të shtojë një kulm. Në vend të kësaj, mund të propozohet një strukturë trashëgimie në të cilën figurat me një numër fiks kulmesh janë të ndara nga të gjitha të tjerat dhe problemi nuk do të lindte. Megjithatë, gjatë projektimit të strukturave të trashëgimisë, preferohet gjithmonë të ketë ato që nuk kanë përjashtimet taksonomike. Por a mund të eliminohen plotësisht? Kur diskutojmë kufizimet e eksportit në një nga leksionet e mëposhtme, do të shohim se kjo nuk është e mundur për dy arsye. E para është prania e kritereve konkurruese të klasifikimit. Së dyti, gjasat që zhvilluesi të mos gjejë zgjidhjen ideale, edhe nëse ajo ekziston.

Analiza globale

Ky seksion i kushtohet përshkrimit të qasjes së ndërmjetme. Zgjidhjet kryesore praktike janë paraqitur në leksionin 17 .

Gjatë eksplorimit të opsionit të fiksimit, vumë re se ideja kryesore e tij ishte të ndante grupet e entiteteve kovariante dhe polimorfike. Pra, nëse marrim dy udhëzime të formularit


secila prej tyre është një shembull i zbatimit të saktë të mekanizmave të rëndësishëm OO: i pari - polimorfizmi, i dyti - ripërcaktimi i llojit. Problemet fillojnë kur kombinohen për të njëjtin ent s. Në mënyrë të ngjashme:


problemet nisin me bashkimin e dy operatorëve të pavarur dhe krejtësisht të pafajshëm.

Thirrjet e gabuara çojnë në shkelje të tipit. Në shembullin e parë, caktimi polimorfik bashkon një objekt DJALI tek thelbi s, çfarë po bën ai g argument i pavlefshëm ndajnë, sepse lidhet me një objekt VAJZE. Në shembullin e dytë për njësinë ekonomike r objekti është i bashkangjitur DREJTKËNDËSH, e cila përjashton shtoj_kulm nga komponentët e eksportuar.

Këtu është ideja e një zgjidhjeje të re: paraprakisht - në mënyrë statike, kur kontrolloni llojet nga një përpilues ose mjete të tjera - ne përcaktojmë tipografiçdo entitet, duke përfshirë llojet e objekteve me të cilat entiteti mund të shoqërohet në kohën e ekzekutimit. Pastaj, përsëri në mënyrë statike, ne sigurohemi që çdo thirrje është e saktë për çdo element të llojit dhe grupeve të argumenteve të objektivit.

Në shembujt tanë, operatori s:=b tregon se klasa DJALI i përket grupit të llojeve për s(sepse si rezultat i ekzekutimit të deklaratës krijimi krijoni b i përket grupit të llojeve për b). VAJZE, për shkak të pranisë së udhëzimeve krijoj g, i përket grupit të llojeve për g. Por pastaj sfida ndajnë do të jetë i pavlefshëm për objektivin s lloji DJALI dhe argumenti g lloji VAJZE. Në mënyrë të ngjashme DREJTKËNDËSHështë në llojin e caktuar për fq, e cila është për shkak të caktimit polimorfik, megjithatë, thirrja shtoj_kulm për fq lloji DREJTKËNDËSH do të jetë i pavlefshëm.

Këto vëzhgime na bëjnë të mendojmë për krijimin globale qasje e bazuar në rregullin e ri të shtypjes:

Rregulli i korrektësisë së sistemit

Thirrni x.f(arg)është sistem-korrekt nëse dhe vetëm nëse është i saktë për klasën x, dhe arg, të cilat kanë çdo lloj nga grupet e tyre përkatëse të tipit.

Në këtë përkufizim, një thirrje konsiderohet e saktë në klasë nëse nuk shkel rregullin e thirrjes së komponentëve, i cili thotë: nëse C ekziston një klasë bazë si x, komponent f duhet të eksportohet C, dhe llojin arg duhet të jetë në përputhje me llojin e parametrit formal f. (Kujtojmë se për thjeshtësi supozojmë se çdo nënprogram ka vetëm një parametër, por nuk është e vështirë të zgjerohet rregulli në një numër arbitrar argumentesh.)

Korrektësia e sistemit të një thirrjeje reduktohet në korrektësinë e klasës, me përjashtim që kontrollohet jo për elementë individualë, por për çdo çift nga grupet e grupeve. Këtu janë rregullat themelore për krijimin e një grupi llojesh për çdo entitet:

1 Për çdo entitet, grupi fillestar i llojeve është bosh.

2 Duke përmbushur një udhëzim tjetër të formularit krijoj (SOME_TYPE) a, shtoni SOME_TYPE te grupi i llojeve për a. (Për thjeshtësi, ne do të supozojmë se çdo udhëzim krijoni një do të zëvendësohet nga udhëzimi krijoj (ATYPE) a, ku ATIPI- lloji i entitetit a.)

3 Përballja me një detyrë tjetër të formularit a:=b, shtoni në grupin e llojeve për a b.

4 Nese nje aështë një parametër formal i nënprogramit, atëherë, pasi të keni përmbushur thirrjen tjetër me parametrin aktual b, shtoni në grupin e llojeve për a të gjithë elementët e grupit të llojeve për b.

5 Do të përsërisim hapat (3) dhe (4) derisa grupet e llojeve të ndalojnë së ndryshuari.

Ky formulim nuk merr parasysh mekanizmin e universalitetit, megjithatë, është e mundur të zgjerohet rregulli sipas nevojës pa ndonjë problem të veçantë. Hapi (5) është i nevojshëm për shkak të mundësisë së zinxhirëve të caktimeve dhe transferimeve (nga b te a, nga c te b etj.). Është e lehtë të kuptohet se pas një numri të caktuar hapash ky proces do të ndalet.

Siç mund ta keni vënë re, rregulli nuk merr parasysh sekuencat e udhëzimeve. Kur


krijoj (TYPE1) t; s:=t; krijojnë (TYPE2) t

te grupi i llojeve për s futni si LLOJI 1, dhe LLOJI 2, edhe pse s, duke pasur parasysh sekuencën e udhëzimeve, është në gjendje të marrë vetëm vlera të llojit të parë. Marrja në konsideratë e vendndodhjes së udhëzimeve do të kërkojë që përpiluesi të analizojë thellësisht rrjedhën e udhëzimeve, gjë që do të çojë në një rritje të tepruar të nivelit të kompleksitetit të algoritmit. Në vend të kësaj, zbatohen rregulla më pesimiste: sekuenca e veprimeve:


do të deklarohen si sistem i pasaktë, pavarësisht se sekuenca e ekzekutimit të tyre nuk çon në shkelje të llojit.

Analiza globale e sistemit u prezantua (në mënyrë më të detajuar) në kapitullin e 22-të të monografisë. Në të njëjtën kohë, u zgjidh si problemi i kovariancës ashtu edhe problemi i kufizimeve të eksportit gjatë trashëgimisë. Sidoqoftë, kjo qasje ka një të metë praktike të pafat, domethënë: supozohet të kontrollohet sistemi në tërësi në vend se çdo klasë veç e veç. Rregulli (4) rezulton të jetë fatal, i cili, kur thërret një nënprogram të bibliotekës, do të marrë parasysh të gjitha thirrjet e tij të mundshme në klasat e tjera.

Edhe pse më vonë u propozuan algoritme për të punuar me klasa individuale në , vlera e tyre praktike nuk mund të përcaktohet. Kjo do të thoshte se në një mjedis programimi që mbështet kompilimin në rritje, do të ishte e nevojshme të organizohej një kontroll i të gjithë sistemit. Është e dëshirueshme të futet kontrolli si një element i përpunimit (të shpejtë) lokal të ndryshimeve të bëra nga përdoruesi në disa klasa. Megjithëse dihen shembuj të qasjes globale, për shembull, programuesit C përdorin mjetin garzë për të gjetur mospërputhje në sistem që nuk zbulohen nga përpiluesi - e gjithë kjo nuk duket shumë tërheqëse.

Si rezultat, me sa di unë, kontrolli i korrektësisë së sistemit nuk u zbatua nga askush. (Një arsye tjetër për këtë rezultat mund të ketë qenë kompleksiteti i vetë rregullave të vlefshmërisë.)

Korrektësia e klasës përfshin vërtetimin e kufizuar në klasë dhe për këtë arsye është e mundur me përpilim në rritje. Korrektësia e sistemit nënkupton një kontroll global të të gjithë sistemit, i cili është në konflikt me kompilimin në rritje.

Megjithatë, pavarësisht nga emri i tij, është në të vërtetë e mundur të kontrollohet korrektësia e sistemit duke përdorur vetëm kontrollin në rritje të klasës (gjatë rrjedhës së një përpiluesi normal). Ky do të jetë kontributi përfundimtar për zgjidhjen e problemit.

Kujdes nga thirrjet polimorfike!

Rregulli i korrektësisë së sistemit është pesimist: për hir të thjeshtësisë, ai gjithashtu refuzon kombinime plotësisht të sigurta të udhëzimeve. Paradoksale sado që mund të duket, por ne do të ndërtojmë versionin e fundit të zgjidhjes në bazë të rregull edhe më pesimist. Natyrisht, kjo ngre pyetjen se sa real do të jetë rezultati ynë.

Kthehu në Jaltë

Thelbi i zgjidhjes Catcall (Catcall), - kuptimin e këtij koncepti do ta shpjegojmë më vonë - në një rikthim në frymën e marrëveshjeve të Jaltës, duke e ndarë botën në polimorfike dhe bashkëvariante (dhe një shoqërues i kovariancës - fshehja e pasardhësve), por pa nevojën për të zotëruar mençuri të pafund.

Si më parë, ne e ngushtojmë çështjen e kovariancës në dy operacione. Në shembullin tonë kryesor, kjo është një detyrë polimorfike: s:=b, dhe thirrja e nënprogramit kovariant: s.share(g). Duke analizuar se kush është fajtori i vërtetë i shkeljeve, e përjashtojmë argumentin g të të dyshuarve. Çdo argument i llojit SKIER ose që rrjedh prej saj, nuk na shkon për shkak të polimorfizmit s dhe kovarianca ndajnë. Prandaj, nëse e përshkruani në mënyrë statike thelbin tjera si SKIER dhe i bashkëngjitni në mënyrë dinamike objektit SKIER, pastaj thirrja s.share (tjetër) do të japë në mënyrë statike përshtypjen e të qenit ideal, por do të rezultojë në shkelje të tipit nëse caktohet në mënyrë polimorfike s kuptimi b.

Problemi themelor është se ne po përpiqemi të përdorim s në dy mënyra të papajtueshme: si një entitet polimorfik dhe si objektiv i një thirrjeje nënprograme bashkëvariante. (Në shembullin tonë tjetër, problemi është përdorimi fq si një ent polimorfik dhe si objektiv i thirrjes së një nënprogrami të një fëmije që fsheh komponentin shtoj_kulm.)

Zgjidhja e Catcall, si Pinning, është radikale: ndalon përdorimin e një entiteti si polimorfik dhe kovariant në të njëjtën kohë. Ashtu si analizimi global, ai përcakton në mënyrë statike se cilat entitete mund të jenë polimorfike, por nuk përpiqet të jetë shumë i zgjuar duke kërkuar grupe të llojeve të mundshme për entitetet. Në vend të kësaj, çdo entitet polimorfik perceptohet si mjaft i dyshimtë për t'u ndaluar të bashkohet me një rreth njerëzish të respektuar, duke përfshirë kovariancën dhe fshehjen e pasardhësve.

Një rregull dhe disa përkufizime

Rregulli i llojit për zgjidhjen Catcall ka një formulim të thjeshtë:

Lloji Rregulla për Catcall

Thirrjet polimorfike janë të pasakta.

Ai bazohet në përkufizime po aq të thjeshta. Para së gjithash, një entitet polimorfik:

Përkufizimi: entitet polimorfik

Thelbi x Lloji i referencës (jo i zgjeruar) është polimorfik nëse ka një nga vetitë e mëposhtme:

1 Ndodh në detyrë x:= y, ku thelbi y ka një lloj tjetër ose është polimorfik nga rekursioni.

2 Gjendet në udhëzimet e krijimit krijo (OTHER_TYPE) x, ku OTHER_TYPE nuk është lloji i specifikuar në deklaratë x.

3 Është një argument formal për një nënprogram.

4 Është një funksion i jashtëm.

Qëllimi i këtij përkufizimi është t'i japë statusin polimorfik ("potencialisht polimorfik") çdo entiteti që mund t'i bashkëngjitet objekteve të llojeve të ndryshme gjatë ekzekutimit të programit. Ky përkufizim vlen vetëm për llojet e referencës, pasi entitetet e zgjeruara nuk mund të jenë nga natyra polimorfike.

Në shembujt tanë, skiatori s dhe shumëkëndëshi fq janë polimorfe sipas rregullit (1). Të parës i caktohet një objekt DJALI b, e dyta - objekti DREJTËKËNDËSH r.

Nëse keni parë përkufizimin e një grupi llojesh, do të vini re se sa më pesimist është përkufizimi i një entiteti polimorfik dhe sa më i lehtë është për t'u testuar. Pa u përpjekur të gjejmë të gjitha llojet e mundshme dinamike të një entiteti, mjaftohemi me pyetjen e përgjithshme: a mund të jetë një ent i caktuar polimorfik apo jo? Më befasuesja është rregulli (3), sipas të cilit polimorfike numëron çdo parametër formal(përveç nëse lloji i tij është i zgjeruar, siç është rasti me numrat e plotë, etj.). Ne as nuk merremi me analizat e thirrjeve. Nëse nënprogrami ka një argument, atëherë ai është në dispozicion të plotë të klientit, që do të thotë se nuk mund të mbështeteni në llojin e specifikuar në deklaratë. Ky rregull lidhet ngushtë me ripërdorimin - qëllimi i teknologjisë së objektit - ku çdo klasë mund të përfshihet në një bibliotekë dhe të thirret shumë herë nga klientë të ndryshëm.

Një veti karakteristike e këtij rregulli është se nuk kërkon ndonjë kontroll global. Për të identifikuar polimorfizmin e një entiteti, mjafton të shikoni tekstin e vetë klasës. Nëse për të gjitha kërkesat (atributet ose funksionet) ruhen informacione për statusin e tyre polimorfik, atëherë as tekstet e paraardhësve nuk duhet të studiohen. Ndryshe nga gjetja e grupeve të llojeve, ju mund të zbuloni entitete polimorfike duke kontrolluar klasë për klasë gjatë përpilimit në rritje.

Thirrjet, si entitetet, mund të jenë polimorfike:

Përkufizimi: thirrje polimorfike

Një thirrje është polimorfike nëse objektivi i saj është polimorfik.

Të dy thirrjet në shembujt tanë janë polimorfikë: s.share(g) për shkak të polimorfizmit s, p.add_vertex(...) për shkak të polimorfizmit fq. Sipas përkufizimit, vetëm thirrjet e kualifikuara mund të jenë polimorfike. (Duke bërë një telefonatë të pakualifikuar f (...) lloj i kualifikuar Aktuale.f(...), ne nuk e ndryshojmë thelbin e çështjes, pasi Aktuale, të cilit nuk mund t'i caktohet asgjë, nuk është një objekt polimorfik.)

Më pas, ne kemi nevojë për konceptin e Catcall, bazuar në konceptin e CAT. (CAT është një shkurtim për Ndryshimin e Disponueshmërisë ose Llojit). Një nënprogram është një nënprogram CAT nëse një ripërcaktim i saj nga një fëmijë rezulton në një nga dy llojet e ndryshimeve që kemi parë që janë potencialisht të rrezikshme: ndryshimi i llojit të argumentit (në mënyrë bashkëvariante) ose fshehja e një komponenti të eksportuar më parë.

Përkufizimi: rutinat CAT

Një rutinë quhet rutinë CAT nëse një ripërcaktim i saj ndryshon statusin e eksportit ose llojin e ndonjë prej argumenteve të saj.

Kjo veçori përsëri lejon kontrollin në rritje: çdo ripërcaktim i llojit të argumentit ose statusit të eksportit e bën procedurën ose funksionin një nënprogram CAT. Këtu hyn nocioni i Catcall: thirrja e një nënprogrami CAT që mund të jetë e gabuar.

Përkufizimi: Catcall

Një thirrje quhet Catcall nëse një ripërcaktim i rutinës do ta bënte atë të dështonte për shkak të një ndryshimi në statusin e eksportit ose llojin e argumentit.

Klasifikimi që krijuam na lejon të dallojmë grupe të veçanta thirrjesh: polimorfike dhe catcalls. Thirrjet polimorfike i japin fuqi shprehëse qasjes së objektit, catcalls ju lejojnë të ripërcaktoni llojet dhe të kufizoni eksportet. Duke përdorur terminologjinë e prezantuar më herët në këtë kapitull, mund të themi se thirrjet polimorfike shtrihen dobia, macet - përdorshmërisë.

Sfidat ndajnë dhe shtoj_kulm, të konsideruara në shembujt tanë, janë thirrjet e maceve. I pari kryen një ripërcaktim kovariant të argumentit të tij. E dyta eksportohet nga klasa DREJTKËNDËSH, por të fshehura nga klasa POLIGONI. Të dyja thirrjet janë gjithashtu polimorfike, kështu që ato janë shembuj të shkëlqyeshëm të thirrjeve polimorfike. Ato janë të gabuara sipas rregullit të tipit Catcall.

Gradë

Përpara se të përmbledhim gjithçka që kemi mësuar rreth kovariancës dhe fshehjes së fëmijëve, le të përmbledhim se shkeljet e korrektësisë së sistemeve janë vërtet të rralla. Vetitë më të rëndësishme të shtypjes statike OO janë përmbledhur në fillim të leksionit. Ky grup mbresëlënës i mekanizmave të shtypjes, së bashku me vërtetimin e bazuar në klasë, hap rrugën për një metodë të sigurt dhe fleksibël të ndërtimit të softuerit.

Ne kemi parë tre zgjidhje për problemin e kovariancës, dy prej të cilave kanë prekur edhe kufizimet e eksportit. Cili është i saktë?

Nuk ka përgjigje përfundimtare për këtë pyetje. Pasojat e ndërveprimit tinëzar midis shtypjes OO dhe polimorfizmit nuk kuptohen aq mirë sa çështjet e diskutuara në leksionet e mëparshme. Vitet e fundit janë shfaqur botime të shumta mbi këtë temë, referenca për të cilat jepen në bibliografinë në fund të ligjëratës. Për më tepër, shpresoj që në këtë leksion të kem mundur të paraqes elementet e zgjidhjes përfundimtare, ose të paktën t'i afrohem asaj.

Një analizë globale duket jopraktike për shkak të një kontrolli të plotë të të gjithë sistemit. Megjithatë, na ndihmoi ta kuptonim më mirë problemin.

Zgjidhja e pinning është jashtëzakonisht tërheqëse. Është e thjeshtë, intuitive, e lehtë për t'u zbatuar. Aq më tepër duhet të na vjen keq për pamundësinë e mbështetjes në të një sërë kërkesash kryesore të metodës OO, të pasqyruara në parimin e hapur-mbyllur. Nëse do të kishim vërtet një intuitë të shkëlqyer, atëherë fiksimi do të ishte një zgjidhje e shkëlqyeshme, por cili zhvillues do të guxonte ta pohonte këtë, ose, për më tepër, të pranonte që autorët e klasave të bibliotekës të trashëguara në projektin e tij kishin një intuitë të tillë?

Nëse detyrohemi të braktisim fiksimin, atëherë zgjidhja Catcall duket se është më e përshtatshme, e cila shpjegohet mjaft lehtë dhe zbatohet në praktikë. Pesimizmi i tij nuk duhet të përjashtojë kombinimet e dobishme të operatorëve. Në rastin kur një catcall polimorfik gjenerohet nga një deklaratë "legjitime", është gjithmonë e sigurt ta pranosh atë duke futur një përpjekje për caktimin. Kështu, një numër kontrollesh mund të transferohen në kohën e ekzekutimit të programit. Megjithatë, numri i rasteve të tilla duhet të jetë jashtëzakonisht i vogël.

Si sqarim, duhet të theksoj se në momentin e këtij shkrimi, zgjidhja e Catcall nuk është zbatuar. Derisa përpiluesi të përshtatet me kontrollin e rregullave të tipit Catcall dhe të zbatohet me sukses në sistemet e përfaqësimit të madh dhe të vogël, është shumë herët të thuhet se fjala e fundit është thënë për problemin e harmonizimit të shtypjes statike me polimorfizmin, të kombinuar me kovariancën dhe fshehjen e pasardhësve.

Pajtueshmëri e plotë

Duke përfunduar diskutimin e kovariancës, është e dobishme të kuptohet se si një metodë e përgjithshme mund të zbatohet për një problem mjaft të përgjithshëm. Metoda u shfaq si rezultat i teorisë Catcall, por mund të përdoret brenda kornizës së versionit bazë të gjuhës pa futur rregulla të reja.

Le të ketë dy lista të përputhura, ku e para specifikon skiatorët dhe e dyta specifikon shokun e dhomës për skiatorin nga lista e parë. Ne duam të kryejmë procedurën e duhur të vendosjes ndajnë, vetëm nëse lejohet nga rregullat e përshkrimit të tipit që lejojnë vajzat me vajza, çmimin e vajzave me vajzat e çmimeve, etj. Problemet e këtij lloji janë të zakonshme.

Mund të ketë një zgjidhje të thjeshtë bazuar në diskutimin e mëparshëm dhe përpjekjen për detyrë. Merrni parasysh funksionin universal të pajisura(të miratojë):


i pajisur (tjetër: E PËRGJITHSHME): si të tjerat është
-- Objekti aktual (Aktual), nëse lloji i tij përputhet me llojin e objektit,
-- ngjitur me një tjetër, përndryshe i pavlefshëm.
nëse tjetër /= Void dhe pastaj përputhet me (tjetër) atëherë

Funksioni të pajisura kthen objektin aktual, por i njohur si një entitet i tipit të bashkangjitur argumentit. Nëse lloji i objektit aktual nuk përputhet me llojin e objektit të bashkangjitur argumentit, atëherë kthehet E pavlefshme. Vini re rolin e përpjekjes për detyrë. Funksioni përdor komponentin përputhet me nga klasa E PËRGJITHSHME, i cili përcakton përputhshmërinë e tipit të një çifti objektesh.

Zëvendësimi përputhet me në një komponent tjetër E PËRGJITHSHME Me emër lloji i njëjtë na jep një funksion perfekte (pajtueshmërinë e plotë) i cili kthehet E pavlefshme nëse llojet e të dy objekteve nuk janë identike.

Funksioni të pajisura- na jep një zgjidhje të thjeshtë për problemin e përputhjes së skiatorëve pa shkelur rregullat për përshkrimin e llojeve. Pra, në kodin e klasës SKIER ne mund të prezantojmë një procedurë të re dhe ta përdorim atë në vend të ndajnë, (kjo e fundit mund të bëhet një procedurë e fshehtë).


-- Zgjidhni, nëse është e aplikueshme, të tjera si numra fqinjë.
-- gjinia e konstatuar - gjinia e caktuar
gjinia_konstatuar_tjetër: si Aktuale
gjinia_konstatuar_tjetër:= tjetër .përshtatur(aktuale)
nëse gjinia_konstatohet_tjetër /= E pavlefshme atëherë
share (gjinia_e_konstatuar_tjetër)
"Përfundim: Kolokimi me të tjerët nuk është i mundur"

Për tjera lloj arbitrar SKIER(jo vetem si Rryma) përcaktoni versionin gjinia_e konstatuar_tjetër, e cila ka një lloj të caktuar për të Aktuale. Funksioni do të na ndihmojë të garantojmë identitetin e llojeve perfekte.

Nëse ka dy lista paralele të skiatorëve që përfaqësojnë akomodimin e planifikuar:


banuesi1, banuesi 2: LIST

është e mundur të organizohet një lak duke ekzekutuar një thirrje në çdo hap:


occupant1.item.safe_share(occupant2.item)

përputhen elementet e listës nëse dhe vetëm nëse llojet e tyre janë plotësisht të pajtueshme.

Konceptet kryesore

[x]. Shkrimi statik është çelësi i besueshmërisë, lexueshmërisë dhe efikasitetit.

[x]. Për të qenë realist, shtypja statike kërkon një kombinim mekanizmash: pohime, trashëgimi e shumëfishtë, tentativë caktimi, gjenerikë e kufizuar dhe e pakufishme, deklarata të ankoruara. Sistemi i tipit nuk duhet të lejojë kurthe (lloji hedhje).

[x]. Rregullat e përgjithshme për rideklarimin duhet të lejojnë ripërkufizimin kovariant. Rezultatet e anashkaluara dhe llojet e argumenteve duhet të jenë të pajtueshme me ato origjinale.

[x]. Kovarianca, si dhe aftësia e një fëmije për të fshehur një komponent të eksportuar nga një paraardhës, e kombinuar me polimorfizmin, krijon një problem shkeljeje të llojit të rrallë, por shumë serioz.

[x]. Këto shkelje mund të shmangen duke përdorur: analizën globale (e cila është jopraktike), kufizimin e kovariancës në tipe fikse (që është në kundërshtim me parimin Open-Closed), zgjidhjen e Catcall, e cila parandalon një objektiv polimorfik të thërrasë një nënprogram me kovariancë ose të fshehë një fëmijë.

Shënime bibliografike

Një numër materialesh nga ky leksion janë paraqitur në raportet në forumet OOPSLA 95 dhe TOOLS PACIFIC 95, dhe janë publikuar gjithashtu në . Një numër materialesh rishikimi janë huazuar nga artikulli.

Koncepti i konkluzionit automatik të tipit u prezantua në , ku përshkruhet algoritmi i konkluzionit të tipit të gjuhës funksionale ML. Marrëdhënia ndërmjet polimorfizmit dhe kontrollit të tipit është hulumtuar në.

Teknikat për përmirësimin e efikasitetit të kodit në gjuhët e shtypura në mënyrë dinamike në kontekstin e gjuhës së vetvetes mund të gjenden në.

Luca Cardelli dhe Peter Wegner shkruan një artikull teorik mbi llojet në gjuhët e programimit që patën një ndikim të madh te specialistët. Kjo punë, e ndërtuar mbi bazën e llogaritjes lambda (shih), shërbeu si bazë për shumë studime të mëtejshme. Ajo u parapri nga një tjetër dokument themelor nga Cardelli.

Manuali ISE përfshin një hyrje në problemet e aplikimit të përbashkët të polimorfizmit, kovariancës dhe fshehjes së pasardhësve. Mungesa e analizës së duhur në botimin e parë të këtij libri çoi në një sërë diskutimesh kritike (të parat prej të cilave ishin komentet e Philippe Elinck në veprën bachelor "De la Conception-Programmation par Objets", Memoire de licence, Universite Libre de Bruxelles (Belgjikë), 1988), shprehur në veprat dhe . Artikulli i Cook jep disa shembuj në lidhje me problemin e kovariancës dhe përpjekjet për ta zgjidhur atë. Një zgjidhje e bazuar në parametrat e tipit për entitetet bashkëvariante në TOOLS EUROPE 1992 u propozua nga Franz Weber. Përkufizime të sakta të koncepteve të korrektësisë së sistemit, si dhe korrektësisë së klasës, jepen në , dhe aty propozohet gjithashtu një zgjidhje duke përdorur një analizë të plotë të sistemit. Zgjidhja Catcall u propozua për herë të parë në ; Shiko gjithashtu .

Zgjidhja e rregullimit u prezantua në fjalimin tim në seminarin TOOLS EUROPE 1994. Megjithatë, në atë kohë, nuk e pashë nevojën për spirancë- reklamat dhe kufizimet përkatëse të përputhshmërisë. Paul Dubois dhe Amiram Yehudai nxituan të theksojnë se problemi i kovariancës mbetet në këto kushte. Ata, si dhe Reinhardt Budde, Karl-Heinz Sylla, Kim Walden dhe James McKim, bënë shumë komente që ishin të një rëndësie thelbësore në punën që çoi në shkrimin e kësaj leksioni.

Një sasi e madhe literaturë i kushtohet çështjeve të kovariancës. Në dhe do të gjeni një bibliografi të gjerë dhe një pasqyrë të aspekteve matematikore të problemit. Për një listë të lidhjeve me materialet online në teorinë e tipit OOP dhe faqet e internetit të autorëve të tyre, shihni faqen e Laurent Dami. Konceptet e kovariancës dhe kontravariancës janë huazuar nga teoria e kategorisë. Paraqitjen e tyre në kontekstin e shtypjes së programeve ia detyrojmë Luca Cardelli-t, i cili filloi t'i përdorte në fjalimet e tij në fillim të viteve '80, por nuk i përdori ato në shtyp deri në fund të viteve '80.

Teknikat e bazuara në variablat e tipit janë përshkruar në , , .

Kontravarianca u zbatua në gjuhën Sather. Shpjegimet janë dhënë në.

  • Shtypja dinamike është një teknikë e përdorur gjerësisht në gjuhët e programimit dhe gjuhët e specifikimeve, në të cilën një variabël lidhet me një lloj në momentin kur caktohet vlera, dhe jo në kohën kur deklarohet ndryshorja. Kështu, në pjesë të ndryshme të programit, e njëjta variabël mund të marrë vlera të llojeve të ndryshme. Shembuj të gjuhëve të shtypura në mënyrë dinamike janë Smalltalk, Python, Objective-C, Ruby, PHP, Perl, JavaScript, Lisp, xBase, Erlang, Visual Basic.

    Teknika e kundërt është shtypja statike.

    Në disa gjuhë me shtypje dinamike të dobët, ekziston një problem i krahasimit të vlerave, për shembull, PHP ka operatorët e krahasimit "==", "!=" dhe "===", "!==", ku çifti i dytë i operacioneve krahason vlerat dhe llojet e variablave. Operatori "===" vlerëson të vërtetën vetëm nëse përputhet në mënyrë të përsosur, ndryshe nga "==" që e konsideron shprehjen e mëposhtme si të vërtetë: (1=="1"). Vlen të theksohet se ky nuk është problem i shtypjes dinamike në përgjithësi, por i gjuhëve programuese specifike.

Konceptet e ndërlidhura

Një gjuhë programimi është një gjuhë zyrtare për të shkruar programe kompjuterike. Një gjuhë programimi përcakton një grup rregullash leksikore, sintaksore dhe semantike që përcaktojnë pamjen e programit dhe veprimet që interpretuesi (zakonisht një kompjuter) do të kryejë nën kontrollin e tij.

Sheqeri sintaksor në një gjuhë programimi është një veçori sintaksore, përdorimi i të cilit nuk ndikon në sjelljen e programit, por e bën përdorimin e gjuhës më të përshtatshëm për njerëzit.

Një veti është një mënyrë për të hyrë në gjendjen e brendshme të një objekti, duke imituar një variabël të një lloji. Hyrja në një veçori të një objekti duket njësoj si qasja në një fushë strukture (në programimin e strukturuar), por në fakt zbatohet nëpërmjet një thirrjeje funksioni. Kur përpiqeni të vendosni vlerën e kësaj vetie, thirret një metodë, dhe kur përpiqeni të merrni vlerën e kësaj vetie, thirret një metodë tjetër.

Extended Backus–Naur Form (EBNF) është një sistem formal përkufizimi sintaksor në të cilin disa kategori sintaksore përcaktohen në mënyrë sekuenciale përmes të tjerave. Përdoret për të përshkruar gramatikat formale pa kontekst. Sugjeruar nga Niklaus Wirth. Është një ripërpunim i zgjeruar i formave Backus-Naur, dallon nga BNF në konstruksione më “kapacitet”, të cilat, me të njëjtën aftësi shprehëse, bëjnë të mundur thjeshtimin...

Programimi aplikativ është një lloj programimi deklarativ në të cilin shkrimi i një programi konsiston në aplikimin sistematik të një objekti në një tjetër. Rezultati i një aplikacioni të tillë është përsëri një objekt që mund të marrë pjesë në aplikacione edhe si funksion edhe si argument, e kështu me radhë. Kjo e bën regjistrimin e programit të qartë matematikisht. Fakti që një funksion shënohet me një shprehje tregon mundësinë e përdorimit të funksioneve të vlerës - funksionale ...

Një gjuhë programimi lidhëse është një gjuhë programimi e bazuar në faktin se lidhja e dy pjesëve të kodit shpreh përbërjen e tyre. Në një gjuhë të tillë, specifikimi i nënkuptuar i argumenteve të funksionit përdoret gjerësisht (shih programimin e pakuptimtë), funksionet e reja përcaktohen si përbërje funksionesh dhe lidhja përdoret në vend të aplikimit. Kjo qasje është në kundërshtim me programimin aplikativ.

Një variabël është një atribut i një sistemi fizik ose abstrakt që mund të ndryshojë vlerën e tij, zakonisht numerike. Koncepti i një variabli përdoret gjerësisht në fusha të tilla si matematika, shkencat natyrore, inxhinieria dhe programimi. Shembuj të variablave janë: temperatura e ajrit, parametri i funksionit dhe shumë më tepër.

Analiza sintaksore (ose analizimi, analizimi i zhargonit ← analizimi i anglishtes) në gjuhësi dhe shkenca kompjuterike është procesi i krahasimit të një sekuence lineare leksemash (fjalësh, shenjash) të një gjuhe natyrore ose formale me gramatikën e saj formale. Rezultati është zakonisht një pemë analizuese (pema sintaksore). Zakonisht përdoret në lidhje me analizën leksikore.

Një lloj i përgjithësuar i të dhënave algjebrike (GADT) është një nga llojet e llojeve të të dhënave algjebrike, i cili karakterizohet nga fakti se ndërtuesit e tij mund të kthejnë vlera që nuk janë të llojit të tyre të lidhur me të. Projektuar nën ndikimin e punimeve në familjet induktive midis studiuesve të llojeve të varura.

Semantika në programim është një disiplinë që studion formalizimin e kuptimeve të konstrukteve të gjuhëve programuese duke ndërtuar modelet e tyre formale matematikore. Mjete të ndryshme mund të përdoren si mjete për ndërtimin e modeleve të tilla, për shembull, logjika matematikore, llogaritja λ, teoria e grupeve, teoria e kategorive, teoria e modelit, algjebra universale. Formalizimi i semantikës së një gjuhe programimi mund të përdoret si për të përshkruar gjuhën, ashtu edhe për të përcaktuar vetitë e gjuhës...

Programimi i orientuar nga objekti (OOP) është një metodologji programimi e bazuar në paraqitjen e një programi si një grup objektesh, secila prej të cilave është një shembull i një klase të caktuar, dhe klasat formojnë një hierarki trashëgimore.

Variabla dinamike - një variabël në program, një vend në RAM për të cilin ndahet gjatë ekzekutimit të programit. Në fakt, është një pjesë e memories e alokuar nga sistemi në një program për qëllime specifike ndërsa programi është duke u ekzekutuar. Në këtë, ai ndryshon nga një ndryshore statike globale - një pjesë e memories e alokuar nga sistemi në një program për qëllime specifike përpara se të fillojë programi. Një variabël dinamik është një nga klasat e ruajtjes së ndryshoreve.

Për të shpjeguar sa më thjeshtë dy teknologji krejtësisht të ndryshme, le të fillojmë nga e para. Gjëja e parë që has një programues kur shkruan kod është deklarimi i variablave. Ju mund të vini re se, për shembull, në gjuhën e programimit C++, duhet të specifikoni llojin e një ndryshoreje. Kjo do të thotë, nëse deklaroni një ndryshore x, atëherë duhet të shtoni int - për të ruajtur të dhënat e numrave të plotë, float - për të ruajtur të dhënat me pikë lundruese, char - për të dhënat e karaktereve dhe lloje të tjera të disponueshme. Prandaj, C++ përdor shtypjen statike, ashtu si paraardhësi i tij, C.

Si funksionon shtypja statike?

Në momentin e deklarimit të një ndryshoreje, përpiluesi duhet të dijë se cilat funksione dhe parametra mund të përdorë në lidhje me të dhe cilët jo. Prandaj, programuesi duhet menjëherë të tregojë qartë llojin e ndryshores. Gjithashtu vini re se lloji i një ndryshoreje nuk mund të ndryshohet gjatë kohës që kodi është duke u ekzekutuar. Por ju mund të krijoni llojin tuaj të të dhënave dhe t'i përdorni në të ardhmen.

Le të shqyrtojmë një shembull të vogël. Kur inicializojmë variablin x (int x;), ne specifikojmë identifikuesin int - kjo është një shkurtim për të cilin ruan vetëm numra të plotë në rangun nga - 2 147 483 648 deri në 2 147 483 647. Kështu, përpiluesi kupton që mund të kryejë vlerat matematikore në këtë variabël - shuma, ndryshimi, shumëzimi dhe pjesëtimi. Por, për shembull, funksioni strcat(), i cili lidh dy vlera char, nuk mund të aplikohet në x. Në fund të fundit, nëse hiqni kufizimet dhe përpiqeni të lidhni dy vlera int duke përdorur një metodë simbolike, atëherë do të ndodhë një gabim.

Pse na duhen gjuhët me shtypje dinamike?

Pavarësisht nga disa kufizime, shtypja statike ka një sërë avantazhesh dhe nuk sjell shumë shqetësime në algoritmet e shkrimit. Megjithatë, për qëllime të ndryshme, mund të nevojiten më shumë "rregulla të lira" në lidhje me llojet e të dhënave.

Një shembull i mirë për të dhënë është JavaScript. Kjo gjuhë programimi përdoret zakonisht për të futur në një kornizë në mënyrë që të fitojë akses funksional në objekte. Për shkak të kësaj veçorie, ajo ka fituar një popullaritet të madh në teknologjitë e uebit, ku shtypja dinamike është ideale. Ndonjëherë, shkrimi i skripteve të vogla dhe makrove thjeshtohet. Dhe gjithashtu ka një avantazh në ripërdorimin e variablave. Por kjo mundësi përdoret mjaft rrallë, për shkak të konfuzionit dhe gabimeve të mundshme.

Cili lloj i shtypjes është më i mirë?

Debati se shtypja dinamike është më e mirë se shtypja e rreptë vazhdon edhe sot e kësaj dite. Zakonisht ato ndodhin në programues shumë të specializuar. Sigurisht, zhvilluesit e uebit përdorin çdo ditë të gjitha avantazhet e shtypjes dinamike për të krijuar kodin me cilësi të lartë dhe produktin përfundimtar të softuerit. Në të njëjtën kohë, programuesit e sistemit që zhvillojnë algoritme komplekse në gjuhët e programimit të nivelit të ulët zakonisht nuk kanë nevojë për aftësi të tilla, kështu që shtypja statike është e mjaftueshme për ta. Ka, natyrisht, përjashtime nga rregulli. Për shembull, shtypja dinamike zbatohet plotësisht në Python.

Prandaj, është e nevojshme të përcaktohet lidershipi i një teknologjie të veçantë bazuar vetëm në parametrat e hyrjes. Shkrimi dinamik është më i mirë për zhvillimin e kornizave të lehta dhe fleksibël, ndërsa shtypja e fortë është më e mirë për krijimin e arkitekturës masive dhe komplekse.

Ndarja në shtypje "të fortë" dhe "të dobët".

Ndër materialet në gjuhën ruse dhe në gjuhën angleze për programimin, mund të haset shprehja - shtypje "e fortë". Ky nuk është një koncept më vete, ose më saktë, një koncept i tillë nuk ekziston fare në leksikun profesional. Edhe pse shumë përpiqen ta interpretojnë ndryshe. Në fakt, shtypja "e fortë" duhet kuptuar si ajo që ju përshtatet dhe me të cilën është më komode për të punuar. Një sistem "i dobët" është një sistem i papërshtatshëm dhe joefikas për ju.

Karakteristikë dinamike

Duhet ta keni vënë re se në fazën e shkrimit të kodit, përpiluesi analizon konstruktet e shkruara dhe gjeneron një gabim nëse llojet e të dhënave nuk përputhen. Por jo JavaScript. Veçantia e tij qëndron në faktin se do të kryejë operacionin në çdo rast. Këtu është një shembull i thjeshtë - ne duam të shtojmë një karakter dhe një numër, i cili nuk ka kuptim: "x" + 1.

Në gjuhët statike, në varësi të vetë gjuhës, ky operacion mund të ketë pasoja të ndryshme. Por në shumicën e rasteve, as nuk do të lejohet të përpilohet, pasi përpiluesi do të japë një gabim menjëherë pas shkrimit të një konstruksioni të tillë. Ai thjesht do ta konsiderojë të pasaktë dhe do të ketë plotësisht të drejtë.

Në gjuhët dinamike, ky operacion mund të kryhet, por në shumicën e rasteve një gabim do të pasojë tashmë në fazën e ekzekutimit të kodit, pasi përpiluesi nuk analizon llojet e të dhënave në kohë reale dhe nuk mund të vendosë për gabimet në këtë fushë. JavaScript është unik në atë që do të kryejë një operacion të tillë dhe do të përfundojë me një grup karakteresh të palexueshëm. Ndryshe nga gjuhët e tjera të cilat thjesht do ta mbyllin programin.

A janë të mundshme arkitekturat ngjitur?

Për momentin, nuk ka asnjë teknologji të lidhur që mund të mbështesë njëkohësisht shtypjen statike dhe dinamike në gjuhët e programimit. Dhe mund të themi me besim se nuk do të shfaqet. Meqenëse arkitekturat ndryshojnë nga njëra-tjetra në terma themelorë dhe nuk mund të përdoren njëkohësisht.

Por, megjithatë, në disa gjuhë, ju mund të ndryshoni shtypjen me ndihmën e kornizave shtesë.

  • Në gjuhën e programimit Delphi, nënsistemi Variant.
  • Në gjuhën e programimit AliceML - paketa shtesë.
  • Në gjuhën e programimit Haskell, biblioteka Data.Dynamic.

Kur është vërtet më e mirë shtypja e fortë sesa shtypja dinamike?

Ju mund të miratoni pa mëdyshje avantazhin e shtypjes së fortë mbi dinamikën vetëm nëse jeni një programues fillestar. Absolutisht të gjithë specialistët e IT-së pajtohen për këtë. Kur mësoni aftësitë themelore dhe themelore të programimit, është më mirë të përdorni shtypje të fortë për të fituar një disiplinë kur punoni me variabla. Pastaj, nëse është e nevojshme, mund të kaloni në dinamikë, por aftësitë e fituara me shtypje të fortë do të luajnë një rol të rëndësishëm. Do të mësoni se si të kontrolloni me kujdes variablat dhe të merrni parasysh llojet e tyre kur hartoni dhe shkruani kodin.

Përfitimet e shtypjes dinamike

  • Minimizon numrin e karaktereve dhe rreshtave të kodit për shkak të deklarimit të panevojshëm paraprak të variablave dhe specifikimit të llojit të tyre. Lloji do të përcaktohet automatikisht pasi të caktohet vlera.
  • Në blloqe të vogla kodi, perceptimi vizual dhe logjik i ndërtimeve thjeshtohet për shkak të mungesës së linjave të deklarimit "ekstra".
  • Dinamika ka një efekt pozitiv në shpejtësinë e përpiluesit, pasi nuk merr parasysh llojet dhe nuk i kontrollon ato për pajtueshmëri.
  • Rrit fleksibilitetin dhe ju lejon të krijoni dizajne të gjithanshme. Për shembull, kur krijoni një metodë që duhet të ndërveprojë me një grup të dhënash, nuk keni nevojë të krijoni funksione të veçanta për të punuar me vargje numerike, tekst dhe lloje të tjera. Mjafton të shkruani një metodë dhe do të funksionojë me çdo lloj.
  • Ai thjeshton daljen e të dhënave nga sistemet e menaxhimit të bazës së të dhënave, kështu që shtypja dinamike përdoret në mënyrë aktive në zhvillimin e aplikacioneve në internet.

Mësoni më shumë rreth gjuhëve të programimit me shtypje statike

  • C++ është gjuha programuese më e përdorur për qëllime të përgjithshme. Sot ajo ka disa botime kryesore dhe një ushtri të madhe përdoruesish. U bë popullor për shkak të fleksibilitetit, shtrirjes së pakufishme dhe mbështetjes për paradigma të ndryshme programimi.

  • Java është një gjuhë programimi që përdor një qasje të orientuar nga objekti. Fitoi popullaritet për shkak të shumë platformave. Kur përpilohet, kodi interpretohet në bytecode që mund të ekzekutohet në çdo sistem operativ. Java dhe shtypja dinamike janë të papajtueshme sepse gjuha është e shtypur fort.

  • Haskell është gjithashtu një nga gjuhët e njohura, kodi i së cilës mund të integrohet dhe të ndërveprojë me gjuhë të tjera. Por, pavarësisht nga një fleksibilitet i tillë, ai ka shtypje të fortë. Pajisur me një grup të madh të integruar të llojeve dhe aftësinë për të krijuar tuajin.

Mësoni më shumë rreth gjuhëve të programimit me shtypje dinamike

  • Python është një gjuhë programimi që u krijua kryesisht për të lehtësuar punën e një programuesi. Ka një sërë përmirësimesh funksionale, falë të cilave rrit lexueshmërinë e kodit dhe shkrimin e tij. Në shumë mënyra, kjo u arrit falë shtypjes dinamike.

  • PHP është një gjuhë skriptimi. Përdoret gjerësisht në zhvillimin e uebit, duke ofruar ndërveprim me bazat e të dhënave për të krijuar faqe interneti dinamike interaktive. Falë shtypjes dinamike, puna me bazat e të dhënave lehtësohet shumë.

  • JavaScript është gjuha e programimit e përmendur tashmë më lart, e cila ka gjetur aplikim në teknologjitë e ueb-it për krijimin e skripteve të internetit që funksionojnë në anën e klientit. Shtypja dinamike përdoret për ta bërë kodin më të lehtë për t'u shkruar, sepse zakonisht ndahet në blloqe të vogla.

Lloji dinamik i shtypjes - Disavantazhet

  • Nëse është bërë një gabim shtypi ose gabim gjatë përdorimit ose deklarimit të variablave, përpiluesi nuk do ta shfaqë atë. Dhe problemet do të shfaqen gjatë ekzekutimit të programit.
  • Kur përdorni shtypjen statike, të gjitha deklarimet e variablave dhe funksioneve zakonisht vendosen në një skedar të veçantë, gjë që e bën të lehtë krijimin e dokumentacionit në të ardhmen ose edhe përdorimin e vetë skedarit si dokumentacion. Prandaj, shtypja dinamike nuk lejon përdorimin e një veçorie të tillë.

Përmblidhni

Shtypja statike dhe dinamike përdoren për qëllime krejtësisht të ndryshme. Në disa raste, zhvilluesit po ndjekin përfitime funksionale, dhe në të tjera motive thjesht personale. Në çdo rast, për të përcaktuar vetë llojin e shtypjes, duhet t'i studioni me kujdes ato në praktikë. Në të ardhmen, kur krijoni një projekt të ri dhe zgjidhni një shtypje për të, kjo do të luajë një rol të madh dhe do të japë një kuptim të zgjedhjes efektive.

Kur studioni gjuhë programimi, shpesh dëgjoni fraza si "shtypur në mënyrë statike" ose "shtypur në mënyrë dinamike" në biseda. Këto koncepte përshkruajnë procesin e kontrollit të tipit, dhe si kontrolli statik i tipit ashtu edhe ai dinamik i tipit i referohen sistemeve të llojeve të ndryshme. Një sistem tipi është një grup rregullash që caktojnë një veti të quajtur "lloj" entiteteve të ndryshme në një program: variablave, shprehjeve, funksioneve ose moduleve - me qëllimin përfundimtar të reduktimit të gabimeve duke siguruar që të dhënat të shfaqen në mënyrë korrekte.

Mos u shqetësoni, e di që e gjithë kjo tingëllon konfuze, kështu që ne do të fillojmë me bazat. Çfarë është "kontrolli i tipit" dhe çfarë është një lloj në përgjithësi?

Lloji i

Kodi që kalon kontrollin e tipit dinamik është përgjithësisht më pak i optimizuar; përveç kësaj, ekziston mundësia e gabimeve në kohën e ekzekutimit dhe, si rezultat, nevoja për të kontrolluar para çdo ekzekutimi. Megjithatë, shtypja dinamike hap derën për teknika të tjera të fuqishme programimi, siç është metaprogramimi.

Keqkuptime të zakonshme

Miti 1: Shtypja statike/dinamike == shtypje e fortë/dobët

Një keqkuptim i zakonshëm është se të gjitha gjuhët e shtypura në mënyrë statike janë të shtypura fort dhe të gjitha gjuhët e shtypura dinamike janë të shtypura dobët. Kjo është e gabuar, dhe ja pse.

Një gjuhë e shtypur fort është ajo në të cilën variablat lidhen me lloje specifike të dhënash dhe e cila do të shkaktojë një gabim tipi nëse llojet e pritura dhe ato aktuale nuk përputhen, sa herë që kryhet kontrolli. Është më e lehtë të mendosh për një gjuhë të shtypur fort si një gjuhë shumë të sigurt për tipin. Për shembull, në kodin e përdorur tashmë më lart, një gjuhë e shtypur fort do të prodhojë një gabim të qartë të llojit që do të ndërpresë programin:

X = 1 + "2"

Ne shpesh i lidhim gjuhët e shtypura në mënyrë statike si Java dhe C# me gjuhë të shtypura fort (të cilat janë) sepse lloji i të dhënave vendoset në mënyrë eksplicite kur variabli inicializohet - si në këtë shembull Java:

String foo = String i ri ("përshëndetje botë");

Sidoqoftë, Ruby, Python dhe JavaScript (të gjitha janë të shtypura në mënyrë dinamike) janë gjithashtu të shtypura fuqishëm, megjithëse zhvilluesi nuk ka nevojë të specifikojë llojin e ndryshores kur e deklaron atë. Konsideroni të njëjtin shembull, por të shkruar në Ruby:

Foo = "përshëndetje botë"

Të dyja gjuhët janë të shtypura fort, por përdorin metoda të ndryshme të kontrollit të tipit. Gjuhët si Ruby, Python dhe JavaScript nuk kërkojnë përkufizime të qarta të tipit për shkak të konkluzionit të llojit, aftësisë për të konkluduar në mënyrë programore llojin e saktë të një ndryshoreje bazuar në vlerën e saj. Konkludimi i tipit është një veti e veçantë e gjuhës dhe nuk zbatohet për sistemet e tipit.

Një gjuhë e shtypur lirshëm është një gjuhë në të cilën variablat nuk janë të lidhur me një lloj të caktuar të dhënash; ata ende kanë një lloj, por kufizimet e sigurisë së llojit janë shumë më të dobëta. Konsideroni shembullin e mëposhtëm të kodit PHP:

$foo = "x"; $foo = $foo + 2; // jo një gabim echo $foo; // 2

Meqenëse PHP është shtypur dobët, nuk ka asnjë gabim në këtë kod. Ngjashëm me sugjerimin e mëparshëm, jo ​​të gjitha gjuhët e shtypura dobët janë të shtypura në mënyrë dinamike: PHP është një gjuhë e shtypur dinamikisht, por C, gjithashtu një gjuhë e shtypur dobët, është me të vërtetë e shtypur në mënyrë statike.

Miti është thyer.

Megjithëse sistemet e tipit statik/dinamik dhe të fortë/dobët janë të ndryshëm, ato të dyja lidhen me sigurinë e tipit. Mënyra më e lehtë për ta thënë është se sistemi i parë ju tregon se kur kontrollohet siguria e tipit, dhe i dyti ju tregon se si.

Miti 2: Shtypja statike/dinamike == gjuhë të përpiluara/interpretuara

Është e vërtetë të thuhet se shumica e gjuhëve të shtypura në mënyrë statike zakonisht përpilohen dhe shtypen në mënyrë dinamike interpretohen, por kjo deklaratë nuk mund të përgjithësohet, dhe ekziston një shembull i thjeshtë për këtë.

Kur flasim për shtypjen e gjuhës, po flasim për gjuhën në tërësi. Për shembull, nuk ka rëndësi se çfarë versioni të Java-së po përdorni - ai gjithmonë do të shtypet në mënyrë statike. Kjo është ndryshe nga rasti kur gjuha përpilohet ose interpretohet, pasi në këtë rast bëhet fjalë për një zbatim specifik të gjuhës. Në teori, çdo gjuhë mund të përpilohet dhe interpretohet. Implementimi më i popullarizuar i gjuhës Java përdor kompilimin në bytecode, i cili interpretohet nga JVM - por ka zbatime të tjera të kësaj gjuhe që përpilohen drejtpërdrejt në kodin e makinës ose interpretohen siç është.

Nëse kjo ende nuk është e qartë, ju këshilloj ta lexoni këtë seri.

konkluzioni

E di që kishte shumë informacion në këtë artikull - por besoj se e keni kuptuar mirë. Do të doja të transferoja informacionin rreth shtypjes së fortë / të dobët në një artikull të veçantë, por kjo nuk është një temë aq e rëndësishme; për më tepër, duhej treguar se ky lloj shtypjeje nuk kishte të bënte me kontrollin e tipit.

Nuk ka asnjë përgjigje të vetme për pyetjen "cila shtypje është më e mirë?" - secila ka avantazhet dhe disavantazhet e veta. Disa gjuhë - të tilla si Perl dhe C# - madje ju lejojnë të zgjidhni vetë midis sistemeve të kontrollit të tipit statik dhe dinamik. Kuptimi i këtyre sistemeve do t'ju lejojë të kuptoni më mirë natyrën e gabimeve që ndodhin, si dhe ta bëni më të lehtë trajtimin e tyre.



Po ngarkohet...
Top