Tutoriale C# – Programarea orientată pe obiecte – 3 –

Articolul 6 din 7 din seria C#

În cadrul acestui tutorial vom discuta despre moștenire, polimorfism, interfețe și structuri. Sunt elemente definitorii pentru limbajul C#, dar și pentru programarea orientată pe obiect, în general.

1.Moștenirea

Acest concept, în contextul programării orientate pe obiecte, se referă la capacitatea unui limbaj de programare ce suportă paradima menționată anterior, de a extinde un tip de date definit de utilizator (clasă), în scopul de a forma un nou tip de date care înglobează funcționalitatea tipului inițial.

Spre exemplu: avem o aplicație cu interfață grafică. Vrem să plasăm un buton pe această interfață grafică, dar nu orice buton, ci unul adaptat la preferințele noastre. Limbajul C# definește o clasă care se numește button și care are funcționalitățile unui buton obișnuit. Dacă preferințele nostre sunt ca butonul să fie albastru, cu un contur verde închis, pe care să scrie „Press here!”, atunci va trebui să definim o clasă, să-i zicem „My button” care să moștenească clasa button, cea definită deja de limbaj. Ce înseamnă să moștenească? Adică să aibă toate calitățile unui buton default, definit de cei care au creat limbajul, dar, pe lângă acestea, să dispună și de preferințele noastre!  Cu alte cuvinte, moștenirea realizează o relație de specializare a clasei. Butonul implicit, cu toate proprietățile sale, se adaptează la preferințele noastre, deci se specializează.

1.1.  Operatorul :

Implementarea moștenirii se realizează prin intermediul operatorului ”:” (două puncte), astfel:

class Baza {         
    //Metode, câmpuri clasa Baza
}

clasa Derivata : Baza {        
    // Metode, campuri clasa Derivata
}

Deci imediat după specificarea numelui clasei, se pune ”: nume_clasă_moștenită”.

Clasa moștenită se numește clasă de bază iar clasa care moștenește se numește clasă derivată. După cum am spus mai sus, clasa derivată are ca membrii atât membrii definiți în cadrul ei, cât și membrii clasei de bază. Acest aspect poartă numele de extinderea clasei de bază.

Ca o observație, putem remarca faptul că orice clasă în limbajul C# este o clasă derivată din clasa object, care este rădăcina tuturor ierarhiilor de clase din BCL.

1.2. Exemplu 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace code_it.ro {
   
   class A {
        public void MetodaA() {
            Console.WriteLine("Ne aflam in metoda clasei A");
        }
    }

    class B : A {
        public void MetodaB() {
            Console.WriteLine("Ne aflam in metoda clasei B");
        }
    }
    
    class Program {
        static void Main(string[] args) {
            B obiect = new B();

            obiect.MetodaA();
            obiect.MetodaB();

            Console.ReadKey();
        }
    }
}

La rularea codului, se va afișa:

Ne aflam in metoda clasei A
Ne aflam in metoda clasei B

Remarcăm astfel că un obiect al unei clase derivate, poate accesa membrii clasei de bază. Acest lucru nu este însă stabil. Programatorul poate decide ce nivel de acces poate avea un obiect al clasei derivate la membrii clasi de bază, prin utilizarea specificatorilor de acces. Spre exemplu, dacă metoda din clasa A avea private în loc de public, obiectul clasei derivate (cel din Main() ) nu ar fi putut accesa metoda.

1.3. Constructori

Am văzut cum se implementează moștenirea în C#, acum se pune problema în felul următor: dacă ambele clase, atât cea de bază, cât și cea derivată, implementează constructori impliciți, care din ei se apelează și în ce ordine? Răspunsul îl poate oferi următorul exemplu:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace code_it.ro {
    
    class A {
        public A() {
            Console.WriteLine("Constructor clasa de baza");
        }
    }

    class B : A {
        public B() {
            Console.WriteLine("Constructor clasa derivata");
        }
    }
    
    class Program {
        static void Main(string[] args) {
            B obiect = new B();
            Console.ReadKey();
        }
    }
}

