Patrones de dise帽o creacional en Java

    Visi贸n general

    Este es el primer art铆culo de una breve serie dedicada a los patrones de dise帽o en Java.

    Patrones de creaci贸n

    Los patrones de creaci贸n en Java que se tratan en este art铆culo son:

    • M茅todo / plantilla de f谩brica
    • F谩brica abstracta
    • Constructor
    • Prototipo
    • 煤nico

    M茅todo de f谩brica

    El m茅todo de f谩brica, tambi茅n llamado patr贸n de f谩brica, es un patr贸n de dise帽o ampliamente utilizado que ordena la creaci贸n de objetos.

    En este patr贸n, se crea una clase Factory como la clase padre de todas las subclases que pertenecen a un determinado segmento l贸gico de clases relacionadas.

    Como un SessionFactory se utiliza para crear, actualizar, eliminar y manipular todos Session objetos, tambi茅n lo es cualquier otra f谩brica responsable de su conjunto de clases secundarias.

    Es importante tener en cuenta que no se puede llegar a las subclases sin utilizar su f谩brica respectiva. De esta forma, su creaci贸n queda oculta al cliente y depende de la f谩brica.

    Implementaci贸n:

    Construyamos un proyecto peque帽o y simple para demostrar esto.

    Vamos a definir algunas clases que pertenecen a un segmento l贸gico, cada una de las cuales implementa la misma interfaz. Luego crearemos una f谩brica para estos objetos.

    public interface Animal {
        void eat();    
    }
    

    La interfaz solo tiene un m茅todo para la conveniencia de presentar el punto.

    Ahora, definamos algunas clases que implementan esta interfaz, cada una a su manera:

     public class Dog implements Animal {
        @Override
        public void eat() {
            System.out.println("Dog is eating, woof!");
        }    
    }
    
    public class Cat implements Animal {
        @Override
        public void eat() {
            System.out.println("Cat is eating, meow!");
        }   
    }
    
    public class Rabbit implements Animal {
        @Override
        public void eat() {
            System.out.println("Rabbit is eating, squeak!");
        } 
    }
    

    Nota: Estas clases son archivos .java separados, est谩n agrupados de esta manera para facilitar la lectura.

    Ahora que tenemos un grupo de clases, podemos designar una f谩brica para ellas:

    public class AnimalFactory {
        
        public Animal getAnimal(String animal) {
            if(animal.equals(null)) return null;
            
            if(animal.equalsIgnoreCase("Dog")) {
                return new Dog();
            } else if(animal.equalsIgnoreCase("Cat")) {
                return new Cat();
            } else if(animal.equalsIgnoreCase("Rabbit")) {
                return new Rabbit();
            }
            return null;        
        }  
    }
    

    De esta forma, tenemos una f谩brica para instanciar nuestros objetos de una manera predefinida por la f谩brica, sin contacto directo con los objetos en s铆.

    Ahora, observemos el resultado.

    public class Main {
        public static void main(String[] args) {
            AnimalFactory animalFactory = new AnimalFactory();
          
            Animal animal = animalFactory.getAnimal("dOg");
            animal.eat();
          
            Animal animal2 = animalFactory.getAnimal("CAT");
            animal2.eat();
          
            Animal animal3 = animalFactory.getAnimal("raBbIt");
            animal3.eat();
        }
    }
    
    

    Ejecutar este fragmento de c贸digo producir谩:

    Dog is eating, woof!
    Cat is eating, meow!
    Rabbit is eating, squeak!
    

    Si desea leer un art铆culo detallado independiente sobre el patr贸n de dise帽o del m茅todo de f谩brica, 隆lo tenemos cubierto!

    F谩brica abstracta

    los F谩brica abstracta patr贸n de dise帽o se basa en el Patr贸n de f谩brica y act煤a como la f谩brica m谩s alta de la jerarqu铆a. Representa la pr谩ctica de crear un f谩brica de f谩bricas.

    Este patr贸n es responsable de crear todas las dem谩s f谩bricas como sus subclases, exactamente como las f谩bricas son responsables de crear todas sus propias subclases.

    Implementaci贸n:

    El ejemplo anterior se puede utilizar como una buena base para esta implementaci贸n.

    los Animal se cambia el nombre de la interfaz a Pet interfaz y cada implementaci贸n se cambia:

    public class Dog implements Pet {
        @Override
        public void eat() {
            System.out.println("Dog is eating, woof!");
        }
    }
    
    public class Cat implements Pet {
        @Override
        public void eat() {
            System.out.println("Cat is eating, meow!");
        } 
    }
    
    public class Rabbit implements Pet {
        @Override
        public void eat() {
            System.out.println("Rabbit is eating, squeak!");
        }  
    }
    

    Se define una nueva interfaz:

    public interface Human {
        public void feedPet();
    }
    

    Y como de costumbre, algunas clases concretas implementan esta interfaz:

    public class Child implements Human {
        @Override
        public void feedPet() {
            System.out.println("Child is feeding pet irresponsibly.");
        }
    }
    
    public class Adult implements Human {
        @Override
        public void feedPet() {
            System.out.println("Adult is feeding pet responsibly.");
        }
    }
    
    public class Elder implements Human {
        @Override
        public void feedPet() {
            System.out.println("Elder is overfeeding the pet.");
        } 
    }
    

    En este punto, tenemos las clases adecuadas para crear un AbstractFactory as铆 como las respectivas clases Factory para estos dos grupos: PetFactory y HumanFactory.

    los AbstractFactoryLa preocupaci贸n es la capacidad de proporcionar estos objetos a la FactoryProducer, no para instanciarlos:

    public abstract class AbstractFactory {
        public abstract Pet getPet(String pet);
        public abstract Human getHuman(String human);
    }
    

    Antes de definir la clase que instancia estos objetos usando el AbstractFactory, necesitamos crear nuestras dos f谩bricas.

    public class HumanFactory extends AbstractFactory {
    
        @Override
        Human getHuman(String human) {
            if(human.equals(null)) return null;
          
            if(human.equalsIgnoreCase("chILd")) {
                return new Child();
            } else if(human.equalsIgnoreCase("adult")) {
                return new Adult();
            } else if(human.equalsIgnoreCase("elDeR")) {
                return new Elder();
            }
            return null;
        }
        
        @Override
        Pet getPet(String pet) {
            // don't implement
            return null;
        }
    
    public class PetFactory extends AbstractFactory {
        
        @Override
        public Pet getPet(String pet) {
            if(pet.equals(null)) return null;
            
            if(pet.equalsIgnoreCase("Dog")) {
                return new Dog();
            } else if(pet.equalsIgnoreCase("Cat")) {
                return new Cat();
            } else if(pet.equalsIgnoreCase("Rabbit")) {
                return new Rabbit();
            }
            return null;        
        }
    
        @Override
        Human getHuman(String human) {
            //don't implement
            return null;
        }
    }
    

    Y ahora, con estos, podemos crear el FactoryProducer que tiene la responsabilidad de instanciar las f谩bricas adecuadas, con la ayuda del AbstractFactory:

    public class FactoryProducer {
        public static AbstractFactory getFactory(String factory) {
            if(factory.equalsIgnoreCase("Human")) {
                return new HumanFactory();
            } else if(factory.equalsIgnoreCase("Pet")) {
                return new PetFactory();
            }
            return null;   
        }
    }
    

    Pasando un String, la FactoryProducer devuelve el AbstractFactory con su f谩brica infantil solicitada.

    Ahora, observemos el resultado:

    public class Main {
        public static void main(String[] args) {
    
            AbstractFactory humanFactory = FactoryProducer.getFactory("Human");
            AbstractFactory petFactory = FactoryProducer.getFactory("Pet");
            
            Human human = humanFactory.getHuman("Child");
            human.feedPet();
            
            Pet pet = petFactory.getPet("Dog");
            pet.eat();
            
            Human human2 = humanFactory.getHuman("Elder");
            human2.feedPet();
            
            Pet pet2 = petFactory.getPet("Rabbit");
            pet2.eat();
        }
    }
    

    Al ejecutar este fragmento de c贸digo, somos recibidos con:

    Child is feeding pet irresponsibly.
    Dog is eating, woof!
    Elder is overfeeding the pet.
    Rabbit is eating, squeak!
    

    Constructor

    El patr贸n Builder se utiliza para ayudar a construir objetos finales, para clases con una gran cantidad de campos o par谩metros, paso a paso. No es muy 煤til en clases peque帽as y simples que no tienen muchos campos, pero los objetos complejos son dif铆ciles de leer y mantener por s铆 mismos.

    Inicializar un objeto con m谩s de unos pocos campos usando un constructor es complicado y susceptible a errores humanos.

    Implementaci贸n:

    Definamos una clase con algunos campos:

    public class Computer {
        private String computerCase;
        private String CPU;
        private String motherboard;
        private String GPU;
        private String HDD;
        private String operatingSystem;
        private int powerSupply;
        private int amountOfRAM;
       
        public Computer(String computerCase, String CPU, String motherboard, String GPU, 
        String HDD, String operatingSystem, int powerSupply, int amountOfRAM) {
            this.computerCase = computerCase;
            this.CPU = CPU;
            this.motherboard = motherboard;
            this.GPU = GPU;
            this.HDD = HDD;
            this.operatingSystem = operatingSystem;
            this.powerSupply = powerSupply;
            this.amountOfRAM = amountOfRAM;
       }
    
        //getters and setters
    }
    

    El problema es evidente: incluso una clase peque帽a y simple como esta requiere un constructor grande y desordenado.

    Las clases pueden tener f谩cilmente muchos m谩s campos que este, lo que dio origen al patr贸n de dise帽o Builder.

    Para aplicarlo, anidaremos un static Builder clase dentro de la Computer clase.

    Este constructor se utilizar谩 para construir nuestros objetos de una manera limpia y legible, a diferencia del ejemplo anterior:

    public class Computer {
        
       public static class Builder {
           private String computerCase;
           private String CPU;
           private String motherboard;
           private String GPU;
           private String HDD;
           private String operatingSystem;
           private int powerSupply;
           private int amountOfRAM;
            
           public Builder withCase(String computerCase) {
               this.computerCase = computerCase;
               return this;
            }
            
            public Builder withCPU(String CPU) {
                this.CPU = CPU;
                return this;
            }
            
            public Builder withMotherboard(String motherboard) {
                this.motherboard = motherboard;
                return this;
            }
            
            public Builder withGPU(String GPU) {
                this.GPU = GPU;
                return this;
            }
            
            public Builder withHDD(String HDD) {
                this.HDD = HDD;
                return this;
            }
            
            public Builder withOperatingSystem(String operatingSystem) {
                this.operatingSystem = operatingSystem;
                return this;
            }
            
            public Builder withPowerSupply(int powerSupply) {
                this.powerSupply = powerSupply;
                return this;
            }
            
            public Builder withAmountOfRam(int amountOfRAM) {
                this.amountOfRAM = amountOfRAM;
                return this;
            }
            
            public Computer build() {
                Computer computer = new Computer();
                computer.computerCase = this.computerCase;
                computer.CPU = this.CPU;
                computer.motherboard = this.motherboard;
                computer.GPU = this.GPU;
                computer.HDD = this.HDD;
                computer.operatingSystem = this.operatingSystem;
                computer.powerSupply = this.powerSupply;
                computer.amountOfRAM = this.amountOfRAM;
                
                return computer;
            }
       }
       
       private Computer() {
           //nothing here
       }
       
        //fields
        //getters and setters
    }
    

    Esta clase anidada tiene los mismos campos que la Computer class y los usa para construir el objeto en s铆.

    los Computer constructor se hace privado de modo que la 煤nica forma de inicializarlo es a trav茅s del Builder clase.

    Con el Builder toda la configuraci贸n, podemos inicializar Computer objetos:

    public class Main {
        public static void main(String[] args) {
            Computer computer = new Computer.Builder()
                    .withCase("Tower")
                    .withCPU("Intel i5")
                    .withMotherboard("MSI B360M-MORTAR")
                    .withGPU("nVidia Geforce GTX 750ti")
                    .withHDD("Toshiba 1TB")
                    .withOperatingSystem("Windows 10")
                    .withPowerSupply(500)
                    .withAmountOfRam(8)
                    .build();
        }
    }
    

    Esta es una forma mucho m谩s limpia y detallada que escribir:

    public class Main {
        public static void main(String[] args) {
            Computer computer = new Computer("Tower", "Intel i5", "MSI B360M-MORTAR",  
            "nVidia GeForce GTX 750ti, "Toshiba 1TB", "Windows 10", 500, 8);
        }
    }
    

    Si desea leer un art铆culo independiente y detallado sobre el patr贸n de dise帽o del constructor, 隆lo tenemos cubierto!

    Prototipo

    El patr贸n de prototipo se utiliza principalmente para minimizar el costo de creaci贸n de objetos, generalmente cuando las aplicaciones a gran escala crean, actualizan o recuperan objetos que cuestan muchos recursos.

    Esto se hace copiando el objeto, una vez creado, y reutilizando la copia del objeto en solicitudes posteriores, para evitar realizar otra operaci贸n con muchos recursos. Depende de la decisi贸n del desarrollador si se trata de una copia completa o superficial del objeto, aunque el objetivo es el mismo.

    Implementaci贸n:

    Dado que este patr贸n clona objetos, ser铆a apropiado definir una clase para ellos:

    // to clone the object, the class needs to implement Cloneable
    public abstract class Employee implements Cloneable { 
    
        private String id;
        protected String position;
        private String name;
        private String address;
        private double wage;
        
        abstract void work();
        
        public Object clone() {
            Object clone = null;
            try {
                clone = super.clone();
            } catch(CloneNotSupportedException ex) {
                ex.printStackTrace();
            }
            return clone;
        }
       //getters and setters
    }
    

    Ahora, como de costumbre, definamos algunas clases que se extienden Employee:

    public class Programmer extends Employee {
        public Programmer() {
            position = "Senior";
        } 
        @Override
        void work() {
            System.out.println("Writing code!");
        }   
    }
    
    public class Janitor extends Employee {
        public Janitor() {
            position = "Part-time";
        }
        @Override
        void work() {
            System.out.println("Cleaning the hallway!");
        } 
    }
    
    public class Manager extends Employee {
        public Manager() {
            position = "Intern";
        }
        @Override
        void work() {
            System.out.println("Writing a schedule for the project!");
        }  
    }
    

    En este punto, tenemos todo lo que necesitamos para una clase de una capa de datos para guardar, actualizar y recuperar estos empleados por nosotros.

    UN Hashtable se utilizar谩 para simular una base de datos, y los objetos predefinidos simular谩n los objetos recuperados mediante consultas:

    public class EmployeesHashtable {
        
        private static Hashtable<String, Employee> employeeMap = new Hashtable<String, Employee>();
        
        public static Employee getEmployee(String id) {
            Employee cacheEmployee = employeeMap.get(id);
            // a cast is needed because the clone() method returns an Object
            return (Employee) cacheEmployee.clone();
        }
        
        public static void loadCache() {
            // predefined objects to simulate retrieved objects from the database
            Programmer programmer = new Programmer();
            programmer.setId("ETPN1");
            employeeMap.put(programmer.getId(), programmer);
            
            Janitor janitor = new Janitor();
            janitor.setId("ETJN1");
            employeeMap.put(janitor.getId(), janitor);
            
            Manager manager = new Manager();
            manager.setId("ETMN1");
            employeeMap.put(manager.getId(), manager);
        }
    }
    

    Para observar el resultado:

    public class Main {
        public static void main(String[] args) {
            EmployeesHashtable.loadCache();
            
            Employee cloned1 = (Employee) EmployeesHashtable.getEmployee("ETPN1");
            Employee cloned2 = (Employee) EmployeesHashtable.getEmployee("ETJN1");
            Employee cloned3 = (Employee) EmployeesHashtable.getEmployee("ETMN1");
            
            System.out.println("Employee: " + cloned1.getPosition() + " ID:" 
                + cloned1.getId());
            System.out.println("Employee: " + cloned2.getPosition() + " ID:" 
                + cloned2.getId());
            System.out.println("Employee: " + cloned3.getPosition() + " ID:"                 
                + cloned3.getId());
        }
    }
    

    Ejecutar este fragmento de c贸digo producir谩:

    Employee: Senior ID:ETPN1
    Employee: Part-time ID:ETJN1
    Employee: Intern ID:ETMN1
    

    煤nico

    El patr贸n Singleton asegura la existencia de una sola instancia de objeto en toda la JVM.

    Este es un patr贸n bastante simple y proporciona la capacidad de acceder a este objeto incluso sin instanciarlo. Otros patrones de dise帽o usan este patr贸n, como los patrones Abstract Factory, Builder y Prototype que ya hemos cubierto.

    Implementaci贸n:

    Esta es una implementaci贸n bastante simple de una clase Singleton:

    public class SingletonClass {
        
        private static SingletonClass instance = new SingletonClass();
       
        private SingletonClass() {}
        
        public static SingletonClass getInstance() {
            return instance;
        }
        
        public void showMessage() {
            System.out.println("I'm a singleton object!");   
        }
    }
    
    

    Esta clase est谩 creando un objeto est谩tico de s铆 misma, que representa la instancia global.

    Al proporcionar un constructor privado, no se puede crear una instancia de la clase.

    Un m茅todo est谩tico getInstance() se utiliza como punto de acceso global para el resto de la aplicaci贸n.

    Se puede agregar cualquier cantidad de m茅todos p煤blicos a esta clase, pero no es necesario hacerlo para este tutorial.

    Con esto, nuestra clase cumple con todos los requisitos para convertirse en Singleton.

    Definamos un c贸digo que recupere este objeto y ejecute un m茅todo:

    public class Main {
        public static void main(String[] args) {
            SingletonClass singletonClass = SingletonClass.getInstance();
            singletonClass.showMessage();
        }
    }
    

    Ejecutar este c贸digo resultar谩 en:

    I'm a singleton object!
    

    Conclusi贸n

    Con esto, todos Patrones de dise帽o creacional en Java est谩n completamente cubiertos, con ejemplos de trabajo.

    Si desea continuar leyendo acerca de los patrones de dise帽o en Java, el siguiente art铆culo cubre los patrones de dise帽o estructural.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *