DAM-M3-UF4. Conceptes d'Orientació a Objectes

De Wiki IES Marianao. Departament Informàtica
La revisió el 12:00, 6 set 2012 per Alex (Discussió | contribucions) (Intefaces)

Dreceres ràpides: navegació, cerca

torna M3 - Programació

Els pilars de la OO

  • Encapsulament i Ocultació. Restricció d'accés a les dades
  • Composició. Relació entre objectes
  • Herència. Relació entre classes
  • Polimorfisme. Determinació del comportament en temps d'execució


Encapsulament. Ocultació

La programació orientada a objectes es basa en el principi de que la classe ha de tenir el control total sobre les variables dels seus objectes, tant per consultar-les com per modificar-les.

Això implica que la classe ha d’establir qualsevol operació que es vulgui fer sobre els seus objectes i les seves variables, impedint que externament es puguin realitzar operacions no definides. (Ocultació). En general ocultant l'accés directe a la informació que conté l'objecte, les dades.

Cada objecte manté el seu estat i el seu comportament particulars, i alhora només mostra a l’exterior la informació necessària per tal que els altres objectes puguin interactuar amb ell. (Encapsulament).

El conjunt d’operacions / atributs que els objectes mostren per tal que s’interactuï amb ells s’anomena Interfase.

D’aquesta manera es protegeix l’objecte de canvis en el seu estat de manera incoherent o inesperada i s’amaga la implementació de les seves operacions, així també es poden fer canvis que mentre no modifiquin la interfase seran transparents a la resta del Sistema.


http://es.scribd.com/doc/52944613/17/Encapsulacion-y-ocultacion-de-datos

Per exemple. La classe home

  • No permet accés a l'atribut edat (private)
  • Qualsevol que interactuï amb objectes "Home",
    • Desconeix l'existència de l'atribut "edat" o el mètode "arrodonirEdat"
    • Només pot usar mètodes de la seva interface. La seva interface són les operacions públiques

És interessant veure que si en algun moment es decideix modificar el càlcul que es realitza per establir l'edat dels objectes "Home", això no afectaria a cap de les entitats externes que en fan ús (Ni aparició d'errors, ni modificar codi, ni recompilar, etc...)

public class Home {

    private int edat;

    public Home(int edat) {
        super();
        this.edat = arrodonirEdat(edat);
    }

    public int getEdat() {
        return edat;
    }

    public void setEdat(int edat) {
        this.edat = arrodonirEdat(edat);
    }
   
    private int arrodonirEdat(int edat) {
        return ((int) edat/10)*10;
    }
}

Composició. Associació de classes

La composició de classes fa referència a usar instàncies de classes (objectes) dins altres objectes.

package dam.m3.uf4;

public class Punt {
    private int x;
    private int y;
   
    public Punt(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
    public int getX() {
        return x;
    }
    public void setX(int x) {
        this.x = x;
    }
    public int getY() {
        return y;
    }
    public void setY(int y) {
        this.y = y;
    }
}
package dam.m3.uf4;

public class Cercle {
    private Punt centre;
    private int radi;
   
