Capitulo 7 – Herencia de Clases (Parte IV)

Buenos dias, hoy vamos con unos ejemplos avanzados para ver la herencia y la encapsulación, con los que sin duda nos quedará algo mejor marcado y cualquier duda puede aclararse.

El objetivo de esta parte es desarrollar el código que defina clases concretas, clases abstractas e interfaces, código que soporte la implementación y la herencia de interfaces, código que declare atributos de instance y métodos, y código que use los modificadores de acceso de Java: private y public.

Esta sección concluye el capítulo revisando todos los conceptos que han sido discutidos y demostrarlos en ejemplos de código. Cada ejemplo estará seguido por una explicación detallada sobre lo mas remarcado y como trabaja. Esto nos ayudará a reforzar todos los conceptos que hemos visto.

Examples of Java Access Modifiers (Ejemplos de Modificadores de Acceso)

El siguiente ejemplo es la implementación de una clase con sus detalles de implementación ocultos. Usa métodos publicos para exponer una interface, como los getter y setters ofrecen acceso a las instance variables.

public class PhoneBookEntry {
    private String name="";
    private int phoneNumber=0;
    private long lastUpdate=0;
    
    public String getName(){
        return name;
    }
    
    public void setNameNumber(String name, int phoneNumber){
        this.name=name;
        this.phoneNumber=phoneNumber;
        lastUpdate = System.currentTimeMillis();
    }
    
    public int getPhoneNumber(){
        return phoneNumber;
    }
    
    public void setPhoneNumber (int phoneNumber){
        this.phoneNumber=phoneNumber;
        lastUpdate = System.currentTimeMillis();
    }
}

Este código es una buena clase aplicando la encapsulación. Es una clase que puede representar una entrada en una agenda de números telefónicos. Puede almacenar un nombre y un número de teléfono. también almacena una instance variable para guardar la última vez que se actualizó. Todas las instances variables usan el modificador de acceso private, por lo cual, ninguna clase externa podrá leer o modificarlos directamente. Estos tendrán que usar los métodos setter o getter para poder acceder a ellas. En el ejemplo, no hay métodos setter para asignar un valor a la variable name. Para hacer esto, tendrá que establecer un nuevo número de teléfono, con lo que aseguraríamos que no puedan existir nombres sin número de teléfono. Si la instances variable fuese pública, esta clase no podría prevenir que otra clase pudiera introducir un nombre sin un número de teléfono.
Este ejemplo tambien usa sus métodos setter para actualizar al variable lastUpdate. Esta variable se usa para seguir un rastro de cuando fue la última vez que se actualizó. Usando getter y setter, la clase tendrá la garantía de que cualquier objeto externo actualizará un campo por medio del setter, y la variable lastUpdate tambien será actualizada. Los detalles de como lastUpdate se actualiza es invisible para los objetos externos.

Examples of Inheritance with Concrete Classes (Ejemplos de Herencia con clases concretas)

Una clase concreta es una clase estándar de Java. Todos sus métodos son implementados y pueden ser instanciados. El siguiente ejemplo es de una clase Bicycle. La clase base representa una bicicleta básica. Otra clase representa una bicicleta con 10 velocidades. Esta será llamada TenSpeedBicycle y heredará de la clase Bicycle. La clase TenSpeedBicycle podrá heredar al funcionalidad mientras sustituye partes de la clase base que necesita tener.
La clase TennSpeedBicycle tiene la habilidad de cambiar su relación de transmisión en adición de que la clase Bicycle no puede hacerlo:

public class Bicycle {
    private float wheelRPM;
    private int degreeOfTurn;
    
    public void pedalRPM(float pedalRPM){
        float gearRatio = 2f;
        this.wheelRPM = pedalRPM*gearRatio;
    }
    
    public void setDegreeOfTurn(int degreeOfTurn){
        this.degreeOfTurn = degreeOfTurn;
    }
    
    public float getWheelRPM(){
        return this.wheelRPM;
    }
    
    public int getDegreeOfTurn(){
        return this.degreeOfTurn;
    }
}

El ejemplo anterior es la clase Bicycle. Es uan clave concreta y por lo tanto puede ser instanciada. Este ejemplo muestra una bicicleta básica. Tiene 2 instance variables, wheelRPM que es usada para almacenar las RPM de las ruedas, y degreeOfTurn. Cada variable tiene un getter, y degreeOfTurn tiene un setter. La variable wheelRPM se establece con el método pedalRPM(float pedalRPM). Esto acepta un argumento que contiene los RPM de los pedales y entonces multiplica eso por la relación de transmisi´no para buscar y establecer la variable weheelRPM.

public class TenSpeedBicycle extends Bicycle{
    public float gearRatio = 2f;
    private float wheelRPM;
    
    public void setGearRatio(float gearRatio){
        this.gearRatio = gearRatio;
    }
    
    public float getWheelRPM(){
        return this.wheelRPM;
    }
}

La clase TenSpeedBicycle, vista en este ejemplo anterior, hereda de la clase Bicycle. Esta clase representa una bicicleta que puede tener 10 diferentes tipos de relaciones de transmisión. Una bicicleta regular no puede ser usada porque tiene solo 1 marcha fija. La clase TenSpeedBicycle añade un método y una instance variable con lo que se puede establecer una marcha. Tambien sustituye la variable wheelRPM. Esto debe hacerse porque la variable en la clase Bicycle no tiene un setter para asignar el valor directamente. La clase TenSpeedBicycle tambien sustituye el método pedalRPM(float pedalRPM). La clase TenSpeedBicycle tambien sustituyeel método pedalRPM(float pedalRPM). La versión de este método en la clase Bicycle, la marcha era fija. en esta nueva versión se establece que la marcha se puede cambiar. Para recuperar la variable wheelRPM, el getter debe ser sustituido. Esto ocurre porque la versión original de este método solo peude retornar la instance variable que esta en la misma clase:

public class Main {
    public static void main(String[] args){
        System.out.println("Starting..");
        System.out.println("Creating a bicycle...");
        Bicycle b = new Bicycle();
        b.setDegreeOfTurn(0);
        b.pedalRPM(50);
        System.out.println("Turning: " + b.getDegreeOfTurn());
        System.out.println("Wheel RPM: " + b.getWheelRPM());
        System.out.println("Creating a 10 speed Bicycle...");
        TenSpeedBicycle tb = new TenSpeedBicycle();
        tb.setDegreeOfTurn(10);
        tb.setGearRatio(3f);
        tb.pedalRPM(40);
        System.out.println("Turning: " + tb.getDegreeOfTurn());
        System.out.println("Wheel RPM: " + tb.getWheelRPM());
    }
}

EL ejemplo anterior del código es el que usa ambas clases. Este código imprime información en la salida estándar por cada paso que hace. Primero crea un objeto Bicycle. Establece degreeOfTurn a 0 y pedalRPM a 50. El código entonces imprime degreeOfTurn, que será 0, y los RPM de la rueda, que será 100, ya que la relación de transmisión es 2 (2*50).
Después, se crea un objeto TenSpeedBicycle. Este objeto gira con 10 grados que es 10, y sus RPM de la rueda se establece en 40. Finalmente imprime los grados de giro que es 10, y sus RPM de la rueda, que es 120 (3*40). Vemos que los métodos getDegreeOfTurn y setDegreeOfTurn del objeto TenSpeedBicycle fue heredado de la clase Bicycle.

Examples of Inheritance with Abstract Classes (Ejemplos de Herencia con Clases Abstractas)

En este ejemplo mostraremos las clases abstractas. Una clase abstracta en Java es aquella que no puede ser instanciada. Otra clase debe heredarla para ello. Una clase abstracta puede contener métodos concretos que tienen implementación, y métodos abstractos que deben ser implementados en la subclase. Este ejemplo crea una simulación de una planta. Tiene una clase abstracta Plant que será la extendida a la clase MapleTree y a la clase Tulip. La clase Plant es una buen ejemplo de clase abstracta ya que todas las plantas comparten algunas caracteristicas que pueden ponerse en esta clase. Cada clase especifica contendrá los detalles de implementación.
Lo siguiente es el código de la clase abstracta Plant:

public abstract class Plant {
    private int age = 0;
    private int height = 0;
    
    public int getAge(){
        return age;
    }
    
    public void addYearToAge(){
        age++;
    }
    
    public int getHeight(){
        return height;
    }
    
    public void setHeight(int height){
        this.height = height;
    }
    
    abstract public void doSpring();
    abstract public void doSummer();
    abstract public void doFall();
    abstract public void doWinter();
}

Ka ckase abstracta anterior es una vista muy simple sobre loq ue representa una planta. Contiene 2 instance variables que todas las plantas tendrían, age y height(año y altura). Ambas tienen un getter y un setter para height, y un getter para age. la instance variable age tiene un método que es usado para incrementar cada año.
La clase Plant tiene 4 métodos abstractos. Cada uno d estos métodos representa las acciones que una planta debe tomar durante las siguientes estaciones. estas acciones son específicas para cada tipo de planta y por tanto no pueden ser generalizadas. teniéndolas declaradas en la clase abstracta Plant ofrece la garantía de que deba ser implementada por cualquier clase que herede de la clase Plant.

public class MapleTree extends Plant{
    private static final int AMOUNT_TO_GROW_IN_ONE_GROWING_SEASON = 2;
    
    //Crece un concreto numero de pies al año
    //No baja a nivel del suelo cuando llega el invierno
    public void grow(){
        int currentHeight = getHeight();
        setHeight(currentHeight + AMOUNT_TO_GROW_IN_ONE_GROWING_SEASON);
    }

    @Override
    public void doSpring() {
        grow();
        addYearToAge();
        System.out.println("Spring: The maple tree is starting to grow leaves and new branches");
        System.out.println("tCurrent Age: " + getAge() + " Current Height: " + getHeight());
    }

    @Override
    public void doSummer() {
        grow();
        System.out.println("Summer: The maple tree is continuing grow");
        System.out.println("tCurrent Age: " + getAge() + " Current Height: " + getHeight());
    }

    @Override
    public void doFall() {
        System.out.println("Fall: The maple tree has stopped growing and is losing its leaves");
        System.out.println("tCurrent Age: " + getAge() + " Current Height: " + getHeight());
    }

    @Override
    public void doWinter() {
        System.out.println("Winter: The maple tree is dormant");
        System.out.println("tCurrent Age: " + getAge() + " Current Height: " + getHeight());
    }
}

La clase anterior es la clase MapleTree. esta hereda de la clase Plant y es usada para representar un arbol de Arce. Desde que la clase Plant es abstracta, la clase MapleTree tiene una variable llamada AMOUNT_TO_GROW_IN_ONE_GROWING_SEASON. Se declara como variable static, que es como se representan las constantes en Java. Esta variale es usada para establecer la cantidad de crecimiento que un arce crece en sus estaciones de crecimiento. La clase MapleTree contiene un método grow(). Este método es usado para añadir mas altura a la altura actual de la planta. Los siguientes cuatro métodos son aquellos que requieren implementación. estos métodos han sido implementados en la clase Plant, los cuales representan las cuatro estaciones. Cuando son invocados, realizan la acción requerida que necesitan en esa estación y después imprimen 2 lineas por la salida estándar. La primera linea meustra la estación en al que está y lo que el arce hará. La siguiente linea meustra los valores de los años y la altura de la planta en cuestión.

public class Tulip extends Plant{
    private static final int AMOUNT_TO_GROW_IN_ONE_GROWING_SEASON = 1;
    
    //Un tulipan crece cada año la misma altura
    //En invierno yacen al mismo nivel del suelo
    public void grow(){
        int currentHeight = getHeight();
        setHeight(currentHeight + AMOUNT_TO_GROW_IN_ONE_GROWING_SEASON);
    }
    
    public void dieDownForWinter(){
        setHeight(0);
    }
    
    @Override
    public void doSpring() {
        grow();
        addYearToAge();
        System.out.println("Spring: The tulip is starting to grow up from the ground");
        System.out.println("tCurrent Age: " + getAge() + " Current Height: " + getHeight());
    }

    @Override
    public void doSummer() {
        System.out.println("Summer: The tulip has stopped growing and is flowering");
        System.out.println("tCurrent Age: " + getAge() + " Current Height: " + getHeight());
    }

    @Override
    public void doFall() {
        System.out.println("Fall: The tulip begin to will");
        System.out.println("tCurrent Age: " + getAge() + " Current Height: " + getHeight());
    }

    @Override
    public void doWinter() {
        dieDownForWinter();
        System.out.println("Winter: The tulip is dormant underground");
        System.out.println("tCurrent Age: " + getAge() + " Current Height: " + getHeight());
    }
}

El anterior ejemploe s la clase Tulip. ha sido Creada para representar un tulipan. Hereda de la clase Plant y debe tambien implementar todos sus métodos abstractos. Como la clase MapleTree, tambien tiene una constante para almacenar cuanto crece en cada estación de crecimiento. Tiene 2 métodos privados, el método grow() es como el representado en la clase MapleTree. tambien tiene un método llamado dieDownForWinter(). este método es usado para reiniciar la altura a 0 cuando el tulipán pierde todass sus hojas durante el verano.
Los ultimos cuatro métodos en al clase son los métodos abstractos de la clase Plant. en cada estación, realiza la acción necesaria primero y luego imprime la estación y las propiedades de la planta:

public class PlantSimulator {
    public static void main (String[] args){
        System.out.println("Creating a Maple tree and a Tulip...");
        MapleTree mapleTree = new MapleTree();
        Tulip tulip = new Tulip();
        System.out.println("Entering a loop to simulate 3 years");
        for (int i = 0; i<3; i++){
            mapleTree.doSpring();
            tulip.doSpring();
            mapleTree.doSummer();
            tulip.doSummer();
            mapleTree.doFall();
            tulip.doFall();
            mapleTree.doWinter();
            tulip.doWinter();
        }
    }
}

El código final es el método main() que usa ambas clases. Primero un objeto de cada tipo es creado. Luego hay un bucle for que invoca los métodos para todas las estaciones de cada objeto. Este ejemplo muestra un programa de simulación básico. Cada iteración representa un año.
Ambos objetos incrementan estatura y años de un año a otro:

Creating a Maple tree and a Tulip...
Entering a loop to simulate 3 years
Spring: The maple tree is starting to grow leaves and new branches
	Current Age: 1 Current Height: 2
Spring: The tulip is starting to grow up from the ground
	Current Age: 1 Current Height: 1
Summer: The maple tree is continuing grow
	Current Age: 1 Current Height: 4
Summer: The tulip has stopped growing and is flowering
	Current Age: 1 Current Height: 1
Fall: The maple tree has stopped growing and is losing its leaves
	Current Age: 1 Current Height: 4
Fall: The tulip begin to will
	Current Age: 1 Current Height: 1
Winter: The maple tree is dormant
	Current Age: 1 Current Height: 4
Winter: The tulip is dormant underground
	Current Age: 1 Current Height: 0
Spring: The maple tree is starting to grow leaves and new branches
	Current Age: 2 Current Height: 6
Spring: The tulip is starting to grow up from the ground
	Current Age: 2 Current Height: 1
Summer: The maple tree is continuing grow
	Current Age: 2 Current Height: 8
Summer: The tulip has stopped growing and is flowering
	Current Age: 2 Current Height: 1
Fall: The maple tree has stopped growing and is losing its leaves
	Current Age: 2 Current Height: 8
Fall: The tulip begin to will
	Current Age: 2 Current Height: 1
