Moștenire (informatică)

De la Wikipedia, enciclopedia liberă.
Salt la navigare Salt la căutare

În calcul , „Moștenirea este unul dintre conceptele fundamentale în programarea obiectului paradigmă . Acesta constă dintr-o relație pe care limbajul de programare , sau programatorul însuși, o stabilește între două clase . Dacă clasa B moștenește din clasa A , B se spune că este o subclasă a lui A și A este o superclasă a lui B. Denumirile alternative echivalente sunt clasa părinte sau clasa de bază pentru A și clasa copil sau clasa derivată pentru B În funcție de programarea limbajului, moștenirea poate fi moștenire simplă sau simplă (fiecare clasă poate avea cel mult o superclasă directă) sau multiplă (fiecare clasă poate avea mai multe superclase directe).

În general, utilizarea moștenirii are ca rezultat o ierarhie de clase; în limbile cu moștenire unică, există un copac dacă există o superclasă unică „rădăcină” din care toate celelalte sunt subclase direct sau indirect (cum ar fi clasa Object în cazul Java ) sau o pădure altfel; moștenirea multiplă, pe de altă parte, definește o ierarhie grafică aciclică directă .

Interpretare

Moștenirea este o relație de generalizare / specializare: superclasa definește un concept general, iar subclasa reprezintă o variantă specifică a acelui concept general. Întreaga teorie a moștenirii în limbaje orientate obiect se bazează pe această interpretare. Pe lângă faptul că este un instrument important de modelare (și, prin urmare, semnificativ și în alte contexte decât programarea în sens strict, de exemplu în UML ), moștenirea are repercusiuni foarte importante asupra reutilizării software-ului.

Este-o relație

De exemplu, li s-a dat un telefono clasă dacă ar putea obține subclasa cellulare , deoarece telefonul este un caz special al telefonului . Acest tip de relație se mai numește și o relație is-a („este-a”): „un telefon mobil este-un telefon”.

Relația este o relație care trebuie să lege o subclasă de superclasa sa este adesea făcută explicită prin referirea la așa-numitul principiu de substituție Liskov , introdus în 1993 de Barbara Liskov și Jeannette Wing . Conform acestui principiu, obiectele aparținând unei subclase trebuie să poată prezenta toate comportamentele și proprietățile prezentate de cei care aparțin superclasei, astfel încât utilizarea acestora în locul acesteia din urmă să nu modifice corectitudinea informațiilor returnate de program. Pentru ca clasa cellulare să fie concepută ca o subclasă de telefono , de exemplu, este necesar ca un telefon mobil să poată fi utilizat în toate contextele în care este necesară utilizarea unui telefon.

Atât relația is-a, cât și principiul Liskov nu necesită ca subclasa să prezinte doar caracteristicile prezentate de superclasă. De exemplu, faptul că un telefon mobil poate trimite , de asemenea , SMS - uri nu afectează faptul că acesta poate fi înlocuit cu un telefon. Prin urmare, subclasa poate prezenta caracteristici suplimentare în comparație cu superclasa.
În plus, ar putea, de asemenea, să efectueze unele funcționalități în mod diferit, atâta timp cât această diferență nu este observabilă din exterior. De exemplu, un telefon mobil inițiază sau primește un apel telefonic într-un mod tehnic diferit de un telefon tradițional (folosind rețeaua GSM ), dar chiar și acest lucru nu contrazice principiul substituibilității.

Încălcarea principiului substituibilității

În ciuda tuturor, în general este posibil din punct de vedere tehnic extinderea unei clase prin încălcarea principiului substituibilității, deoarece regulile impuse de limbajul de programare utilizat nu pot depăși corectitudinea formală a codului scris și, eventual, aderarea acestuia la anumite condiții prealabile sau postcondiții. În unele cazuri, principiul este încălcat în mod intenționat [1] ; cu toate acestea, atunci când se întâmplă acest lucru, este recomandabil să documentați problema în mod corespunzător, pentru a evita utilizarea instanțelor clasei în care principiul menționat anterior al substituibilității este considerat a fi valid [1] .

