Thursday, March 28, 2024 22:11

Cuprins >> Programarea Orientată Pe Obiecte > Moștenirea (inheritance)

Moștenirea (inheritance)

În prima lecție a capitolului Obiecte, am discutat în mod generic despre Programarea Orientată pe Obiecte (acronim OOP – din englezescul Object Oriented Programming) și am enumerat principiile sale fundamentale: încapsularea, moștenirea, abstractizarea și polimorfismul. În această lecție, voi explica moștenirea pe larg, și modul în care ierarhiile de clase îmbunătățesc lizibilitatea și reutilizarea codului.
Deoarece OOP încearcă să imite lumea reală formată din obiecte, moștenirea este și ea un principiu care are ca scop exact ceea ce îi sugerează numele: permite unui obiect (o clasă) să moștenească proprietăți sau comportamente ale unui alt obiect (clasă) mai general. Un exemplu simplu de moștenire din lumea reală este acela că un leu moștenește (face parte din) un grup biologic mai mare, de feline (Felidae). Acest grup moștenește la rândul său dintr-un grup mai larg, de mamifere. Mamiferele fac parte (moștenesc din) un grup și mai larg, de animale, etc.

Spre deosebire de C++, C# nu acceptă moștenirea multiplă, adică nu putem avea o clasă care moștenește atât dintr-o clasă Om, cât și dintr-o clasă Inginer. Acest lucru se datorează dificultății de a decide care metode să fie folosite, dacă atât clase părinte implementează o metodă cu același nume (C++ rezolvă acest lucru într-un mod foarte complicat). Cu toate acestea, C# permite implementarea mai multor interfețe, care pot ajuta la imitarea moștenirii multiple. Voi discuta acest concept într-o lecție viitoare.

În jargonul de programare, clasa de la care moștenim se numește clasă părinte, clasă de bază sau super clasă. Iată un exemplu simplu de clasă de bază:

Avem o proprietate booleană numită Mascul și doi constructori, unul dintre ei apelând pe celălalt, cu o valoare predefinită de Adevărat (True).

Putem avea o altă clasă, numită Pisica, ce moștenește din clasa Felidae:

Primul lucru de remarcat este sintaxa moștenirii: prin plasarea a doua puncte după numele clasei copil, urmate de numele clasei de bază, spunem compilatorului că vrem să o moștenească. În exemplul meu, Pisica : Felidae înseamnă că clasa Pisica moștenește clasa Felidae. Am folosit cuvântul cheie base în constructorul clasei Pisica; acest cuvânt cheie indică faptul că clasa de bază trebuie utilizată și permite accesul la metodele, constructorii și variabilele sale. Folosind base(), putem apela constructorul clasei de bază. Folosind base.Metoda(...) putem apela o metodă a clasei de bază, îi putem oferi parametri și putem utiliza rezultatele acesteia. Folosind base.câmp putem obține valoarea unei variabile membru din clasa de bază sau îi putem atribui o valoare diferită.

După cum vom învăța în lecția despre metode virtuale, în .NET, metodele moștenite din clasa de bază și declarate ca virtuale pot fi suprascrise. Aceasta înseamnă schimbarea implementării acestora; codul sursă original din clasa de bază este ignorat și codul nou îi ia locul.

Ca și în cazul cuvântului cheie this, utilizat în contextul instanțierii claselor, care indică faptul că ne referim la acea instanță particulară a clasei și care poate fi omis dacă nu accesăm un membru al unei clase diferite cu același nume, putem invoca metode din clasa de bază care nu sunt suprascrise fără a utiliza cuvântul cheie base. Utilizarea cuvântului cheie este necesară numai dacă avem o metodă sau o variabilă suprascrisă, cu același nume, în clasa ce moștenește.

Cuvântul cheie base poate fi utilizat în mod explicit pentru claritate. base.method(...) apelează o metodă care trebuie să fie obligatoriu din clasa de bază. Un astfel de cod sursă este mai ușor de citit, deoarece știm unde să căutăm metoda în cauză. Rețineți că în contextul moștenirii, folosirea cuvântului cheie this nu este același lucru cu utilizarea sa în contextul instanțierii. Poate însemna accesarea unei metode din instanță, precum și din clasa de bază.

La moștenirea dintr-o clasă de bază, constructorul clasei ce moștenește trebuie să apeleze la constructorii clasei de bază, pentru a inițializa variabilele sale membre. Nu suntem obligați să facem acest lucru în mod explicit, deoarece dacă nu o facem, compilatorul o va face automat pentru noi, apelând constructorul implicit al clasei de bază. Luați în considerare acest exemplu în care moștenim de la o clasă, dar nu-i apelăm constructorul:

În realitate, compilatorul va transforma codul nostru astfel:

ceea ce va avea ca efect apelarea constructorului prestabilit, fara parametrii, a clasei parinte. În cazul în care acest constructor anume nu e declarat, sau modificatorii săi de acces nu ne permit sa-l accesăm din clasa moștenitoare, trebuie să apelăm în mod explicit un constructor supraîncărcat din clasa de bază, sau vom obține o eroare de compilator: Eroare CS0122 „ClasaParinte.ClasaParinte()” este inaccesibil datorită nivelului său de protecție (Error CS0122 ‘BaseClass.BaseClass()’ is inaccessible due to its protection level).

