Capitulo 7 – Herencia de Clases (Parte I)

Buenos días, hoy estrenamos el Capitulo 7, un capítulo mucho mas que importante por lo que lo dividire en muchas mas partes que los anteriores, no solo porque sea largo, que tambien, sino porque es importante ver las cosas ordenadas y poco a poco para lograr un conocimiento mas sólido.
Lo que daremos en el Capitulo 7 serán las siguientes partes:

  • Inheritance and Class Type
  • Encapsulation
  • Advanced Examples of Classes with Inheritance and encapsulation

Dicho esto podemos empezar con la primera parte.

Inheritance And Class Type

El objetivo de esta sección es describir, comparar, y contrastar las clases concretas, clases abstractas, e interfaces, y como aplicar herencia entre ellas.

La herencia es un concepto fundamental del lenguaje Java. Esto permite a clases específicas heredar los métodos y las instance variables de otras clases mas generales. Esta práctica crea código que es mas fácil de mantener y empatiza el reúso del código.
En esta sección veremos las diferencias entre clases concretas y abstractas. Las clases concretas son las clases estandar, mientras que las clases abstractas tienden a la herencia.
Finalmente discutiremos las interfaces. En corto, una interface permite al desarrollador especificar como interface pública a una clase. Cualquier clase que implemente o use una interface debe sorportar las especificaciones de la descripción de la interface.
Trataremos la herencia y los detalles de como se trabaja con ella.

  • Inheritance (Parte I)
  • Overriding methods (Parte I)
  • Abstract Classes (Parte I)
  • Interfaces (Parte II)
  • Advanced concepts of Inheritance (Parte III)

Inheritance

La herencia permite al desarrollador crear clases generales que pueden ser usados como una fundación de mñltiples clases específicas. Por ejemplo, un programa puede requerer que se tenga clases que no representan animales. Los animales que deben ser representados son perros, gatos y caballos. En todos estos animales, sus clases tienen elementos comunes. En este simple ejemplo, cada animal tendría un peso, una edad y un color como instance variable. Cada clase de animal tambien tendría métodos que permitiesen hacer ciertas acciones como correr, descansar y moverse.
Estos métodos podrían llamarse eat(), rest() y move(int direction). Esto puede ser implementado sin usar la herencia creando una clase para cada tipo de animal y definiendo para cada uno los anteriores métodos. Esta implementación funcionaría pero con algunos pequeños inconvenientes. Como cada animal come, descansa y se mueven de manera similar, habrá una gran cantidad de código duplicado entre cada clase. El código duplicado hace que el programa sea dificil de mantener. Si se encuentra un bug en una clase, el desarrollador debe recordar donde ir para encontrarlo en las otras clases donde se ha usado el mismo código. Surge el mismo problema al añadir nuevas caracteristicas al código duplicado. Otro inconveniente de este uso es que no se puede usar el polimorfismo. El polimorfismo es una técnica que permite a un objeto específico, como un objeto perro, a ser referido en el código como un pariente animal mas general.
El siguiente ejemplo muestra cada clase animal usando este enfoque.. Los detalles de cada clase está presentada en comentarios para aplicar la funcionalidad que presentaría si fuera implementada:

public class Dog1 {
    int weigth;
    int age;
    int hairColor;
    public void eat(){
        //Come comida masticando
    }
    public void rest(){
        //Descansa
    }
    public void move(int direction){
        //Camina en la dirección dada como parámetro
    }
    public void bark(){
        //Ladra
    }
}
public class Cat1 {
    int weigth;
    int age;
    int hairColor;
    public void eat(){
        //Come comida masticando
    }
    public void rest(){
        //Descansa
    }
    public void move(int direction){
        //Camina en la dirección dada como parámetro
    }
    public void meow(){
        //Maulla
    }
}
public class Horse1 {
    int weigth;
    int age;
    int hairColor;
    public void eat(){
        //Come comida masticando
    }
    public void rest(){
        //Descansa
    }
    public void move(int direction){
        //Camina en la dirección dada como parámetro
    }
    public void neigh(){
        //Relincha
    }
}