Polimorfism
Pictogramă lupă mgx2.svg Același subiect în detaliu: Polimorfism (informatică) .

Când principiul substituibilității este respectat, moștenirea poate fi utilizată pentru a obține așa-numitul polimorfism . Dacă este bine utilizat, permite programe flexibile , în sensul că vă permite să scrieți cod capabil să facă față nevoilor și schimbărilor viitoare, necesitând corecții minime și / sau bine circumscrise.

Definiție tehnică

Modul în care limbajele de programare gestionează relațiile de moștenire rezultă din sensul dat moștenirii ca relație is-a . O clasă B a declarat o subclasă a altei clase A

  • moștenește (implicit are) toate variabilele de instanță și metodele lui A.
  • poate avea variabile sau metode suplimentare.
  • Poate redefini metodele moștenite de la A până la suprascrierea , astfel încât acestea să ruleze aceeași operație conceptuală într-un mod specializat.

Faptul că subclasa moștenește toate caracteristicile superclasei are sens în lumina conceptului de substituibilitate. În paradigma orientată obiect, de fapt, o clasă de obiecte este definită de caracteristicile sale (atribute și metode). În consecință, ar fi fals să pretindem că „un telefon mobil este un telefon” dacă telefonul mobil nu ar avea toate caracteristicile definitorii ale unui telefon (de exemplu, un microfon , un difuzor și capacitatea de a iniția sau de a primi apeluri).

Totuși, ceea ce s-a spus nu implică faptul că substituibilitatea este garantată: relația clasă-subclasă trebuie să fie distinctă conceptual de relația tip-subtip. În special, mecanismul de imperative nu garantează că semantica metodei superclasa rămân neschimbate în subclasa. De asemenea, substituibilitatea nu este respectată atunci când se utilizează instrumente pentru ascunderea vizibilității metodelor ( limitare ).

Aplicații de moștenire

Ereditatea poate fi studiată și descrisă din mai multe puncte de vedere:

  • comportamentul obiectelor față de mediul extern;
  • structura internă a obiectelor;
  • ierarhia nivelurilor de moștenire;
  • impactul moștenirii asupra ingineriei software .

În general, pentru a evita confuzia, este recomandabil să abordați aceste aspecte separat

Specializare

Pictogramă lupă mgx2.svg Același subiect în detaliu: Subtipul (informatică) .

Unul dintre avantajele majore ale moștenirii este capacitatea de a crea versiuni „specializate” ale claselor existente, adică de a crea subtipuri ale acestora . Constructele care permit obținerea moștenirii nu garantează specializarea, pe care programatorul trebuie să o furnizeze prin definirea subclasei în mod adecvat, pentru a respecta principiul substituibilității.

Un alt mecanism similar cu specializarea este specificarea: apare atunci când o clasă moștenită declară că posedă un anumit „comportament” fără a-l implementa efectiv: în acest caz vorbim de o clasă abstractă . Toate clasele „concrete” (adică nu ele însele abstracte) care moștenesc din această clasă abstractă trebuie să implementeze în mod necesar acel comportament „lipsă”.

Redefinirea

Pictogramă lupă mgx2.svg Același subiect în detaliu: suprascriere .

Multe limbaje de programare orientate pe obiecte permit unei clase sau unui obiect să modifice modul în care este implementată propria funcționalitate moștenită de la o altă clasă (de obicei o metodă). Această caracteristică se numește „ suprascriere ”. În fața suprascrierii, aceeași metodă va avea un comportament diferit dacă este invocată asupra obiectelor superclasei sau în cele din subclasă (cel puțin în cazul limbajelor care adoptă legarea dinamică ). De exemplu, având în vedere o clasă Quadrilatero care definește unele comportamente generale pentru toate figurile geometrice cu 4 fețe, subclasa Rettangolo ar putea redefini (adică „suprascrie”) acele metode Quadrilatero care pot fi reimplementate într-un mod mai specific ținând cont de specificitățile dreptunghiurilor ( de exemplu, calculul suprafeței ar putea fi rescris în clasa Rettangolo mai ușor și mai eficient, pur și simplu ca produs al laturilor).