Dacă o clasă are numai constructori privați, atunci nu poate fi moștenită și acest lucru ar putea indica multe alte lucruri. De exemplu, nimeni (în afară de clasa în sine) nu poate crea instanțe ale unei astfel de clase. De fapt, așa este implementat unul dintre cele mai populare modele de design (singleton).

Apelarea constructorului unei clase de bază se întâmplă înaintea executării corpului constructorului clasei moștenitoare. Aceasta deoarece câmpurile clasei de bază ar trebui inițializate înainte de a începe inițializarea câmpurilor clasei moștenitoare, deoarece acestea ar putea depinde de un câmp al clasei de bază.

În lecția despre modificatorii de acces, am spus că există 4 tipuri principale de modificatori de acces: public, private, protected și internal, cu o combinație suplimentară, protected internal. În contextul moștenirii, acum pot explica câteva dintre conceptele care la vremea respectivă nu prea aveau sens:

  • modificatorul de acces protected definește membrii clasei care nu sunt vizibili pentru utilizatorii acesteia (cei care o instantanează, inițializează și o folosesc), dar sunt vizibili pentru toate clasele moștenitoare (descendenți). Asta înseamnă că acești membri sunt accesibili doar în cadrul clasei în care sunt declarați și în clasele copil care moștenesc din clasa în care sunt declarați. Nu sunt accesibile în instanțele clasei lor.
  • protected internal definește membrii clasei care sunt atât interni (vizibili în întregul assembly), cât și protejați (nu sunt vizibili în afara assembly-ului, dar vizibili claselor care moștenesc clasa în care sunt declarați, chiar și în afara assembly-ului).

Adițional:

  • toți membrii săi publici, protejați și protejați intern (metode, proprietăți, etc.) sunt vizibili clasei moștenitoare.
  • toate metodele sale, proprietățile și variabilele membre care sunt private, nu sunt vizibile clasei moștenitoare.
  • toți membrii ei interni sunt vizibili pentru clasa moștenitoare numai dacă clasa de bază și clasa ce moștenește se află în același assembly (același proiect Visual Studio).

Exemplu:

Dacă încercăm să compilăm exemplele de mai sus, vom primi o eroare de compilator, deoarece variabila mascul este declarată ca fiind privată în clasa de bază și astfel, este vizibilă doar în cadrul acelei clase.

În .NET, există o clasă de bază numită Object, System.Object sau pur și simplu object, care este clasa de bază de la care toate celelalte tipuri moștenesc (de exemplu, chiar și int, bool, string moștenesc din object), direct sau indirect. În această lumină, toate obiectele pot fi considerate instanțe ale acestei clase de bază. Toate clasele care nu moștenesc în mod specific o clasă, moștenesc din object (compilatorul are grijă de asta). Dacă moștenesc de la o anumită clasă, ele moștenesc și din object, indirect, prin ea. În acest fel, orice clasă moștenește direct sau indirect din object, cu toate proprietățile și metodele sale. Acest lucru oferă capacitatea implicită de a putea converti orice tip în object, ceea ce este cunoscut și sub denumirea de upcasting.

Deoarece orice clasă moștenește din object, avem voie să atribuim instanța de clasă pisica, de tip Pisica, unei variabile de tip object, deoarece Pisica moștenește din object. Pe de altă parte, dacă dorim să convertim o variabilă de tip object într-un tip specific, trebuie să utilizăm conversia de tip explicită. Acest lucru se datorează faptului că orice clasă poate fi object, dar nu orice object poate fi o anumită clasă:

De vreme ce variabila de tip obiect obj ar putea sau nu să fie de tip Pisica, trebuie să plasăm conversia în interiorul unui bloc Try … Catch, în cazul în care converia nu reușește.

Tipul object are o metodă numită ToString(), care transformă obiectul într-o reprezentare textuală. Deoarece toate clasele moștenesc din object, aceasta înseamnă că toate clasele moștenesc și dețin metoda ToString(). Cu toate acestea, întrucât un obiect poate conține mai mulți membri (proprietăți, metode, variabile, etc.), compilatorul nu știe cum să convertească un astfel de obiect în text. Din acest motiv, dacă folosim implementarea moștenită implicit a metodei ToString(), compilatorul va afișa pur și simplu numele obiectului:

Metoda ToString() mostenita implicit

Dacă dorim să modificăm acest comportament, trebuie să înlocuim implementarea moștenită, implicită a metodei ToString():

Puteți observa că am declarat o metodă numită ToString() și am folosit de asemenea, cuvântul cheie override, deoarece această metodă suprascrie pe cea implicită a tipului object, de la care moștenește indirect. În acest fel, ori de câte ori vom apela metoda ToString() pe o instanță de tip Pisica, compilatorul va folosi implementarea noastră personalizată a metodei:


Moștenirea: Metoda ToString() suprascrisaDacă doriți să declarați o clasă ce nu poate fi moștenită de nicio altă clasă, trebuie să folosiți cuvântul cheie sealed în declarația clasei, astfel:

În lecția următoare, voi scrie despre metodele virtuale și de ce am folosit cuvântul cheie override în exemplul de mai sus. Până atunci, nu uitați: clasele de bază și moștenirea pot fi folosite pentru a aplica un comportament comun mai multor obiecte înrudite.

Tags: , , , ,

2 Responses to “Moștenirea (inheritance)”

  1. Marius M spune:

    Foarte fain scris articolul !

Leave a Reply



Follow the white rabbit