La primera implementación de estos animales es crear una clase única para cada uno. Cada una de las clases anteriores no tienenr elación con los otros. Es fácil de ver que cada clase es similar y hay código duplicado entre ellas. Sin embargo, todos los métodos son los mismos excepto el método bark(), meow() y neigh(). Aunque no hay relaciones explícitas definidos en el código, es fácil de ver que las 3 clases son casi iguales.
El mismo ejemplo puede implementarse de mejor manera usando la herencia. Ene ste ejemplo, 3 de los 4 métodos que se necesita implementar son comunes para cada animal. Un perro, un gato y un cabalo comen, descansan y se mueven. Esta funcionalidad común puede ser ubicada en una clase general Animal. Esta clase contiene los métodos generales y las instance variables. Cuando el desarrollador crea mas tipos de anmales mas específicos como perros, gatos o caballos, estos pueden usar la clase Animal como base o superclase. Las clases mas específicas heredarán todos los métodos no privados y las instance variables de la clase base Animal. Una clase hereca cuando es extendida (extends). Es importante recordar que las clases solo pueden heredar de una clase. Es inválido heredar múltiples clases. La sentencia extends es usado en la linea de la declaración de la clase.
Lo siguiente es un ejemplo de los mismos animales siendo implementados usando la herencia:

public class Animal {
    int weigth;
    int age;
    int hairColor;
    public void eat(){
        //Come comida masticando
    }
    public void rest(){
        //Descansa
    }
    public void move(int direction){
        //Camina en la dirección dada como parámetro
    }
}
public class Dog2 extends Animal{
    public void bark(){
        //Ladra
    }
}
public class Cat2 extends Animal{
    public void meow(){
        //Maulla
    }
}

public class Horse2 extends Animal{
    public void neigh(){
        //Relincha
    }
}

Este ejemplo cre las clases Dog2, Cat2, Horse 2 que funcionalmente son como las del primer ejemplo. Cada una de estas clases hereda la clase Animal. La clase Animal es usada como clase base o superclase. Las clases especificas heredan todos los métodos e instance variables que la clase particular puede necesitar. En este ejemplo, cada clase ha añadido un método para hacer el ruido del animal. Las clases pueden añadir cualquier método o instance variables como se necesiten, o usar solo las que ofrece la superclase.
Cuando una clase hereda de otra clase, cualquier método no privado que esté en la superclase es accesible en la subclase. Pueden ser invocados de la misma manera como los métodos implementados en la subclase. El siguiente ejemplo muestra como la clase Dog2 puede ser usada:

Dog2 dog = new Dog2();
dog.bark();
dog.eat();

En este ejemplo, un objeto Dog2 es creado. Luego son llamados los métodos bark() y eat(). Vemos como ambos métodos pueden ser llamados de la misma manera, aunque el método bark() es implementado en la clase Dog2. Esto es porque cualquier objeto Dog2 hereda todos los métodos que no sean privados de la clase Animal.

Overriding Methods

Heredar de una clase es un buen enfoque para factorizar funcionalidad común entre clases. las clases que heredan de otras clases mas generales premiten que el código sea rehusable en un proyecto. Como dijimos antes, esto ayuda a que un proyecto sea mas mantenible y sea menos propicio a generar bugs en el progreso del ciclo de desarrollo.
El problema de este enfoque es que la subclase que hereda los métodos de la superclase son a veces ligeramente diferentes. Por ejemplo una clase Fish hereda de la clase animal, donde el método move() no funcionaría ya que un pez no anda, sino que nada. una clase que hereda de otra clase puede sustituir cualquier método heredado. Esto se hace definiendo otro método llamado move() con los mismos argumentos. Cuando el método move() sea invocado, el implementado en la clase Fish será usado. Una clase puede sustituir todos los métodos heredados, uno o ninguno. Lo siguiente es un ejemplo de como la clase Fish que hereda de la clase Animal sustituye al método move():

