5. Relaciones entre clases. Herencia.

1.      INTRODUCCIÓN.

Las clases, al igual que los objetos, no existen de modo aislado. La Orientación a Objetos (POO) intenta modelar aplicaciones del mundo real tan fielmente como sea posible y por lo tanto debe reflejar estas relaciones entre clases y objetos.

Según G. Booch [Booch, 94] existen tres clases básicas de relaciones entre los objetos:

  1.       Agregación / Composición (todo-parte//tiene-un // parte-de).

Esta relación se presenta entre una clase TODO y una clase PARTE que es componente de TODO. La implementación de este tipo de relación se consigue definiendo como atributo un objeto de la otra clase que es parte-de.

Los objetos de la clase TODO son objetos contenedores. Un objeto contenedor es aquel que contiene otros objetos.

En la agregación, las clases contienen objetos, y no otras clases.

Tipos de agregación

El contenedor contiene el objeto en sí. Cuando creamos un objeto contenedor, se crean también automáticamente los contenidos.

Se tienen punteros a objetos. No hay un acoplamiento fuerte. Los objetos se crean y se destruyen dinámicamente.

La relación de agregación/composición establece jerarquías de clases por grado de composición.

2. Asociación

Especifica una relación semántica entre objetos no relacionados. Este tipo de relaciones permiten crear asociaciones  que capturen los participantes en una relación semántica.

Es una relación entre clases. Implica una dependencia semántica. Son relaciones del tipo «pertenece_a» o «está_asociado_con». Se da cuando una clase usa a otra clase para realizar algo.

Multiplicidad de la Relación

Indica el número de instancias de una clase que se asocian con las instancias de la otra clase.

Tipos de multiplicidad:

La implementación en código suele conseguirse mediante un puntero desde la clase a la clase asociada. Un puntero (referencia), por cada instancia de esa relación.

2. Generalización / Especialización: HERENCIA.

De todas las relaciones posibles entre las distintas clases y objetos, hay que destacar por su importancia en O.O  la relación de herencia.  La relación de herencia es una relación entre clases que comparten su estructura y el comportamiento.

Se denomina herencia simple:    Cuando una clase comparte la estructura y comportamiento de una sola clase.

Se denomina herencia múltiple:     Cuando una clase comparte la estructura y comportamiento de varias clases.

Para que un lenguaje de programación pueda ser considerado orientado a objetos, debe implementar el mecanismo de herencia.

La relación de herencia entre dos clases es una relación binaria entre dos clases que nos dice:

La clase  subclase o derivada  hereda de la clase base  o  superclase.

La clase superior de la jerarquía, en cada relación, se denomina superclase, clase base ó clase padre y, la clase que hereda de la superclase, se denomina subclase, clase derivada ó clase hija.

La herencia es la transmisión de la vista pública (métodos  públicos) y la vista privada (atributos y métodos privados), de una clase a otra.

La herencia es un mecanismo fundamental en la construcción de clases, necesario para reutilizar código. Mediante la herencia podemos crear clases nuevas a partir de otras que ya existen, sin necesidad de reescribir todo el código: mecanismo de reutilización de código.

Mediante la herencia podemos organizar las distintas clases en estructuras jerárquicas.

Cada clase en la jerarquía  establece un dominio de elementos incluido en el dominio de la superclase  de la cual hereda.

Esta jerarquía de clasificación es  subjetiva, dependiendo de las intenciones con las que se pretenda trabajar.

Es muy difícil establecer una relación perfecta y surgen elementos que no se acomodan en ninguna categoría.

Una relación de herencia entre dos clases tiene que cumplir al menos  dos reglas:

a.Especialización.

En la subclase se debe producir una especialización de la superclase; es decir, la subclase  debe incluir todas las características de la superclase  y otras.

b. Responder afirmativamente a la pregunta     

¿Todo objeto del dominio de la subclase  es un objeto del dominio de la superclase?

La relación de herencia es transitiva:

Si C hereda-de A y  F  hereda-de C  entonces F hereda-de A.

La herencia puede ser representada mediante un grafo dirigido en el que los nodos son las clases y los arcos las relaciones de herencia que parten de la subclase  a la superclase.

3. Relación de herencia. Subclase o Clase derivada.

En una relacion de herencia entre dos clases BASE y DERIVADA  se produce la transmisión de atributos y métodos definidos en la clase PADRE hacia la clase HIJA:

En cada relación de herencia se transmite  la vista publica y privada de la superclase en la subclase.

Todos los atributos y métodos de la superclase son atributos y métodos de la subclase.

Pero para que esta relación pueda considerarse de HERENCIA debe producirse algo más: Una ESPECIALIZACIÓN de la clase  BASE en la clase DERIVADA

Esta especialización puede llevarse a cabo por diferentes vías que se enumeran del 1 al 4:

      1. Añadir métodos a los heredados de la superclase.
      2. Añadir atributos y los métodos que los manejan a los heredados de la superclase.
      3. Redefinir métodos heredados de la superclase.
      4. Nunca eliminar atributos  o métodos de la subclase.
      5. Añadir atributos y métodos.

 Esta  especialización   tiene que  cumplirse   la Ley estricta de Demeter, que consiste en tener en cuenta que:

La Ley de Demeter tiene dos versiones: la Ley estricta  de Demeter  que no contempla el acceso a los atributos de finidos en la superclase desde la subclase y la Ley Flexible de Demeter que si lo permite.

 La ley estricta de Demeter:

Desde la definición de los métodos de una clase se tiene acceso a:

La actitud correcta  es evitar el acceso a los atributos de la superclase; es decir, en lo posible, se debe intentar respetar la ley estricta de Demeter.

La utilización de los atributos heredados desde la superclase, en la definición de los métodos de la subclase, aumenta las cotas de acoplamiento entre las clases.

Los métodos añadidos,  manejarán los atributos de la superclase    a través de los métodos heredados de la superclase,  mediante  la instancia actual de la clase this  y no directamente.

(Los métodos que manejan correctamente  los atributos de la superclase, son los métodos definidos en la superclase.)

Redefinición de métodos.

La redefinición implica que el mismo método, con el mismo nombre y mismos parámetros, se encuentra presente en dos clases: la superclase   y la subclase.  Como el método redefinido en la subclase, oculta el correspondiente método de la superclase, se perdería el acceso a los atributos  y métodos de  la superclase  en la redefinición,en cumplimiento de la Ley Estricta de Demeter.

Para solucionar esto se contempla una palabra reservada  super que es un supuesto objeto de la superclase  al que sí se tendría acceso desde la subclase. Mediante el envío del mensaje super.método(), estaríamos accediendo desde la subclase  al    método del mismo nombre de la superclase y, este método (el de la superclase),   sí es el adecuado para manejar los atributos definidos en la superclase.

En la redefinición del método m2():

public ….. m2(….){

super.m2();

}

super.ma2()    se referiría al método m2() de la superclase,  para manejar los atributos definidos en la superclase.

Redefinimos por tanto las leyes de Demeter:


 

La ley estricta  de Demeter: Desde la definición de los métodos de una clase se tiene acceso a:


 

Cuando no sea posible observar la Ley estricta de Demeter habrá que recurrir a la ley flexible del mismo nombre.

La ley flexible de Demeter: Desde la definición de los métodos de una clase se tiene acceso a:

–         Los atributos de la clase

–         Los atributos heredados desde la superclase  (protected)

–         los parámetros locales del método

–         Los objetos locales al método que se está  definiendo

–         Instancia actual de la clase this

–         Objeto super de la superclase


La  herencia es pués:

HERENCIA = TRANSMISIÓN + REDEFINICIÓN + ADICIÓN

Las implicaciones de la herencia sobre los objetos de las clases involucradas son las siguientes:

–         Sobre los objetos de la superclase A:  ninguna

–         Sobre los objetos de la subclase B:

–         Contienen todos los atributos que contienen  los objetos de la superclase.

–         Contiene los atributos añadidos de la subclase.

–         Responden a los mensajes que corresponden con métodos transmitidos a la subclase; es decir, como dicta el método de la superclase.

–         Responden a los mensajes que corresponden con métodos añadidos a la subclase; es decir, como dicta el método de la subclase.

–         Responden a los mensajes que corresponden con métodos redefinidos  en la subclase; es decir, como dicta el método de la subclase anulando el método de la superclase.

No hay ninguna  implicación de la herencia sobre los objetos de la superclase.

Para la subclase:

Atributos   =   Atributos de la superclase  + Atributos añadidos en la subclase

Comportamiento: Para a de  la clase A y  b de  la clase B

Mensaje Comportamiento
b. ma1() ma1() de superclase
b.ma2() ma2() Redefinido en la subclase
b.mb1() mb1() de la subclase

3. Sintaxis de la herencia en Java.


modificador    NombreSubClase         extends         NombreSuperClase{

                           …

}


La subclase puede redefinir tanto los atributos cómo los métodos heredados (si no son final o static).

Los métodos de la superclase redefinidos pueden ser utilizados a través de la referencia super en el método  correspondiente (al principio):

super.métodoRedefinido(…)

Si el método es un constructor, se utiliza mediante super(lista de argumentos), que construirá/inicializaría los atributos heredados utilizando el constructor coreespondiente con argumentos de la  superclase  o super() que construirá/inicializará los atributos heredados utilizando el constructor sin argumentos de la superclase; después,  el constructor de la subclase inicializaría el resto de los atributos no heredados. Si en un constructor de una subclase se realiza una llamada al constructor de la superclase, tal y como seha descrito anteriormente, esta sentencia tiene que ser la primera.

En la subclase se pueden añadir otros métodos.

4. Jerarquía de clases en Java.

En Java todas las clases derivan de una clase raíz java.lang.Object.

Algunos métodos heredados de esta clase y que pueden ser redefinidos son:

Otros métodos de la clase Objet no podrán ser redefinidos por ser métodos final:

5. Herencia múltiple.

La herencia simple no puede representar las relaciones múltiples que se presentan en muchas ocasiones, de la forma:

Para estas situaciones debe utilizarse herencia múltiple, la subclase comparte atributos y comportamiento con varias superclases.

La herencia múltiple favorece en este sentido la reutilización y favorece un enfoque más flexible para el diseño de las aplicaciones.

Sin embargo, el diseño de las clases que implican herencia múltiple es una tarea difícil, resuelta de múltiples formas en los diferentes lenguajes de programación.

Cualquier implementación de herencia múltiple, debe solucionar dos problemas fundamentales:

Las colisiones por nombre quedan resueltas, según Booch, de la formas:

–     La semantica del lenguaje considera que una colisión de nombre es ilegal y rechaza la compilación de la clase.

–    La semantica del lenguaje permite el conflicto pero exige que todas las referencias de nombres se cualifiquen con la clase correspondiente.

La herencia repetida se soluciona:

–     El compilador duplica la clase que se hereda dos ó más veces y  se exige el uso de nombres cualificados para referirse a una copia determinada de la clase.

–     Se considera la herencia repetida ilegal.

–     Se utilizan clases virtuales indicando así su uso compartido.

En Java, una clase sólo tiene una superclase. No existe la herencia múltiple.

6. Interfaces de Java.

En Java, una clase sólo tiene una superclase. No existe la herencia múltiple. Para solucionar las situaciones en las que es necesario definir una clase con características definidas en  más de una clase, se emplean interfaces.

Una interfaz es una colección de constantes y métodos abstractos.


 

Una clase puede implementar una interfaz empleando la cláusula implements  y anulando los métodos abstractos definidos dentro de la interfaz.

modificador     class NombreClase extends SuperClase implements Interfaz

{

//Hay que redefinir todos los métodos incluidos en la interfaz

public void metodo()

{

}

}


La definición de una interfaz es exactamente igual que la definición de una clase sustituyendo la palabra class por interface y solamente permitiendo el uso de las constantes y de los métodos abstractos:


 

public interface  NombreTerminadoEnAble

{

public      constantes….

public      métodoPublicoAbstracto(…);

}


Por defecto, todas las interfaces son abstractas y todos los métodos de una interfaz abstractos y públicos, por eso no se especifica explícitamente

Las interfaces pueden formar jerarquías heredando de una interfaz determinada (cláusula extends).

public interface I1   extends I2{

}

Los métodos abstractos son útiles cuando se quiere que cada implementación de la clase parezca y funcione igual, pero necesita que se cree una nueva clase para utilizar los métodos abstractos.

Los interfaces proporcionan un mecanismo para abstraer los métodos a un nivel superior.

Un interface contiene una colección de métodos que se implementarán  en otro lugar.

La principal diferencia entre interface y una clase abstracta abstract es que un interface proporciona un mecanismo de encapsulación de los prototipos de  los métodos evitando al desarrollador forzar  una relación de herencia.

La ventaja principal del uso de interfaces es que una clase interface puede ser implementada por cualquier número de clases, permitiendo a cada clase compartir el interfaz de programación sin tener que ser consciente de la implementación que hagan las otras clases que implementen el interface.

Interfaces


Un interface es una colección de declaraciones de métodos (sin definirlos). También puede incluir constantes. Representa una funcionalidad o lo que es lo mismo un conjunto de comportamientos declarados, pero no definidos.


 

Comparable es un ejemplo de interfaz en el cual se declara pero no se implementa  un método:

public abstract  int compareTo(Object o);

public interface Comparable {

public abstract  int compareTo(Object o);

}


 

Comparator es otro ejemplo de interfaz en el cual se declara pero no se implementan los métodos

public interface  Comparator{

public abstract bolean equals(Object o);

public abstract int compare(Object o1, Object o2);

}


 

Runnable es otro ejemplo de interface en el cual se declara, pero no se implemementa, un método run().

public interface Runnable {
    public abstract void run();
}


Las clases que implementen (implements) el interface Runnable tienen que  definir obligatoriamente el método run().

class Animacion implements Runnable{
//..
        public void run(){
               //define el método  run()
        }
}

Una clase que implementa (implements) una interface tiene necesariamente que definir todos aquellos métodos que incluidos en la interfaz.

El papel del interface es el de describir algunas de las características de una clase.

Clases que no están relacionadas pueden implementar el mismo interface lo que significa que saben hacer las mismas cosas pero de distinta manera.

Diferencias entre un interface y una clase abstracta

Un interface es simplemente una lista de métodos no implementados, además puede incluir la declaración de constantes.

Una clase abstracta puede incluir métodos implementados y no implementados o abstractos, constantes y otros atributos.

Ahora bien, la diferencia es mucho más profunda.

Una clase solamente puede derivar (extends) de una clase base, pero puede implementar varios interfaces. Los nombres de los interfaces se colocan separados por una coma después de la palabra reservada implements.

El lenguaje Java no fuerza por tanto, una relación jerárquica, simplemente permite que clases no relacionadas puedan tener algunas características de su comportamiento similares.

Las interfaces y el polimorfismo

En el lenguaje Java solamente existe la herencia simple, pero las clases pueden implementar una o varias interfaces.

Vamos a ver en este apartado que la importancia de los interfaces no estriba en resolver los problemas inherentes a la herencia múltiple sin forzar relaciones jerárquicas, sino  el de incrementar el polimorfismo del lenguaje más allá del que proporciona la herencia simple.


 

La clase Figura es la que contiene las características comunes a dichas figuras concretas por tanto, no tiene forma ni tiene área. Esto lo expresamos declarando Figura como una clase abstracta, declarando el método area() abstract.

public abstract class Figura {
     private int x;
     private int y;
     public Figura(){
       this.x=0;
       this.y=0;
     }
     public abstract double area();
     public String toString(){
        String s=””;
        s=s+ “soy una figura” + “ y mi origen esta en:” + x +”,”+y;
     }
}

Las clases abstractas solamente se pueden usar como clases base para otras clases. No se pueden crear objetos pertenecientes a una clase abstracta. Sin embargo, se pueden declarar referencias de dichas  clases.

Figura f;

La definición de la clase abstracta Figura, contiene la posición x e y de la figura particular, de su centro, y el método area()  que se va a definir en las clases derivadas, para calcular el área de cada figura en particular.

public abstract class Figura {
    private int x;
    private int y;
    public Figura(int x, int y) {
        this.x=x;
        this.y=y;
    }
     public String toString(){
        String s=””;
        s=s+ “soy una figura” + “ y mi origen esta en:” + x +”,”+y;
     }

    ...
    public abstract double area();
}

Las clases derivadas heredan los miembros dato x e y de la clase base, y definen el método area(), declarado abstract en la clase base Figura, ya que cada figura particular tiene una fórmula distinta para calcular su área. Por ejemplo, la clase derivada Rectangulo, tiene como atributos, aparte de su posición (x, y) en el plano, sus dimensiones, es decir, su anchura ancho y altura alto.

class Rectangulo extends Figura{
    private double ancho, alto;
    public Rectangulo(int x, int y, double ancho, double alto){
        super(x,y);
        this.ancho=ancho;
        this.alto=alto;
    }
    ...
    public double area(){
        return ancho*alto;
    }
}

La primera sentencia en el constructor de la clase derivada es una llamada al constructor de la clase base, para ello se emplea la palabra reservada super. El constructor de la clase derivada llama al constructor de la clase base y le pasa las coordenadas del punto x e y. Después inicializa sus atributos ancho y alto.

En la definición del método  area(), se calcula el área del rectángulo como producto de la anchura por la altura, y se devuelve el resultado

class Circulo extends Figura{
    private double radio;
    public Circulo(int x, int y, double radio){
        super(x,y);
        this.radio=radio;
    }
    ...
    public double area(){
        return Math.PI*radio*radio;
    }
}

Como vemos, la primera sentencia en el constructor de la clase derivada es una llamada al constructor de la clase base empleando la palabara reservada super. Posteriormente, se inicializa el atributo radio, de la clase derivada Circulo.

Creamos un objeto c de la clase Circulo:.

        Circulo c=new Circulo(0, 0, 5.5);
        System.out.println("Area del círculo "+c.area());

Creamos un objeto r de la clase Rectangulo:.

        Rectangulo r=new Rectangulo(0, 0, 5.5, 2.0);
        System.out.println("Area del rectángulo "+r.area());

Taambién puede utilizarse una forma alternativa, guardamos el valor devuelto por new al crear objetos de las clases derivadas en una variable f del tipo Figura (clase base).

        Figura f=new Circulo(0, 0, 5.5);
        System.out.println("Area del círculo "+f.area());
        f=new Rectangulo(0, 0, 5.5, 2.0);
        System.out.println("Area del rectángulo "+f.area());

Herencia, enlace dinámico y polimorfismo

Podemos crear un array de referencias a posibles objetos de  la clase base Figura y» guardar» en sus elementos los valores devueltos por new al crear objetos de las clases derivadas.

        Figura[] fig=new Figura[4];  //array de referncias a Figura clase abstracta
        fig[0]=new Rectangulo(0,0, 5.0, 7.0);  //Un objeto de una clase que hereda de Figura 
        fig[1]=new Circulo(0,0, 5.0);
        fig[2]=new Circulo(0, 0, 7.0);
        fig[3]=new Rectangulo(0,0, 4.0, 6.0);

La sentencia

        fig[i].area();

¿a qué método area()  llamará?. La respuesta la encontramos en el Polimorfismo.

 

3 comentarios »

  1. programmer23 said,

    buen resumen.Ahora estoy un poco mas claro. Gracias!

  2. lo q yo qiero saber cual se le conose com clase derivada

    • vcalpena said,

      No entiendo que es lo que pregunta. ?Cómo reconoceruna clase derivada o hija? ¿o qué? Puede aclararme su duda?
      Un saludo:
      Virginia Calpena


Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

A %d blogueros les gusta esto: