Reutilizarea claselor

“The object has three properties, which makes it a simple, yet powerful model building block. It has state so it can model memory. It has behavior, so that it can model dynamic processes. And it is encapsulated, so that it can hide complexity.”

—Trygve Reenskaug

Sistemele software sunt similare sistemelor reale. La fel cum un sistem real este format din părţi componente care interacţionează între ele, la fel sistemele software sunt formate din părţi componente care interacţionează între ele.

O aplicaţi software orientată pe obiecte este compusă din obiecte care interacţionează în scopul indeplinirii unui set de funcţionalităţi pentru care aplicaţia a fost gândită. Fiecare obiect îndeplineşte unul sau mai multe roluri în cadrul aplicaţie.

In procesul de analiză al obiectelor şi al rolurilor pe care acestea le joacă în cadrul unei aplicaţii este util să ne raportăm la următoarele posibile roluri pe care un obiect le-ar putea îndeplini:

  • stocare informaţii – memorează informaţii şi oferă acces la el
  • structură – menţine relaţii între obiecte şi oferă informaţii despre aceste legături
  • prestator de servicii – efectuează activiăţi
  • coordonator – reacţionează la evenimente ce apar în cadrul aplicaţiei prin delegarea de responsabilităţi sau de activităţi către alte obiecte
  • interfaţă – transformă sau transferă informaţii şi mesaje între diferite părţi subcomponente ale systemului

Pot exista desigur şi situaţii în care un obiect să joace mai multe roluri în acelaşi timp.

Limbajele orientate pe obiecte oferă programatorului două mecanisme prin care poate crea colecţii de obiecte care interacţionează în scopul îndeplinirii unui set de funcţionalităţi: compoziţia şi moştenirea.

Compozitia

Compoziţia este procesul de construire a unei clase prin adăugarea în cadrul acesteia a unuia sau mai multor atribute de tip referinta (obiecte).

class TV{
            RemoteControl rc;
            Display d;
            ElectronicBoard eb;
            booleab state
            int size;
            ....
            void turnOn(){...}
            void turnOFF(){...}
}
class Persoana{
            String nume;
            Adresa adresa;
}

În exemplele anterioare clasele TV şi Persoana conţin referinţe către alte obiecte şi exemplifică conceptul de compoziţie.

Mostenirea

Moştenirea este procesul de construirea a unei clase plecând de la o clasă existentă şi adăugând noi caracteristici, sau modificând caracteristici existente. Clasa de la care se moştenesc caracteristicile se mai numeşte superclasă iar clasa care moşteneşte caracteristicile se mai numeşte subclasă.

Se foloseşte în mod comun termenul de specializare care defineşte noţiunea de moştenirea. O subclasă specializează o superclasă deoarece subclasa prin adăugarea de noi roluri devine mai puţin generală.

Pentru a specifica faptul că o clasă moşteneşte o altă clasă se utilizează cuvântul cheie extends.

public class Persoana {
            String nume;
            Adresa adr;
            void setAdresa(Adresa a){adr = a}
            void afiseaza(){
                        System.out.println("Nume="+nume);
            }          
}
 
class Angajat extends Persoana{
            int venit;
            void afiseaza(){
                        super.afiseaza();
                        System.out.println("Venit="+venit);
            }
            int calculeazaImpozit(){
                        return venit/2;
            }
}
 
class Adresa{
            String strada;
            int nr;
}

Clasa Persoana modelează un concept din lumea reală, şi anume o persoană. După cum se observă s-a decis că această clasă să conţină două atribute. Atribut nume este de tip String (clasa String face parte din librăriile standard java şi este folosită pentru manipularea de şiruri de caractere) şi permite memorarea numelui persoanei. Atributul adr este de tip Adresa (clasa Adresa este definită în cadrul aceluiaşi program) şi permite memorarea adresei unei persoane. Metoda afisare() afişează numelui persoane. în continuare s-a construit o nouă clasă cu numele Angajat. Dar cum un angajat este o persoană dar are in plus o serie de caracteristici suplimentare s-a decis construirea unei clase Angajat care să moştenească caracteristicile clasei Persoana şi care să definească acele atribute pe care le are în plus un Angajat.

Clasa Angajat moşteneşte metodele afiseaza şi calculeazaImpozit şi atributul nume. Obiectele de tip angajat vor conţine aşadar şi caracteristicile definite în cadrul clasei Persoana pe lângă cele definite în cadrul clasei Angajat.

Există posibilitatea ca în cadrul unei subclase să modificăm comportamentul unei metode moştenite dintr-o clasă de bază după cum se vede în cazul funcţie afisare. Operaţia prin care în cadrul unei clase derivate (subclase) se rescrie corpul unei funcţii dintr-o clasă de bază (superclase) se numeşte suprascriere (eng. overwirte).

Cuvântul cheie super este folosit în cadrul exemplului anterior pentru a forţa apelarea unei metode din cadrul unei clase de bază (vezi exemplul anterior).

Aplicaţia prezentată mai sus ilustrează folosirea în cadrul aceleiaşi aplicaţii a conceptelor de compoziţie şi moştenire. În general în orice aplicaţie java regăsim ambele concepte utilizate. Este important de reţinut faptul că orice clasă java moşteneşte în mod implicit (fără a fi nevoie de cuvântul cheie special extend) caracteristicile clasei Object din pachetul standard java.lang.

Initializarea claselor de baza si a claselor derivate

Iniţializarea atributelor claselor de bază şi claselor derivate trebuie realizată prin intermediul constructorilor. În continuare este prezentată o variantă îmbunătăţită a exemplului anterior în cadrul căreia sunt adăugaţi constructorii pentru iniţializarea atributelor.

public class Persoana {
            private String nume;
            private Adresa adr;
 
            public Persoana(String n, Adresa a){nume=n;adr=a;}
            public void afiseaza(){
                        System.out.println("Nume="+nume);
            }
            public static void main(String[] args){
                        Adresa a = new Adresa("stradaX",15);
                        Persoana p1 = new Persoana("Alin",a);
Angajat p2 = new Angajat("Jon", new Adresa("Strada mare",142),2000);
                        p1.afiseaza();
                        p2.afiseaza();
                        System.out.println("Impozit persoana "+p2+"="+ p2.calculeazaImpozit());
            }          
}
 
class Angajat extends Persoana{
            private int venit;
            Angajat(String n, Adresa a, int v){
                        super(n,a);
                        venit = v;
            }
            public void afiseaza(){
                        super.afiseaza();
                        System.out.println("Venit="+venit);
            }
            public int calculeazaImpozit(){
                        return venit/2;
            }
}
 
class Adresa{
            String strada;
            int nr;
            Adresa(String s, int n){strada = s;nr=n;}
}

Deşi în cadrul clasei derivate nu sunt definite atributele nume şi adr acestea trebuie inţializate întrucât sunt moştenite din cadrul clasei Persoana. Dar, întrucât aceste două atribute sunt declarate private în cadrul clasei de bază, în cadrul clasei derivate nu pot fi accesate direct pentru a fi iniţializate, astfel încât se observă că prima instrucţiune în cadrul constructorului clasei derivate este super(n,a) care asigură apelare constructorului din clasa de bază pentru iniţializarea celor două atribute. Se observă aşadar a două utilitate a cuvântului cheie super.

Dacă cele două atributele ar fi fost declarate cu specificatorul de acces public, protected sau acces de pachet (fără nici un specificator), s-ar fi putut accesa aceste două atribute din cadrul constructorului clasei derivate (desi acest lucru nu este recomandat):

Angajat(String n, Adresa a, int v){
                        nume = n
                        adr = a;
                        venit = v;
            }

În momentul construirii claselor trebuie să se asigure iniţializarea corectă atributelor acestora prin definirea constructorilor corespunzători. În cadrul constructorilor claselor derivate trebuie să se asigure apelarea unui dintre constructorii din clasele de bază – în caz contrar compilatorul va atenţiona programatorul.

Agregare vs Mostenire

Cand alegem mostenirea sau agregarea ?

Diferenta dintre mostenire si agregare este de fapt diferenta dintre cele 2 tipuri de relatii majore prezente intre obiectele unei aplicatii :

  • is a (este un tip de) - indica faptul ca o clasa este derivata dintr-o clasa de baza (intuitiv, daca avem o clasa Animal si o clasa Caine, atunci ar fi normal sa avem Caine derivat din Animal, cu alte cuvinte Caine is a Animal)
  • has a (are un/o) - indica faptul ca o clasa-container are o clasa continuta in ea (intuitiv, daca avem o clasa Masina si o clasa Motor, atunci ar fi normal sa avem Motor referit in cadrul Masina, cu alte cuvinte Masina has a Motor)