public class Fish extends Animal{
    public void move(int direction){
        //Nada
    }
}

Vease en el ejemplo que la clase Fish tiene un método move() igual que el de la clase Animal. El método move() de la clase Fish esta sustituyendo al método move() de la clase Animal. Cuando un objeto Fish es creado y se llama al método move(), ejecutará el método localizado en la clase Fish. Para sustituir un método, la declaración del método debe ser idéntica.
Cuando una subclase sustituye a un método, tiene la opción de llamar al método que está siendo sustituido. Esto puede ser logrado usando la sentencia super. La sentencia super trabaja como la sentencia this, pero en vez de referirse a la presente clase, super se refiere a la superclase. Cuando se usa super, este debe pasar el argumento correcto al método pariente. Como los caballos normalmente descansan estando de pie, la clase Horse2 puede ser modificada para que el caballo esté de pie antes de realizar el método rest():

public class Horse3 extends Animal{
    public void neigh(){
        //Relincha
    }
    public void rest(){
        //Se pone de pie antes de descansar
        super.rest();
    }
}

Cuando el objeto Horse3 tiene el método rest(), esto ejecutará el código del método rest() de la clase Horse3. Esto es porque el método rest() sustituye al método rest de la clase Animal. El método rest() del Horse 3 hará que el caballo se ponga de pie y después usa la sentencia super para llamar al método rest de la clase Animal.
Hasta ahora, todos los ejemplos preentados usaban clases concretas. Una clase concreta es una clase regular que puede ser instanciada. Java tiene otro tipo de clases llamadas clases abstractas. Una clase abstracta es diferente a una clase concreta porque no puede ser instanciada, solo heredada. Una clase abstracta puede contener métodos abstractos. Métodos abstractos son aquellos que no pueden ser implementados. Tienen una declaración válida de método pero deben ser sustituodos para poder ser implementados ya que hereda de la clase abstracta. Lo siguiente es un ejemplo de una clase abstracta:

public abstract class MusicPlayer {
    public abstract void play();
    public abstract void stop();
    public void changeVolume(int volumeLevel){
        //Poe el volumen a volumeLevel
    }
}

El anterior ejemplo es una clase abstracta para un reproductor de música. Esto pretende ser uan clase base para diferentes dispositivos reproductores de música como reproductores MP3 o reproductores de CD. Vemos como la clase está definida, con la sentencia abstract para marcar la clase como abstracta. Esta clase provee funcionalidad con el método changeVolume. También contiene 2 métodos abstractos. Los métodos abstractos solo pueden estar en una clase abstracta. La palabra abstract marca el método como abstracto, y estos métodos deben ser implementados en la subclase que la hereda.
El propósito de los métodos abstractos es definir la funcionalidad requerida que la subclase debe tener. En este caso, cualquier reproductor de música puede reproducir o parar. La funcionalidad no puede ser implementada en la clase MusicPlayer ya que no todos los reproductores funcionan igual:

public class MP3Player extends MusicPlayer{
    public void play() {
        //Start decoding and playing MP3
    }

    public void stop() {
        //Stop decoding and playing
    }
}
public class CDPlayer extends MusicPlayer{

    public void play() {
        //Start reading and playing Disc
    }

    public void stop() {
        //Stop reading Disc
    }
}

Las clases MP3Player y CDPlayer son ambos diferentes tipos de reproductores. Heredando de MusicPlayer, requieren implementar los métodos play() y stop() sustituyendo la clase abstracta en la clase base.

Hasta aquí esta parte del Capítulo 7, la siguiente parte la usaremos para ver las Interface.

Cualquier aporte o corrección es bienvenida.

Saludos!!!