    public Punt getCentre() {
        return centre;
    }
    public void setCentre(Punt centre) {
        this.centre = centre;
    }
    public int getRadi() {
        return radi;
    }
    public void setRadi(int radi) {
        this.radi = radi;
    }
}


La representació de la composició (En UML s'anomena associació)

Associacio.png


El tractament d'atributs amb multiplicitat 1 (unaris) d'altres classes és igual que els atributs de tipus primitius.

La principal complicació és la gestió de la inicialització, cal recordar que mentre un objecte no s'instància té un valor null i l'accés a les seves propietats genera error en temps d'execució.


    /* Aquest constructor, deixa la responsabilitat de l'objecte
     * centre a la entitat externa que instància el Cercle.
     *
     * Cercle c1 = new Cercle(new Punt(2,4), 10)
     * Cercle c2 = new Cercle(null, 10)
     */
    public Cercle(Punt centre, int radi) {
        this.centre = centre;
        this.radi = radi;
    }

    /* Aquest constructor pren tota la responsabilitat i obliga
     * a la entitat externa a proporcionar tota la informació necessària.
     *
     * Cercle c1 = new Cercle(new Punt(2,4), 10)
     * Cercle c2 = new Cercle(null, 10)
     */
    public Cercle(int x, int y, int radi) {
        this.centre = new Punt(x,y);
        this.radi = radi;
    }
   
    /* Aquest constructor sempre inicialitza centre = null
     *
     * Cercle c3 = new Cercle(10)
     */
    public Cercle(int radi) {
        this.radi = radi;
    }


Multiplicitat

En general la composició pot implicar una relació 1:molts entre dues classes

Seguint amb l'exemple anterior podem pensar un classes "Quadrat", "Triangle", "Elipse", cadascuna d'aquestes la defineixen varis punts.

public class Triangle {
    private Punt[] vertex;

    // Responsabilitat externa vector mida 3. Arriscat 
    public Triangle(Punt[] vertex) { 
        this.vertex = vertex;
    }
   
    public Triangle() { 
        this.vertex = new Punt[3];
    }

    public Punt[] getVertex() {
        return vertex;
    }

    // index entre 0 i 2
    public void setVertex(Punt vertex, int index) {
        this.vertex[index] = vertex;
    }
}


Aquesta situació es pot generalitzar a un nombre indeterminat en la multiplicitat, per exemple els carrers d'una ciutat, els alumnes d'una classe.


public class Poligon {
    private static final int MAX = 10;
    private int top;
    private Punt[] punts;

    public Poligon() {
        super();
        this.punts = new Punt[MAX];
        this.top = 0;
    }
   
    // Afegir al final. Vigilar top >= this.punts.length
    public void addPunt(Punt punt) {
        this.punts[top] = punt;
        this.top++;
    }
   
    public Punt getPunt(int index) {
        return this.punts[index];
    }
}

Normalment els getters i setters sobre estructures de dades canvien, per exemple

  • setter : Operacions add i remove
  • getter : Afinar per accedir a un element: primer, últim, a través d'un índex

L'exemple anterior té un problema molt evident. El nombre de punts del polígon està limitat.


En aquests tipus d'associacions on la multiplicitat és indeterminada cal usar estructures de dades avançades, que poden tenir una mida variable (Col·leccions: piles, llistes, cues, bosses, mapes, etc...).

Què passa per exemple amb una associació "Alumne-Professor"?

  • Un alumne pot tenir varis professors
  • Un professor pot tenir varis alumnes

Tipus de composició. UML associació, composició i agregació

(Tenir en compte que aquests conceptes són més aviat referents al model, o sigui al problema i als seus requeriments, i no impliquen en el moment de la implementació de les relacions de composició)

En les relacions d'associació els objectes es relacionen entre ells en igualtat. Cadascun pot existir amb independència de l'altre (Exemple "professor - alumne")

Existeixen unes altres relacions de Tot - part

  • Agregació. Els objectes poden existir amb independència, però uns formen part dels altres obligatòriament. Exemple "Compte - Client". Un compte bancari sempre ha d'estar associat a un client. Si el compte desapareix no necessàriament ho ha de fer el Client
  • Composició (No confondre amb el concepte de composició genèric anterior). En aquest cas la relació de pertinença és més forta un l'existència d'un dels objectes està supeditada a l'altre. Exemple "Camí - Tram".


http://www.dcc.uchile.cl/~psalinas/uml/modelo.html

Herència

La Herència consisteix en organitzar es classes en una jerarquia (Pare – Fill). De manera que els descendents poden heretar característiques i comportaments del seus pares.

Es tracta d'una relació generalització - especialització.

En aquesta estructura la classe pare s’anomena superclasse i la classe filla subclasse.

Herencia.png


A l'exemple tant Alumne com Professor hereten els atributs de la Persona, per tant tenen dni, nom i edat. També heretarien mètodes si el pare en tingués.

A més existeixen diferents tipus d’herència en funció del nombre d’ascendents (Pares) que pot tenir una classe.

  • Herència simple : Cada classe només pot tenir una superclasse.
  • Herència múltiple: Una mateixa classe pot tenir vàries superclasses, i heretar de cadascuna d’elles.


Herencia2.png


L’herència també es pot classificar en funció del comportament dels seus descendents

  • Depenent si els objectes de la superclasse s’inclouen o no en una de les subclasses
    • Complete: Tot objecte d’una superclasse pertany a una de les subclasses
    • Incomplete: Un objecte d’una superclasse pot no pertànyer a una de les subclasses
  • Depenent si els objectes de la superclasse poden pertànyer alhora a vàries subclasses
    • Disjoint: Un objecte d’una superclasse només pot pertànyer a una de les subclasses
    • Overlapping: Un objecte d’una superclasse pot pertànyer a vàries de les subclasses

Tota jerarquia és una combinació d’un dels dos tipus

Per exemple

Herencia3.png


El sistema d'herència a Java té les següents característiques

  • Només admet herència simple. Tot i que existeixen mecanismes per simular la herència múltiple (interfaces).
  • Totes les classes deriven de la classe Object. Aquesta herència està implícita i no cal indicar-la.

==> Això implica que qualsevol classe disposa dels mètodes de la classe Object que es poden consultar a l'API http://docs.oracle.com/javase/6/docs/api/

  • Per crear una jerarquia de classes cal indicar-ho en la definició de la classe amb la paraula extends.
  • Els constructors no s'hereten, per crear un objecte ha d'estar clara la classe a la qual pertany sense ambigüitats.


this, super

A vegades es poden produir confusions alhora d’usar atributs o mètodes, així es poden fer servir les següents paraules clau

  • this fa referència a atributs o mètodes de l’objecte actual.
  • super fa referència a atributs o mètodes de l’objecte del pare.

En general serà necessari cridar al constructor de la superclasse dins els constructor de la classe que hereta, per això es pot fer servir la crida a la sentència super(paràmetres), i ha de ser la primera línia del constructor.

Això és recomanable per garantir que la part del comportament de l'objecte responsabilitat del pare està correctament inicialitzada.

public class Clase {
    private int i;

    public Clase() {
        i = 10; // referència l’atribut i
    }

    public Clase( int i ) {
        this.i = i;
        /* s’utilitza this per evitar confusion entre l’atribut d’instància i el paràemtre */
    }
}


public class Clase {
    protected int i;
    public Clase( int i ) {
        this.i = i;
    }    
}

public class SubClase extends {
    private int e;
    public SubClase( int i, int e ) {
        super(i);
        this.e = e;
    }
    public metode( int i ) {
        super.i = i;
    }
}


Classes Abstractes

Una classe abstracte, no està completa, algun dels seus mètodes no està implementat, per tant no es pot instanciar, o sigui no es poden crear objectes d’aquesta.

Estableixen una descripció general de quelcom que s’ha d’acabar de concretar, cal definir alguna subclasse que implementi tots els seus mètodes i la completi. (Obliguen a definir un conjunt mínim de mètodes).

Una classe abstracte s'utilitza quan no és necessari instanciar objectes d'aquest tipus, però en canvi existeixen objectes que tenen propietats i comportaments comuns

Abstract1.png

A l'exemple, el mètode "calcularPerimetre()" de la classe abstracte Figura, només s'implementa a les filles: Triangle i Quadrat

La classe figura no es pot instanciar, no té sentit parlar d'objectes Figura en aquest exemple, què és una Figura? quines propietats té? com es calcula el seu perímetre?


public abstract class Figura {
    private String nom;
   
    public abstract double calcularPerimetre();
   
    public String getNom() {
        return this.nom;
    }

}

public class Cercle extends Figura {
    private int radi;
    private Punt centre;
   
    @Override
    public double calcularPerimetre() {
        // TODO Auto-generated method stub
        return  2 * 3.1416 * radi;
    }
}

public class Quadrat extends Figura {
    private int costat;
   
    @Override
    public double calcularPerimetre() {
        // TODO Auto-generated method stub
        return 4 * costat;
    }
}

Intefaces

Una interface és una classe abstracte on tots els seus mètodes són abstractes.


Les interfaces permeten simular a Java la herència múltiple.

Les interfaces només poden tenir atributs final (constants).

Una interface és una plantilla, defineix com són les interaccions amb els objectes de la classe (Obliga a definir uns mètodes concrets), però no especifica què fan aquestes interaccions (Això corre a càrrec de les classes que implementen la interface).


Interface1.png

Polimorfisme. Sobreescriptura

En la jerarquia d’especialització de les classes, les subclasses poden refinar algun dels mètodes que hereten de la seva superclasse.

Aquests mètodes que comparteixen capçalera (el mateix nom i els mateixos paràmetres), i permeten que s’assigni dinàmicament (en temps d’execució) el comportament de l’objecte, depenent del tipus d’aquest.

No s’ha de confondre el concepte de sobreescriptura amb el de sobrecàrrega de mètode on no intervé la herència.


Herencia4.png

A l'exemple tots els membres de la jerarquia comparteixen un mètode anomenat "horesLectives(): Integer", però cadascun el pot implementar d'una manera diferent.

class Persona {
    private String dni;
    private String nom;
    private int edat;
   
    public int horesLectives() {
        return 0;
    }
}
class Alumne extends Persona {
    public int horesLectives() {
        return 25;
    }
}

class Professor extends Persona  {
    public int horesLectives() {
        return 30;
    }
}

public class Escola {
    public static void main(String[] args) {
        Persona p = new Persona();
        Alumne a = new Alumne();
        Professor r = new Professor();
       
        System.out.println(p.horesLectives());
        System.out.println(a.horesLectives());
        System.out.println(r.horesLectives());
       
        Persona[] persones = new Persona[3];
        persones[0] = p;
        persones[1] = a;
        persones[2] = r;
       
        System.out.println(persones[0].horesLectives());    // 0
        System.out.println(persones[1].horesLectives());    // 25
        System.out.println(persones[2].horesLectives());    // 30
       
    }
}

Càsting

Una altra de les conseqüències de l’herència és la determinació del tipus dels objectes d’una determinada classe.

Un objecte pertany alhora a les classes superiors de la jerarquia, i es pot comportar com a tal.


Upcasting / Late binding / sobreescriptura. Operador “instance of”

class Pare {
    public void prova() { System.out.println("Pare"); }
    public void pare_prova() { System.out.println("Pare prova"); }
}

class Fill extends Pare {
    public void prova() { System.out.println("Fill"); }
    public void fill_prova() { System.out.println("Fill prova"); }
}

class AltreFill extends Pare {
    public void prova() { System.out.println("AltreFill"); }
}


public class Herencia {
    public static void main(String[] args) {
        Fill f = new Fill();
        if (f instanceof Pare) System.out.println("Pare"); //Aquest
        else System.out.println("No Pare");

        if (f instanceof Fill) System.out.println("Fill"); //Aquest
        else System.out.println("No Fill");

        Pare p = new Pare();
        if (p instanceof Pare) System.out.println("Pare"); //Aquest
        else System.out.println("No Pare");
        if (p instanceof Fill) System.out.println("Fill");
        else System.out.println("No Fill"); //Aquest

        Pare pf1 = new Fill();     // upcasting
        if (pf1 instanceof Pare) System.out.println("Pare"); //Aquest
        else System.out.println("No Pare");
        if (pf1 instanceof Fill) System.out.println("Fill"); //Aquest
        else System.out.println("No Fill");

        p = (Pare) f; // upcasting
        p.prova(); // dynamic binding o late binding. Mostra "Fill"
        /* Sempre executarà el fill pq en temps de compilació p es Pare
        però en temps d'execució p és referència a Fill*/
        p.pare_prova();
        //p.fill_prova(); Error p no té accés mètodes de fill
        f.fill_prova();
        f.pare_prova(); // Objecte tipus fill té accés a pare també

        //Fill fp = (Fill) new Pare(); downcasting
        //Fill fp = new Pare(); // Error
        //fp.prova(); // Error


        /* Exemple Crida polimorfisme */
        Fill f_1 = new Fill();
        AltreFill f_2 = new AltreFill();

        exemple ((Pare) f_1);
        exemple (f_2);

        Pare p2 = new Pare();
        //exemple2((Fill) p2); // Error p2 no se fill
   
        p2 = f_1;
        exemple2((Fill) p2); // Ok.
    }

    public static void exemple (Pare p) {
        p.prova();
    }

    public static void exemple2 (Fill f) {
        f.prova();
    }
}

Convencions / Bones pràctiques

S’utilitzaran les següents convencions:

  • Els atributs sense herència es declararan private
  • Els atribut que seran heretats i accessibles en alguna subclasse es declararan protected
  • Hi haurà un mètode constructor que inicialitzarà tots els atributs d’instància.
  • Hi haurà un mètode consultor públic per cada atribut amb el nom getNomAtribut()
  • Hi haurà un mètode modificador públic per cada atribut amb el nom setNomAtribut(tipus NomAtribut)
  • Els mètodes que no volem fer servir fora de la classe es declararan private