Extensie

Un alt motiv pentru utilizarea moștenirii este furnizarea unei clase de date sau a unei funcționalități suplimentare. Aceasta se numește de obicei extensie sau subclasificare . Spre deosebire de cazul specializării descrise mai sus, cu extensia noi date sau funcționalități sunt adăugate clasei moștenite, accesibile și utilizabile de toate instanțele clasei. Extensia este adesea utilizată atunci când nu este posibil sau convenabil să adăugați noi funcționalități la clasa de bază. Aceeași operație poate fi efectuată și la nivelul obiectului - în locul clasei - de exemplu folosind așa-numitele modele de decorator .

Reutilizarea codului

Unul dintre principalele avantaje ale utilizării moștenirii (în special combinat cu polimorfismul) este că favorizează reutilizarea codului . O subclasă nu doar moștenește (și, prin urmare, reutilizează) codul superclasei: polimorfismul asigură, de asemenea, că tot codul scris anterior pentru a manipula obiecte superclase este, de asemenea, implicit capabil să manipuleze obiecte subclase. De exemplu, un program care este capabil să reprezinte grafic obiecte din clasa Quadrilatero nu ar avea nevoie de nicio modificare pentru a trata în mod similar și obiecte dintr-o posibilă clasă Rettangolo .

Exemple