Winter: The maple tree is dormant
	Current Age: 2 Current Height: 8
Winter: The tulip is dormant underground
	Current Age: 2 Current Height: 0
Spring: The maple tree is starting to grow leaves and new branches
	Current Age: 3 Current Height: 10
Spring: The tulip is starting to grow up from the ground
	Current Age: 3 Current Height: 1
Summer: The maple tree is continuing grow
	Current Age: 3 Current Height: 12
Summer: The tulip has stopped growing and is flowering
	Current Age: 3 Current Height: 1
Fall: The maple tree has stopped growing and is losing its leaves
	Current Age: 3 Current Height: 12
Fall: The tulip begin to will
	Current Age: 3 Current Height: 1
Winter: The maple tree is dormant
	Current Age: 3 Current Height: 12
Winter: The tulip is dormant underground
	Current Age: 3 Current Height: 0

Examples of Interfaces (Ejemplos de Interfaces)

Este ejemplo final envuelve las interfaces. Una interface es un set de métodos que deben ser implementados por la clase que usa la interface. Usando una interface, se dice que una clase implementa la funcionalidad definida en la interface. este ejemplo tiene 2 interfaces. Una es llamada Printer y provee una interface públic que las impresoras mas básicas deberían implementar. Cualquier impresora que implemente la nterface Printer, se puede decir que tiene la habilidad de imprimir. La otra interface es Fax. ofrece la habilidad de implementar las habilidades de un Fax. Finalmente,e ste ejemplo tiene una clase que implementa ambos, por lo cual podría representar un instrumento que tiene funcionalidad de Fax y de Impresora:

public interface Printer {
    public void printFile(File f);
    public int getInkLevel();
}

Esta interfaz es para una impresora. Esto provee una interface pública básica que todas las impresoras deberían tener. En este ejemplo, la impresora peude ahcer 2 cosas. Puede imprimir un archivo con el método printFile(File f) o comprobar los niveles de tinta con el método getInkLevel().

public interface Fax {
    public void sendFax(File f, int number);
    public Object getReceivedFaxes();
}

Esta interface es para un fax. Esta máquian puede recibir un fax con el método getReceivedFaxes() o enviar un fax con el método sendFax(File f, int number). Lo siguiente es la clase PrinterFaxCombo. Esta clase implementa ambas interfaces:

public class PrinterFaxCombo implements Fax, Printer{
    private Object incomingFax;
    private int inkLevel;
    
    @Override
    public void sendFax(File f, int number) {
        dialNumber(number);
        faxFile(f);
    }

    @Override
    public Object getReceivedFaxes() {
        return incomingFax;
    }

    @Override
    public void printFile(File f) {
        sendFileToPrinter(f);
    }

    @Override
    public int getInkLevel() {
        return inkLevel;
    }
    
    private boolean dialNumber(int number){
        boolean success = true;
        //Boolean succes pasa a ser falso si no se logra enviar
        return success;
    }
    
    private void faxFile(File f){
        //Envia el archivo F como fax
    }
    
    private void sendFileToPrinter(File f){
        //Imprime el archivo f
    }
}

El anterior ejemplo es la clase PrinterFaxCombo de una impresora que puede usar el servicio de Fax. Esta clase noe stá totalmente implementada, pero los comentarios en lo smétodos nos explican el propósito de cada método. El punto mas importante de esta clase es que implementa las interfaces Printer y Fax. El avance de implementar interfaces es que permite a un objeto externo conocer que esta clase provee la funcionalidad de una impresora y un fax. Aquellas clases que implementan la interface Printer proveen la funcionalidad y tiene la misma interface public. Este crea un código modular que permite intercambio en las clases según las necesidades de la aplicación. Las Interfaces también permiten el polimorfismo.

Y aquí termina el Capitulo 7, un capítulo importante, que debemos controlar bien si queremos en el futuro no tengamos problemas sobre herencias, interfaces o clases abstractas.

Cualquier corrección o aporte es bienvenido.

Saludos!!!