Conversia de tip

Upcasting Upcasting-ul este procesul de conversie a unei referinte la o clasa derivata catre o clasa de baza a acestei clase. Operatie de upcasting este automata si nu trebuie declarata in mod explicit.

class Employee{
	void doWork(){
		System.out.println("Employee do his job.");
	}
}
 
class Manager extends Employee{
 
	void report(){
		System.out.println("Manager is reporting project status.");
	}
	void doWork(){
		System.out.println("Manager coordinate job.");
	}
 
}
 
 
public class TestCasting {
	public static void main(String[] args) {		
		Employee e = new Manager();
		e.doWork();
	}
}

In exemplul de mai sus am declarat o variabila referinta e, care este initializata cu un obiect de tip Manager. Prin intermeidul referintei e putem accesa obiectul de tip Manager, dar vom putea accesa doar acele metode declarate in cadrul clasei Employee (nu vom avea acces la metoda report() ).

Downcasting

Pentru a putea accesa caracteristicile clasei Manager (clasa derivata a clasei Employee), va trebui sa facem o operatie de downcasting.

Downcastingul este conversia inversa celei de downcasting si este o conversie explicita de tip mergand in jos, de la o clasa de baza la una derivata.

Com modifica clasa TestCasting de mai sus in felul urmator:

public class TestCasting {
 
public static void main(String[] args) {		
		Employee alin = new Manager();
		alin.doWork(); 
 
		((Manager)alin).report(); //1.
 
		Employee dan = new Employee(); //2.
 
		((Manager)dan).report(); //3.
 
	}
}

Pe linia 1 am facut o operatie de downcasting prin care am fortat tipul variabilei alin la Manager. In urma fortarii de tip avem acces la metoda report() ce face parte din clasa Manager.

Pe linia 3 facem din nou o conversie de tip prin care fortam convertirea variabilei dan la tipul Manager. Din pacate in momentul rularii executia liniei 3 va genera eroarea:

Exception in thread "main" java.lang.ClassCastException: Employee cannot be cast to Manager
	at TestUpcasting.main(TestCasting.java:28)

Motivul acestei erori este dat de faptul ca variabila dan refera catre on obiect de tip Employee.

Implicatii ale mostenirii

Specificatorul de acces protected specifica faptul ca atributul sau metoda declarata protected poate fi accesata din clasa de definitie si in clasele derivate.

Specificatorul de access private specifica faptul ca atributul sau metoda declarata private poate fi accesata doar din clasa de definitie nu si in clasele derivate.

Declararea unei clase final (cuvantul cheie final in fata cuvantului cheie class) va bloca clasa respectiva in a mai fi derivata (mostenita) de alte clase.

Declararea unei metode final (cuvantul cheie final in fata numelui metodei) va bloca metoda respectiva in a mai fi suprascrisa in clasele derivate.

Exemplu mostenire vehicul

public class Vehicul {
    String nume;
 
    Vehicul(String nume){
        this.nume = nume;
    }
 
    void afiseaza(){
        System.out.println("Vehicul nume: "+nume);
    }
 
    public static void main(String args[]){
        Vehicul v1 = new Vehicul("A");
        Masina m1 = new Masina("B",1000);
        Vehicul m2 = new Masina("C",2000);
        m1.afiseaza();
        m2.afiseaza();
        v1.afiseaza();
    }
}
 
class Masina extends Vehicul{
    int cc;
 
    Masina(String nume, int cc){
        super(nume);
        this.cc = cc;
    }
 
    void afiseaza(){
        super.afiseaza();
        System.out.println("Vehicul cc: "+cc);
    }
}

Exemplu mostenire persoana 1

public class TestMostenire1 {
 
    public static void main(String[] args) {
        Persoana p1 = new Persoana();
        Persoana p2 = new Persoana("XYZ", 22);
        p1.afisare();
        p2.afisare();
 
        Student s1 = new Student("UTCN", "ABC", 23);
        s1.afisare();
 
        Persoana p3 = new Student("FGD", "CRL", 9);
        p3.afisare();
 
        Student s2 = new Student("UTCN", "ABC", 23);
        s1.afisare();
 
        Persoana x = s2;  //conversie de tip implicita
 
        Persoana p4 = new Student("FGD", "CRL", 9);
        p3.afisare();
 
        Student y = (Student) p4; //conversie de tip expilicita
 
        y.afiseazaSituatie();
 
        Persoana p5 = new Persoana();
 
        if (p5 instanceof Student) {
            Student z = (Student) p5;
            z.afiseazaSituatie();
        }
 
    }
}
 
class Persoana {
 
    String nume;
    int varsta;
 
    Persoana() {
        this.nume = "Fara Nume";
        this.varsta = -1;
    }
 
    Persoana(String nume, int varsta) {
        this.nume = nume;
        this.varsta = varsta;
    }
 
    void afisare() {
        System.out.println("Persoana " + nume + ":" + varsta);
    }
 
}
 
class Student extends Persoana {
 
    String uni;
 
    public Student(String uni, String nume, int varsta) {
        super(nume, varsta);
        this.uni = uni;
    }
 
    void afisare() {
        System.out.println("Student " + nume + ":" + varsta);
        System.out.println("Universitatea " + uni);
    }
 
    void afiseazaSituatie() {
        System.out.println("Afiseaza situatie student....");
    }
}

Exemplu mostenire persoana 2

public class Persoana {
    String nume;
    int varsta;  
 
    Persoana(){
        System.out.println("Constructor default persoana.");
    }
 
    Persoana(String nume, int varsta){
        System.out.println("Constructor cu argumente persoana.");
        this.nume = nume;
        this.varsta = varsta;
    }
 
    void afiseazaDetalii(){
        System.out.println(nume+":"+varsta);
    }
 
 
}
 
class Student extends Persoana {
    String university;
 
    Student(String nume, int varsta, String uni){
        super(nume,varsta);
        this.university = uni;
        System.out.println("Constructor student.");
    }
 
    void afiseazaDetalii(){
        super.afiseazaDetalii();
        System.out.println(university);
    }
 
 
}
 
class ErasmusStudent extends Persoana {
    String university;
    String remoteUniversity;
 
    ErasmusStudent(String nume, int varsta, String uni, String runi){
        super(nume,varsta);
        this.university = uni;
        this.remoteUniversity = runi;
        System.out.println("Constructor student.");
    }
 
    void afiseazaDetalii(){
        super.afiseazaDetalii();
        System.out.println(university);
        System.out.println(remoteUniversity);      
    }
 
}
 
public class Test {
 
 
   public static void main(String[] args){
       Persoana p = new Persoana("A", 1);
       Persoana s = new Student("B", 2, "UTCN");
       Persoana e = new ErasmusStudent("C", 3, "UTCN", "UBB");
       p.afiseazaDetalii();
       s.afiseazaDetalii();
       e.afiseazaDetalii();
 
   } 
}

Exemplu mostenire electronic device

public class ElectronicDevice {
    private boolean powered;
 
    public ElectronicDevice() {
        powered = false;
    }
 
 
    protected void turnOn(){
        System.out.println("Device turned ON");
        powered = true;
    }
 
    protected void turnOff(){
        System.out.println("Device turned OFF");
        powered = false;
    }
 
 
}
 
public class TV extends ElectronicDevice{
     int channel;
 
    public TV() {
    }
 
    void channelUp(){
        if(channel<=10){
            channel++;
            out.println("Channel up "+channel);
        }
    }
 
    void channelDown(){
        if(channel>=2){
            channel--;
            out.println("Channel down "+channel);
        }
    }
}
 
public class TestElectronicDevices {
    public static void main(String[] args){
        ElectronicDevice e1 = new ElectronicDevice();
        ElectronicDevice e2 = new TV();
 
        e1.turnOn();
        e1.turnOff();
 
        e2.turnOn();
        e2.turnOff();
 
        TV e3 = new TV();
        e3.turnOn();
        e3.channelUp();
 
        TV t1 = (TV)e2; //conversie de tip explicita
        t1.channelUp();
 
    }
}