Capitulo 8 – Entendiendo el Polimorfismo (Parte II)

Buenas tardes, hoy voy a poner la segunda parte de este Capitulo, ene l que se ve todo esto del polimorfismo que ya hemos visto, con ejemplos prácticos para afianzar mas los conocimientos sobre el tema.

Practical Examples of Polymorphism (Ejemplos prácticos del Polimorfismo)

El objetivo es desarrollar el código que usa el polimorfismo para clases e interfaces y reconocer el código que usa el principio “Program to an interface”.

Esta sección continuará examinando el polimorfismo. Mientras que la primera parte se enfocaba al polimorfismo de manera mas teórica, esta será de una manera mas práctica, mediante ejemplos. Es importante entender, y revisar todo con cuidado, ya que proveen une entendimiento limpio de lo visto anteriormente.

Examples of Polymorphism (Ejemplos del Polimorfismo

Esta sección proveerá ejemplos dl polimorfismo. El primer ejemplo mostrará como el polimorfismo puede ser aplicado cuando una clase se extiende a otra.

  • Examples of Polymorphism via Class Inheritance

    El siguiente código ha sido realizado para mostrar el uso del polimorfismo con la herencia entre clases. Este ejemplo tiene 3 clases, de las cuales 2 de ellas sirven para representar tipos de teléfonos. La clase Phone es creada como representación de un teléfono estandar. Esta clase tiene un método para marcar un número, y retornar el estado de si el teléfono está sonando o no. La segunda clase representa un smartphone, y se llama SmartPhone. La clase SmartPhone se extiende de la clase Phone. Esta clase SmartPhone añade funcionalidad adicional de poder enviar or ecibir emails. La última clase ha sido llamada PhoneTester y es usada como conductor para probar ambas clases y mostrar el polimorfismo ena cción. Las clases de teléfono son simples representaciones, y la mayoría de su funcionalidad no está implementada. en vez de esto, unos comentarios nos darán el propósito. Lo siguiente es la clase Phone:

    public class Phone {
        public void callNumber(long number){
            System.out.println("Phone: Calling number " + number);
            //Marca el número y mantiene la conexión
        }
        
        public boolean isRinging(){
            System.out.println("Phone: Checking if phone is ringing");
            boolean ringing = false;
            //Se comprueba si el teléfono está sonando y se establece la variable ringing
            return ringing;
        }
    }
    

    La clase Phone es una clase normal usada paar representar las caracteristicas de un teléfono. La clase tiene un método callNumber() que es usado para llamar al número que es pasado como argumento. El método isRinging es usado para determinar si el teléfono está actualmente sonando. Esta clase imprime por la salida estandar el nombre de la clase y acción que está realizando cuando entra a cada método. La clase Phone es la clase base para la clase SmartPhone:

    public class SmartPhone extends Phone{
        
        public void sendEmail(String mesage, String address){
            System.out.println("SmartPhone: Sending Email");
            //Envía el mensaje por email
        }
        
        public String retrieveEmail(){
            System.out.println("SmartPhone: Retrieving Email");
            String messages = new String();
            //Retorna un String que contiene todos los mensajes
            return messages;
        }
        
        public boolean isRinging(){
            System.out.println("SmartPhone: Cheking if phone is ringing");
            boolean ringing = false;
            //Comprueba la actividad del email y solo continua cuando esté realizada
            //Comprueba si el teléfono está sonando y establece la variable ringing
            return ringing;
        }
        
    }
    

    La clase SmartPhone representa un smartphone. Esta clase es extendida por la clase Phone y hereda su funcionalidad. La clase SmartPhone tiene un método sendEmail() que es usado para enviar un mensaje por email. Tiene un método llamado retrieveEmail() que retornará como un String todos los mensajes que se han recuperado. Esta clase también contiene el método isRinging() que sustituye el método isRinging() de la superclase Phone. Similar a la clase Phone, la clase SmartPhone imprime por la salida estándar el nombre de la clase y la función que realizará cuando entre al método.
    La clase final se llama Phone Tester. La clase tiene un método main para mostrar el programa. Esta clase prueba todos los métodos de las clases Phone y SmartPhone:

    public class Tester {
        public static void main(String[] args){
            new Tester();
        }
        
        public Tester(){
            Phone landLinePhone = new Phone();
            SmartPhone smartPhone = new SmartPhone();
            System.out.println("About to test a land line phone as a phone...");
            testPhone(landLinePhone);
            System.out.println("nAbout to test a smart phone as a phone...");
            testPhone(smartPhone);
            System.out.println("nAbout to test a smart phone as a smart phone...");
            testSmartPhone(smartPhone);
        }
        
        private void testPhone (Phone phone){
                phone.callNumber(555986944);
                phone.isRinging();
            }
            
        private void testSmartPhone(SmartPhone phone){
                phone.sendEmail("hi", "edward@scjaexam.com");
                phone.retrieveEmail();
            }
    }
    

    EL método main() comienza creando un objeto Tester y luego llamando al constructor PhoneTester(). El constructor es usado para llamar a cada método.
    Entre cada llamada a los métodos, imprime una linea por la salida estandar para indicar que se está haciendo. El método testPhone() es usado para probar cada método de la calse Phone. Este acepta un objeto Phone como argumento. El método final es testSmartPhone(). Este método purba cada método de la clase SmartPhone.
    El constructor PhoneTester() empieza creando 2 variables locales. La primera se llama landLinePhone y es un objeto Phone. El segundo es llamado smartPhone y es un objeto SmartPhone. El constructor entonces muestra un mensaje y llama al método testPhone() con el método con la variable landLinePhone como argumento.
    Despues el constructor muestra otro mensaje y vuelve a llamar al método testPhone(), pasando el objeto SmartPhone como argumento. El método testPhone() requiere un objeto Phone como argumento, pero en el ejemplo se ha usado el objeto SmartPhone en su lugar. Esto es el Polimorfismo.
    Un smartphone es un tipo específico de teléfono, por lo tanto puede hacer lo mismo que un objeto Phone e incluso mas cosas. Esto es representado en la clase SmartPhone heredando a Phone. Es muy importante ver que el método testPhone() esta esperando un objeto Phone, aunque es perfectamente aceptable el objeto SmartPhone. Sin embargo, algún método adicional de la clase mas específica no podrá ser usado. Desde que el método ha sido diseñado para un objeto Phone como argumento, solo se pueden usar métodos declarados en la clase Phone.
    Finalmente el constructor muestra otro mensaje de estado y llama al método testSmartPhone(). Este método prueba los métodos cerados en el objeto SmarthPhone. Como el polimorfismo es unidireccional, el método testSmarthPhone() no puede ser llamado con un objeto Phone como argumento.

    About to test a land line phone as a phone...
    Phone: Calling number 555986944
    Phone: Checking if phone is ringing
    
    About to test a smart phone as a phone...
    Phone: Calling number 555986944
    SmartPhone: Cheking if phone is ringing
    
    About to test a smart phone as a smart phone...
    SmartPhone: Sending Email
    SmartPhone: Retrieving Email
    

    Cuando la variable landLinePhone es usada con el método testPhone(), la salida es simplemente generada desde al clase Phone. Cuando la variable smartPhone es usada con el método testPhone(), el flujo de ejecución es mas complejo. Como la clase SMartPhone hereda de la clase Phone, los métodos heredados son callNumber() y isRinging(). Cuando se inicia el método callNumber(), en un objeto SmartPhone, se usaría el método de la clase Phone si este no esta sustituido. Sin embargo, cuando el método isRinging es llamado, el método en la clase SmartPhone es usado. Esto sigue las reglas básicas de la herencia y los métodos ustituidos.

  • Examples of Polymorphism via Implementing Interfaces (Ejemplos de polimorfismo implementando Interfaces)

    Este ejemplo se enfocará en la habilidad de un objeto para comportarse como una interface que que esta clase implemente. Esto permite a los objetos ser radicalmente diferentes, pero compartir funcionalidades comunes, para ser tratados de manera similar. La funcionalidad común está definida en una interface que cada clase debe implementar.
    este ejemploe stá compuesto de tres clases y uan itnerface. Hay una clase InterfaceTester para probar un programa. Las otras 2 clases son objetos representando una cabra y una caja. Ambos en el programa y conceptualmente, son objetos muy diferentes. Una cabra es un animal vivo y una caja puede describirse con solo el nombre de la clase. Esta funcionalidad ha sido reflejada en el caso de que ambos implementan la interface Describable.
    Las clases que implementen esta interface requieren que se implemente el método getDescription(). Lo siguiente es la interface Describable:

    public interface Describable {
        public String getDescription();
    }
    

    Esta interface solo tiene un método. El método getDescription() es usado para retornar una descripció sobre el objeto. Cualquier clase que implemente esta interface tiene la posibilidad de retornar la descripción. La clase Goat sería la siguiente:

    public class Goat implements Describable{
    
        private String description;
        
        public Goat(String name){
            description = "A goat named " + name;
        }
        
        @Override
        public String getDescription() {
            return description;
        }
        
        //Otros métodos para una cabra
        
    }
    

    La clase Goat es una calse simple que puede ser usada para representar una cabra. Esta clase implementa la inteface Describable y luego requiere implementar el método getDescription(). El constructor de la clase Goat tiene un parámetro que se usa para ponerle un nombre a la cabra en la String que le ofrece la descripción. La siguiente clase en este ejemplo es la clase Box:

    public class Box implements Describable{
        
        private String description;
        private int height;
        private int width;
        private int length;
        
        public Box(int height, int width, int length){
            this.height = height;
            this.width = width;
            this.length = length;
            this.description = "A box that is " + height + " high, " + length + " long and " + width + " wide ";
        }
        
        @Override
        public String getDescription() {
            return description;
        }
        
        //Implementación de otros métodos para una caja
        
    }
    

    La clase Box está diseñada para modelar una caja. Su constructor requiere las dimensiones de la caja para pasarse como argumento. El conductor tambien crea un texto de descripción que se retorna con el método getDescrioption(). Similar a la clase Goat, esta clase tambien implementa la interface Describable. La clase final es la clase InterfaceTester(). Esta clase es usada para mostrar el concepto de polimorfismo con interfaces:

    public class InterfaceTester {
        
        public static void main(String[] args){
            new InterfaceTester();
        }
        
        public InterfaceTester(){
            Goat goat = new Goat("Bob");
            Box box = new Box (3,5,3);
            Pen pen = new Pen("Blue", "Bic");
            System.out.println(description(goat));
            System.out.println(description(box));
        }
        
        private String description(Describable d){
            return d.getDescription();
        }
    }
    

    La clase InterfaceTester() contiene un método main() que comienza con la ejecución del programa. Esto llama al constructor InterfaceTester() donde se crean objetos Goat y Box. Entonces se usa el método description() que requiere un objeto Describable. Es imposible tener un verdadero objeto Describable ya que es una interface. Sin embargo, las clases que implementan la interface Describable están declarando que tienen una funcionalidad Describable. Estos objetos pueden polimorficamente actuar como si fuesen del tipo de dato Describable. Luego la salida del programa sería:

    A goat named Bob
    A box that is 3 high, 3 long and 5 wide 
    
  • Examples of Programming to an interface (Ejemplos de Programar una nterface)

    Este ejemplo mostrará el concepto de “programming to an interface”. Este concepto permite al desarrollador definir la funcionalidad que es requerida en vez de definir un tipo de objeto actual. Esto crea un código mas flexible que se adiere al principio del diseño orientado a objetos de hacer que el código sea reutilizable.
    Un desarrollador puede crear una clase que es usada para crear archivos de log. Esta clase es responsable de crear y manejar el archivo de log en los archivos dl sistema y añadiendo los mensajes del log a el. Esta clase puede llamarse Logger. La clase Logger tiene un método llamado appendToLog() que acepta un objeto como argumento y añade un mensaje sobre el en el log. El diseñador puede sobrecargar este método con todos los posibles tipos de dato que el programa use. Aunque esto funcionaría, sería ineficiente. Si se usa el principio de “programming to an interface”, el desarrollador crearía en su lugar una interface que requiere la clase Logable para el método. Esta interface podría llamarse Logable. El método appendToLog() usaría la interface Logable como argumento. Cualquier clase que requiera logging podría implementar esta interface y podría ser usada polimorficamente con el método appendToLog(). Lo siguiente es la interface Logable:

    public interface Logable {
        public String getInitInfo();
        public String getLogableEvent();
    }
    

    La interface Logable es una interface básica que define los métodos requeridos para trabajar con el método appendToLog() en la clase Logger. El método appendToLog() no tiene qu estar preocupado con los detalles de un objeto. Usando esta interface el desarrollador ha definido un requisito de funcionalidad en oposición a un tipo de dato estricto. Esto es lo que significa cuando usamos el térmito “Programming to an interface”. La clase Logger se muestra a continuación:

    public class Logger {
        
        private BufferedWriter out;
        
        public Logger() throws IOException{
            out = new BufferedWriter (new FileWriter("logfile.txt"));
        }
        
        public void appendToLog(Logable logable) throws IOException{
            out.write("Object history: " + logable.getInitInfo());
            out.newLine();
            out.write("Object log event: " + logable.getLogableEvent());
            out.newLine();
        }
        
        public void close() throws IOException{
            out.flush();
            out.close();
        }
        
    }
    

    Esta es la clase Logger. Esta clase crea un BufferedWriter, que es una manera de escribir en un archivo. Esto está fuera del objetivo del capítulo, asi que no discutiremos de el. El método appendToLog() es usado para escribir en el archivo. Esta clase usa al interface Logable que lo hace mas flexible. Este método trabajará con cualquier otra clase que implemente esta interface y seguirá el concepto “Programming to an interface”. La siguiente clase es la clase NetworkConnection. Esto es una clase que implementa la interface Logable:

    public class NetworkConnection implements Logable{
    
        private long createdTimestamp;
        private String currentLogMessage;
        
        public NetworkConnection(){
            createdTimestamp = System.currentTimeMillis();
            currentLogMessage = "Initialized";
        }
        
        public void connect(){
            //Estableciendo conexión
            currentLogMessage = "Connected at " +System.currentTimeMillis();
        }
        
        @Override
        public String getInitInfo() {
            return "NetworkConnection object created " + createdTimestamp;
        }
    
        @Override
        public String getLogableEvent() {
            return currentLogMessage;
        }
        
    }
    

    esta clase implementa la interface Logable y todos sus métodos requeridos. Cuando esta clase se comporta polimorficamente como un tipo de dato Logable, el código siendo usado no tiene importancia sobre los detalles de implementación de la clase. Tanto como la clase implemente la interface Logable, es libre de elegir como implementará los métodos. La clase SystemStatus es otra clase que usa la interface Logable:

    public class SystemStatus implements Logable{
    
        private long createdTimestamp;
        
        public SystemStatus(){
            createdTimestamp = System.currentTimeMillis();
        }
        
        private int getStatus(){
            if (System.currentTimeMillis() - createdTimestamp > 10000){
                return 1;
            }else {
                return -1;
            }
        }
        
        @Override
        public String getInitInfo() {
            return "SystemStatus object created " + createdTimestamp;
        }
    
        @Override
        public String getLogableEvent() {
            return String.valueOf("Status: " + getStatus());
        }
    }
    

    La clase SystemStatus solo tiene similitud con la clase NetworkConnection en que ambas implementan la misma interface, Logable. Estas clases eligen como implementar el getInitInfo() y getLogableEvent() de diferente manera que la clase NetWorkConnection. La clase final es la clase LogableTester:

    public class LogableTester {
        public static void main(String[] args) throws Exception{
            new LogableTester();
        }
        
        public LogableTester() throws Exception{
            Logger logger = new Logger();
            SystemStatus systemStatus = new SystemStatus();
            NetworkConnection networkConnection = new NetworkConnection();
            logger.appendToLog(systemStatus);
            logger.appendToLog(networkConnection);
            networkConnection.connect();
            Thread.sleep(20000);
            logger.appendToLog(systemStatus);
            logger.appendToLog(networkConnection);
            logger.close();
        }
    }
    

    La clase LogableTester hace todo el trabajo en el constructor. La clase crea un nuevo objeto Logger llamado logger. Tambien crea un objeto SystemStatus nombrado systemStatus y un objeto NetworkConnection llamado networkConnection. Tambien usa el método appendToLog() del objeto Logger. Este método usa el objeto Logable como parámetro. Desce que ambos usan la misma interface, los objetos pueden usarse poliformicamente con este método:

    Object history: SystemStatus object created 1343927588340
    Object log event: Status: -1
    Object history: NetworkConnection object created 1343927588341
    Object log event: Initialized
    Object history: SystemStatus object created 1343927588340
    Object log event: Status: 1
    Object history: NetworkConnection object created 1343927588341
    Object log event: Connected at 1343927588341
    

Hasta aquí llega el capítulo del polimorfismo, una práctica que deberemos practicar para lograr entenderlo a la perfección y no tener dudas al aplicarla, pues nos ahorra mucho código y lo hace todo mas flexible.

Cualquier aporte o corrección es bienvenida.

Saludos!!!