Să presupunem că un program folosește o clasă Animale care conține date pentru a specifica, de exemplu, dacă animalul este viu, unde se află, câte picioare are etc.; pe lângă aceste date, clasa ar putea conține și metode pentru descrierea modului în care animalul mănâncă, bea, se mișcă, se împerechează etc. Dacă am vrea să creăm o clasă de Mammifero , multe dintre aceste caracteristici ar rămâne exact aceleași cu cele ale animalelor generice, dar unele ar fi diferite. Așadar, am spune că Mammifero este o subclasă a clasei Animale (sau, dimpotrivă, Animale este clasa de bază - numită și clasa părinte - a Mammifero ). Important de reținut este că, în definirea noii clase, nu este necesar să specificăm din nou că un mamifer are caracteristicile normale ale unui animal (unde se află, faptul că mănâncă, bea, etc.), dar este suficient pentru a adăuga caracteristicile specifice care diferențiază mamiferele de alte animale (de exemplu, care este acoperit cu păr și are mamele și redefinirea funcțiilor care, deși comune tuturor celorlalte animale, se manifestă diferit, de exemplu modul în care se reproduc. următorul exemplu, scris în Java , observați în interiorul metodei riproduciti() apelul la super.riproduciti() , care este o metodă a clasei de bază care este redefinită. Pentru a folosi cuvinte simple am putea spune că această metodă spune că „ faceți tot ce ar face mai întâi clasa de bază ”urmat de codul care indică ce„ lucruri suplimentare ”trebuie să facă noua clasă.

Java

 clasa Mamifer extinde Animal {
    Păr de păr ;
    Mamele mamare ;

    Mamifer reprodus () {
        Descendenți de mamifere ;

        super . reprodus ();
        if ( isFemale ()) {
            descendenți = super . naște ();
            descendenți . alăptează ( m_b );
        }
        carePiese ( descendenți );
        întoarce urmași ;
    }
}

În exemplul de mai jos, o clasă Angajat este declarată cu unele atribute comune (Variabile). Constructorul (Sub) este declarat datorită căruia poate fi instanțiat un obiect din clasa angajată. Variabilele indicate cu „_” sunt utilizate pentru a vă asigura că puteți introduce validarea datelor înainte de a trece efectiv valorile de intrare în obiect. Mai jos însă, clasa manager moștenește de la clasa Angajați. Prin urmare, va fi obținut implicit (sau mai degrabă moștenit) toate metodele și funcțiile pe care le-am declarat în clasa părinte. În acest exemplu practic putem observa că, pe lângă moștenirea proprietăților clasei angajaților, clasa managerului implementează funcții și parametri exclusivi.

VB.NET

 Clasa publică Funcționar
nume privat ca Șir
salariu privat ca Dublu
matricola privată ca String
ani de serviciu privat ca număr întreg
Sub Public Nou (n Ca String, e la fel de dublu, m ca String, anunțuri ca Integer)
nume = _Nume ca șir  
salariu = _salary as double 
matricola = _matricola as string 
yearsOfService = _ads ca număr întreg 
        
Sfârșitul Sub
clasa finală

„Clasa Manager care a moștenit din clasa Angajați

Public Class Manager Moștenește angajatul
Numele privat Secretar ca Șir
Sub Public Nou (n ca String, e la fel de dublu, m asString, anunțuri ca Integer)
MyBase . Nou ( n , s , m , anunțuri )
Secretaryname = String . gol
Sfârșitul Sub
  
Clasa de sfârșit

„Ldp”

Foi de stil

Conceptul de moștenire se aplică, mai general, oricărui proces computerizat în care un anumit „context” primește anumite „caracteristici ” dintr-un alt context. De exemplu, în unele aplicații de procesare de text, atributele stilistice ale textului, cum ar fi dimensiunea fontului , aspectul sau culoarea, pot fi moștenite dintr-un șablon sau dintr-un alt document. Utilizatorul poate defini atribute pentru a se aplica anumitor elemente specifice, în timp ce toate celelalte moștenesc atributele dintr-o specificație globală de definiție a stilului. De exemplu, așa-numitele Cascading Style Sheets (CSS) sunt un limbaj de definire a stilului utilizat pe scară largă în proiectarea paginilor web . Din nou, unele atribute stilistice pot fi definite într-un mod specific, în timp ce altele sunt primite „în cascadă”. Când consultă site-uri web , de exemplu, utilizatorul poate decide să aplice un stil definit de el însuși pentru dimensiunea fonturilor pe pagini, în timp ce alte caracteristici, precum culoarea și tipul fonturilor, pot fi moștenite din foaia de stil generală. a site-ului.

Limitări și alternative

O utilizare masivă a tehnicii de moștenire în dezvoltarea programelor poate avea unele contraindicații și poate impune anumite constrângeri.

Să presupunem că avem o clasă Persona care conține numele, adresa, numărul de telefon, vârsta și sexul ca date. Putem defini o sub-clasă de Persona , numită Studente , care conține media notelor și cursurilor urmate, și o altă sub-clasă de Persona , numită Impiegato , care conține calificarea, locul de muncă îndeplinit și salariul.

Unele constrângeri sunt deja implicite în definirea acestor ierarhii de moștenire, dintre care unele sunt utile, în timp ce altele creează probleme:

Constrângeri impuse de programarea bazată pe moștenire

  • Unicitate

În cazul moștenirii simple, o clasă poate moșteni doar dintr- o clasă de bază. În exemplul de mai sus, Persona poate fi atât Studente cât și Impiegato , dar nu amândouă. Moștenirea multiplă rezolvă parțial această problemă prin crearea unei clase StudenteImpiegato Impiegato care moștenește atât de la Studente cât și de la Impiegato . Cu toate acestea, această nouă clasă poate moșteni din clasa sa de bază o singură dată: prin urmare, această soluție nu rezolvă cazul în care un „elev” are două locuri de muncă sau frecventează două școli.

  • Staticitate

Ierarhia moștenirii unui obiect este „înghețată” atunci când obiectul este instanțiat și nu poate fi schimbat ulterior. De exemplu, un obiect al clasei Studente nu poate deveni un obiect Impiegato , păstrând în același timp caracteristicile clasei sale de bază Persona [ neclar ] .

  • Vizibilitate

Când un program „client” are acces la un obiect, acesta are de obicei acces la toate datele unui obiect aparținând clasei de bază. Chiar dacă clasa de bază nu este de tip „public”, programul client poate crea obiecte pe tipul său. Pentru ca o funcție să citească valoarea medie a unui Studente acestei funcții trebuie să i se ofere posibilitatea de a accesa toate datele personale stocate în clasa de bază Persona .

Moștenirea și rolurile

Un rol descrie o caracteristică asociată unui obiect bazată pe relațiile pe care acest obiect le are cu un alt obiect (de exemplu: o persoană cu rol de elev frecventează un curs școlar). Moștenirea poate fi utilizată pentru a implementa aceste relații. În programarea orientată pe obiecte, aceste două tehnici de programare sunt adesea folosite ca alternative reciproce. Moștenirea este adesea folosită pentru modelarea rolurilor. De exemplu, puteți defini un rol de student pentru o persoană realizată definind o subclasă de persoană . În orice caz, nici ierarhia moștenirii și nici tipul de obiecte nu se pot schimba în timp. Din acest motiv, definirea rolurilor ca subclase poate provoca înghețarea rolurilor la crearea obiectului. În exemplul nostru, Persoana nu și-ar mai putea schimba cu ușurință rolul din Student în Angajat , dacă circumstanțele o justifică.

Aceste restricții pot fi dăunătoare, deoarece fac mai dificilă implementarea modificărilor care pot deveni necesare în viitor, deoarece acestea din urmă pot fi introduse numai după remodelare și actualizarea întregului proiect.

Pentru a face o utilizare corectă a moștenirii, este necesar să gândim în termeni cât mai „generali” posibil, astfel încât aspectele comune majorității claselor care trebuie instanțiate să fie reunite „cu factor comun” și inserate în clase de părinți. De exemplu, o clasă de bază AspettiLegali poate fi moștenită atât din clasa Person , cât și din clasa Firm pentru a gestiona problemele juridice comune ambelor.

Pentru a alege cea mai convenabilă tehnică de aplicat (proiect bazat pe roluri sau pe moștenire), merită să ne întrebăm dacă:

  • același obiect trebuie să reprezinte roluri diferite și să îndeplinească funcții diferite în momente diferite (proiectare bazată pe roluri);
  • mai multe clase (vă rugăm să rețineți, clase, NU obiecte) trebuie să efectueze operațiuni comune care pot fi grupate și atribuite unei singure clase de bază (proiectare bazată pe moștenire).

O consecință importantă a separării dintre roluri și clase părinte este că timpul de compilare și timpul de rulare al codului obiect produs sunt clar separate. Moștenirea este în mod clar o construcție impusă în timp de compilare, care nu modifică structura obiectelor în timpul rulării. De fapt, „tipurile” de obiecte instanțiate sunt deja predeterminate în timpul compilării. Așa cum s-a indicat deja în exemplele anterioare, la proiectarea clasei Person , fiind un angajat un caz particular de persoană, este necesar să ne asigurăm că clasa Person conține doar funcționalitățile și datele comune tuturor oamenilor, indiferent de contextul în care această clasă este instanțiată. În acest fel, sunteți sigur, de exemplu, că într-o clasă Person nu se va folosi niciodată membruul Work , deoarece nu toți oamenii au un loc de muncă sau, cel puțin, nu este garantat a priori că clasa Person este instanțiată numai pentru a crea obiecte referibile persoanelor care au un loc de muncă.

În schimb, gândindu-ne din punctul de vedere al programării bazate pe roluri, s-ar putea defini un subset al tuturor obiectelor posibile persoane care îndeplinesc „rolul” de angajat. Informațiile necesare definirii caracteristicilor muncii prestate vor fi inserate numai în obiectele care îndeplinesc rolul de angajat.

O modelare orientată obiect poate defini Jobul în sine ca un rol, deoarece un job poate fi realizat chiar și temporar și, prin urmare, nu are caracteristicile de "stabilitate" necesare pentru a modela o clasă pe acesta. Dimpotrivă, conceptul de loc de muncă este înzestrat cu caracteristici de stabilitate și persistență în timp. În consecință, gândind dintr-o perspectivă de programare orientată pe obiecte, s-ar putea construi o clasă Persoană și o clasă la locul de muncă , care interacționează între ele în funcție de o relație de tipul mai multor cu mulți cu schema „lucrări”, unde Persoana joacă rolul de angajat, atunci când are un loc de muncă și unde, simetric, locul de muncă joacă rolul de „locul său de muncă” atunci când angajatul lucrează în acesta.

Rețineți că, cu această abordare, toate clasele sunt create într-un singur "domeniu", în sensul că descriu entități care pot fi urmărite într-un singur domeniu în ceea ce privește terminologia care le descrie, ceea ce nu este posibil dacă se utilizează alte abordări .

Diferența dintre roluri și clase este dificil de înțeles dacă adoptați constructe și funcții cu transparență referențială - adică construcții și funcții care, atunci când primesc același parametru ca intrare, returnează întotdeauna aceeași valoare - deoarece rolurile sunt tipuri accesibile "pentru referință ", în timp ce clasele sunt tipuri care sunt accesibile numai atunci când sunt instanțiate în obiecte.

Programarea orientată pe componente ca alternativă la moștenire

Programarea orientată pe componente oferă o metodă alternativă pentru descrierea și manipularea sistemului menționat mai sus de oameni, studenți și angajați, de exemplu prin definirea unui set de clase auxiliare de Iscrizione și PostoDiLavoro pentru a stoca informațiile necesare pentru a descrie studentul și respectiv angajatul. Prin urmare, o colecție de obiecte la PostoDiLavoro de PostoDiLavoro poate fi asociată cu fiecare obiect Persona . Această procedură rezolvă unele dintre problemele menționate mai sus:

  • o Persona poate avea acum un număr de locuri de muncă și poate participa la un număr de instituții de învățământ;
  • toate aceste lucrări pot fi acum modificate, adăugate și șterse dinamic;
  • acum este posibil să treci un obiect de Iscrizione ca parametru al unei funcții - de exemplu, o funcție care trebuie să decidă dacă o cerere de înregistrare este acceptată - fără a fi nevoie să treci ca parametri toate datele care specifică datele personale (nume, vârstă, adresă) , etc.)