Se va afișa:

Constructorul clasa de baza
Constructorul clasa derivata

Deci remarcăm: dacă se implementează constructori impliciți pentru ambele clase, se apelează prima dată constructorul clasei de bază, și apoi cel al clasei derivate.

Dacă, în schimb, clasa de bază împlementează un constructor care acceptă parametrii? În acest caz, va trebui să furnizăm parametrii corespunzători apelării constructorului clasei de bază.

Sintaxa este următoarea:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace code_it.ro {
    
    class A {
        public A(int x) {
            Console.WriteLine("A fost transmis in constructor {0}", x);
        }
    }

    class B : A {
        public B(int a, int b) : base(b) {
            Console.WriteLine("S-au transmis {0} si {1} iar in constructorul clasei de baza s-a transmis {2} ", a, b, b);
        }
    }
    
    class Program {
        static void Main(string[] args) {
            B obiect = new B(3, 4);
            Console.ReadKey();
        }
    }
}

Deci acel base este un keyword al limbajului, care are rolul de a apela constructorul bazei parametrizat.

1.4. Membrii ascunși

Când apare mecanismul de moștenire, există situații în care membrii clasei de bază au același nume cu cei ai clasei derivate. În această situație, accesarea membrilor clasei de bază este restricționată, întrucât sintaxa obiect.membru are efect doar asupra membrilor definiți în clasa derivată (cei din clasa de bază având același nume cu aceștia). Pentru a avea acces la membrii clasei de bază, se procedează astfel: se pune new în fața numelui membrilor din clasa derivată iar membrii clasei de bază se apelează apoi cu base.membru.

Să exemplificăm:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace code_it.ro {

    class A {
        public void MetodaA() {
            Console.WriteLine("Suntem in metoda clasei A");
        }
    }

    class B : A {
        new public void MetodaA() {
            base.MetodaA();
            Console.WriteLine("Suntem in metoda clasei B");
        }
    }

    class Program {
        static void Main(string[] args) {
            B obiect = new B();
            obiect.MetodaA();
            Console.ReadKey();
        }
    }
}

În principiu, acestea sunt informațiile de care un dezvoltator C# de nivel mediu ar trebui să dispună pentru a putea realiza aplicații funcționale.

2. Interfețe

O interfață este un tip de date built-in, care memorează un set de anteturi (prototipuri) ale metodelor unei clase. In definiția unei interfețe se listează doar anteturile metodelor, implementarea acestora find asigurată în cadrul clasei. Clasa respectivă moștenește intefața în cauză, deci prin acest lucru, clasa trebuie să se preocupe de implementarea metodelor respective, lucru care devine, astfel, obligatoriu.

Sintaxa este următoarea:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace code_it.ro{

    interface IPrintable {
        public void Print(int x);
    }

    class B : IPrintable {
        public void Print(int x) {
            Console.WriteLine("{0}", x);
        }
    }

    class Program {
        static void Main(string[] args) {
            B obiect = new B();
            obiect.Print(3);
        
            Console.ReadKey();
        }
    }
}

O clasă poate moșteni oricât de multe interfețe. Rolul interfețelor este de a grupa anteturile metodelor clasei în scopuri de lizibilitate și compactare a codului, având o aplicabilitate și o utilitate sporită atunci când vine vorba de aplicații complexe.

3. Polimorfismul

Polimorfismul este capacitatea unui limbaj orientat pe obiect de a executa acțiuni diferite, cu puncte de ințiere diferite, însă cu aceeași entitate logică modelatoare. Mai exact, capacitatea unui obiect al clasei de bază de a se referi la obiecte ale clasei derivate, în funcție de referința care îi este atribuită.