Utilizarea componentelor în loc de moștenire produce și cod scris cu sintaxă mai puțin ambiguă și mai ușor de interpretat. Comparați următoarele două exemple: primul folosește moștenirea:

Impiegato i = getImpiegato();
print(i.mansioneDiLavoro());

Este clar că funcția mansioneDiLavoro() este definită în clasa Impiegato , dar ar putea fi definită și în clasa de bază Persona , ceea ce ar putea provoca ambiguitate. Cu programarea componentelor, programatorul poate reduce ambiguitatea prin aplicarea unei ierarhii de moștenire „mai plate”:

Persona p = getPersona();
print(p.impiego().mansione());

Știind că clasa Impiego nu are clase parentale, este imediat evident că funcția mansione() este definită în clasa Impiego

Cu toate acestea, programarea orientată spre componente nu poate fi întotdeauna o alternativă viabilă la programarea bazată pe moștenire, care, de exemplu, permite polimorfismul și încapsularea . Mai mult, crearea de clase de componente poate crește, de asemenea, lungimea codului care trebuie scris.

Notă

  1. ^ a b Exemplu în Java: clasa java.util.IdentityHashMap , aparținând bibliotecilor standard ale limbii, încalcă intenționat contractul general stabilit de tipul java.util.Map , dar, după cum se poate vedea din documentația sa, faptul că este încălcat contractul general al interfeței Map este bine documentat.

Elemente conexe

Controlul autorității GND ( DE ) 4277478-0
Informatică Portal IT : accesați intrările Wikipedia care se ocupă cu IT