Se pune astfel, problema: avem o clasă, derivată, care moștenește o clasă de bază. Dacă noi avem un obiect al clasei de bază, cum accesăm un membru al clasei derivate? Se procedează în felul următor: în clasa de bază, se pune cuvântul cheie virtual în fața antetului funcției. În clasa derivată se pune cuvântul cheie override în fața antetului funcției. Semnătura acesteia (numele, tipul de retur, lista parametrilor formali) trebuie să fie identică cu cea din clasa de bază!). Vom vedea ce se intâmplă la apelare:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace code_it.ro {
    
    class Baza {
        virtual public void Metoda() {
            Console.WriteLine("Metoda clasa de baza");
        }
    }

    class Derivata : Baza {
        override public void Metoda() {
            Console.WriteLine("Metoda clasa derivata");
        }
    }

    class Program {
        static void Main(string[] args) {
            Baza obiect = new Baza();
            obiect.Metoda();    
            Console.ReadKey();
        }
    }
   
}

Se afișază, exact cum ne-am aștepta: ”Metoda clasa de baza”. Dacă însă schimbăm Main()-ul cu următorul:

static void Main(string[] args) {
	Baza obiect = new Baza();
	obiect = new Derivata();
	obiect.Metoda();
            
    Console.ReadKey();
}

Se va afișa: ”Metoda clasa derivata”. Observăm astfel că un obiect al clasei de bază, poate accesa o metodă a clasei derivate, în urma stabilirii obiectului către care trebuie să refere, și anume către un obiect al clasei Derivata.

Utilitatea polimorfismului se dovedește, asemenea moștenirii, în cadrul aplicațiilor de complexitate medie – mare.

4. Structurile

Structurile sunt entități logice similare claselor. Se definesc cu ajutorul cuvântului cheie struct și practic au aceeași funcționalitate de care dispun clasele, în sensul că pot defini metode, pot declara câmpuri, constructori, proprietăți, indexatori, operatori, tipuri imbricate.

Evident, apare întrebarea: de ce clase și nu structuri? Răspunsul ne permite să afirmăm că există totuși niște diferențe între cele două concepte, și anume: clasele sunt tipuri referință, deci se alocă pe heap, în timp ce structurile sunt tipuri valoare, deci se alocă pe stivă. Acest lucru constituie un avantaj pentru structuri din punct de vedere al performanței, întrucât stiva se accesează mai repede decât memoria heap. Problema este însă că stiva are un nivel limitat de memorie, mult mai mic decât cel al memoriei heap, de aceea clasele dispun de o stabilitate crescută și sunt permanent în afara unui overflow iminent de memorie.

However, nu trebuie să neglijăm utilitatea structurilor. Ele sunt foarte utile atunci când vine vorba de obiecte de dimensiuni mici:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace code_it.ro {

    struct complex {
        public double real, imaginar, modul;
    }
    
    class Program {
        static void Main(string[] args) {
            complex z;
            z.real = 4;
            z.imaginar = 3;
            z.modul = Math.Sqrt(z.real * z.real + z.imaginar * z.imaginar);
            Console.WriteLine("{0}", z.modul);
           
            Console.ReadKey();
        }
    }
}

Se va afișa 5. Observăm că structurile trebuie să aibă o utilitate rapidă, exact așa cum se și accesează din memorie.

Cu acest tutorial închidem principiile programării orientate pe obiect, care se regăsesc în limbajul C#. Vom trece la un nou nivel, și anume la tipuri specializate ale limbajului C#, care îl individualizează față de alte limbaje similare, și anume delegări, evenimente, generice.

Respectfully, Ioniță Cosmin.

Navigatie serie<< Tutoriale C# – Programarea orientată pe obiecte – 2 –Tutoriale C# – Delegări, evenimente, generice >>

2 comentarii la „Tutoriale C# – Programarea orientată pe obiecte – 3 –”

  1. Foarte frumos explicat…iti multumesc pentru toate informatiile.
    Ai reusit sa explici, ceea ce multi profesori universitari incerca sa explice print-o multime de cursuri…eu am ajuns
    la concluzia ca nici ei (unii) nu stapanesc materia …din pacate..

    Răspunde

Lasă un comentariu