Patrones de dise帽o estructural en Java

    Visi贸n general

    Este es el segundo art铆culo de una serie corta dedicada a los patrones de dise帽o en Java, y una continuaci贸n directa del art铆culo anterior: Patrones de dise帽o creacionales en Java.

    Patrones estructurales

    Los patrones estructurales se preocupan por brindar soluciones y est谩ndares eficientes con respecto a la composici贸n de clases y estructuras de objetos. Adem谩s, se basan en el concepto de herencia y las interfaces para permitir que m煤ltiples objetos o clases trabajen juntos y formen un solo todo funcional.

    Los patrones estructurales en Java que se tratan en este art铆culo son:

    • Adaptador
    • Puente
    • Filtrar
    • Compuesto
    • Decorador
    • Fachada
    • Peso mosca
    • Apoderado

    Adaptador

    El patr贸n Adaptador, como su nombre lo indica, adapta una interfaz a otra. Act煤a como un puente entre dos interfaces no relacionadas y, a veces, incluso completamente incompatibles, similar a c贸mo un esc谩ner act煤a como un puente entre un papel y una computadora.

    Una computadora no puede almacenar un papel como un documento PDF, pero un esc谩ner, que combina las funcionalidades de ambos, puede escanearlo y permitir que la computadora lo almacene.

    Implementaci贸n

    los Builder La interfaz es nuestra interfaz m谩s general y proporciona un m茅todo que acepta un tipo de edificio y su ubicaci贸n:

    public interface Builder {
        public void build(String type, String location);
    }
    

    los AdvancedBuilder La interfaz proporciona dos m茅todos, uno para construir una casa y otro para construir un rascacielos:

    public interface AdvancedBuilder {
        public void buildHouse(String location);
        public void buildSkyscrapper(String location);
    }
    

    Estas dos interfaces no est谩n relacionadas. S铆, comparten el tema, pero no est谩n relacionados en lo que al c贸digo se refiere.

    En este punto, una clase concreta que implementa el AdvancedBuilder se crea la interfaz:

    public class HouseBuilder implements AdvancedBuilder {
        @Override
        public void buildHouse(String location) {
            System.out.println("Building a house located in the " + location + "area!");
        }
    
        @Override
        public void buildSkyscrapper(String location) {
            //don't implement
        }
    }
    

    Y por supuesto, por la misma analog铆a, se crea otra clase concreta:

    public class SkyscrapperBuilder implements AdvancedBuilder {
        @Override
        public void buildSkyscrapper(String location) {
            System.out.println("Building a skyscrapper in the " + location + "area!");
        }
        
        @Override
        public void buildHouse(String location) {
            //don't implement
        }
    }
    
    

    Aqu铆 viene la parte del adaptador: para conectar estas dos interfaces, BuilderAdapter implementar Builder est谩 hecho:

    public class BuilderAdapter implements Builder {
        AdvancedBuilder advancedBuilder;
    
        public BuilderAdapter(String type) {
            if(type.equalsIgnoreCase("House")) {
                advancedBuilder = new HouseBuilder();
            } else if(type.equalsIgnoreCase("Skyscrapper")) {
                advancedBuilder = new SkyscrapperBuilder();
            }
        }
    
        @Override
        public void build(String type, String location) {
            if(type.equalsIgnoreCase("House")) {
                advancedBuilder.buildHouse(location);
            } else if(type.equalsIgnoreCase("Skyscrapper")) {
                advancedBuilder.buildSkyscrapper(location);
            }
        }
    }
    

    Con el adaptador funcionando, finalmente podemos implementar la soluci贸n y usar el Builder m茅todo de interfaz con el BuilderAdapter para construir los tipos de edificios compatibles.

    public class BuilderImplementation implements Builder {
        BuilderAdapter builderAdapter;
    
        @Override
        public void build(String type, String location) {
            if(type.equalsIgnoreCase("House") || type.equalsIgnoreCase("Skyscrapper")) {
                builderAdapter = new BuilderAdapter(type);
                builderAdapter.build(type, location);
            } else {
                System.out.println("Invalid building type.");
            }
        }
    }
    

    Y para observar el resultado:

    public class Main {
        public static void main(String[] args) {
            BuilderImplementation builderImpl = new BuilderImplementation();
            
            builderImpl.build("house", "Downtown");
            builderImpl.build("Skyscrapper", "City Center");
            builderImpl.build("Skyscrapper", "Outskirts");
            builderImpl.build("Hotel", "City Center");
        }
    }
    

    Ejecutar el fragmento de c贸digo anterior producir谩:

    Building a house located in the Downtown area!
    Building a skyscrapper in the City Center area!
    Building a skyscrapper in the Outskirts area!
    Invalid building type.
    

    Puente

    El patr贸n Bridge se utiliza para segregar clases abstractas de sus implementaciones y actuar como puente entre ellas. De esta forma, tanto la clase abstracta como la implementaci贸n pueden cambiar estructuralmente sin afectar a la otra.

    Si esto es de alguna manera confuso, consulte la implementaci贸n para ver su uso.

    Implementaci贸n

    Como de costumbre, una interfaz es el punto de partida:

    public interface FeedingAPI {
        public void feed(int timesADay, int amount, String typeOfFood);
    }
    

    Despu茅s de lo cual, dos clases concretas lo implementan:

    public class BigDog implements FeedingAPI {
        @Override
        public void feed(int timesADay, int amount, String typeOfFood) {
            System.out.println("Feeding a big dog, " + timesADay + " times a day with " + 
                amount + " g of " + typeOfFood);
        }
    }
    
    public class SmallDog implements FeedingAPI {
        @Override
        public void feed(int timesADay, int amount, String typeOfFood) {
            System.out.println("Feeding a small dog, " + timesADay + " times a day with " + 
                amount + " g of " + typeOfFood);
        }
    }
    
    

    Utilizando el FeedingAPI interfaz, un resumen Animal se crea la clase:

    public abstract class Animal {
        protected FeedingAPI feedingAPI;
        
        protected Animal(FeedingAPI feedingAPI) {
            this.feedingAPI = feedingAPI;
        }
        public abstract void feed();
    }
    

    Aqu铆 es donde entra en juego el patr贸n de puente. Se crea una clase de puente que segrega el resumen Animal class desde su implementaci贸n:

    public class Dog extends Animal{
        private int timesADay, amount;
        private String typeOfFood;
        
        public Dog(int timesADay, int amount, String typeOfFood, FeedingAPI feedingAPI) {
            super(feedingAPI);
            this.timesADay = timesADay;
            this.amount = amount;
            this.typeOfFood = typeOfFood;
        }
        
        public void feed() {
            feedingAPI.feed(timesADay, amount, typeOfFood);
        }
    }
    

    Y para observar el resultado:

    public class Main {
        public static void main(String[] args) {
            Animal bigDog = new Dog(3, 500, "Meat", new BigDog());
            Animal smallDog = new Dog(2, 250, "Granules", new SmallDog());
            
            bigDog.feed();
            smallDog.feed();
        }
    }
    

    Ejecutar este fragmento de c贸digo producir谩:

    Feeding a big dog, 3 times a day with 500 g of Meat
    Feeding a small dog, 2 times a day with 250 g of Granules
    

    Filtrar

    El patr贸n de filtro se usa cuando necesitamos una forma de filtrar a trav茅s de conjuntos de objetos con diferentes criterios personalizados. Podemos encadenar criterios para un filtro a煤n m谩s estrecho, lo que se hace de forma desacoplada.

    Implementaci贸n

    Comenzando con un Employee clase que filtraremos usando diferentes Criteria:

    public class Employee {
        private String name;
        private String gender;
        private String position;
        
        public Employee(String name, String gender, String position) {
            this.name = name;
            this.gender = gender;
            this.position = position;
        }
        //getters
    }
    

    los Criteria La interfaz es bastante simple y todos los dem谩s criterios espec铆ficos implementar谩n su m茅todo a su manera:

    public interface Criteria {
        public List<Employee> criteria(List<Employee> employeeList);
    }
    

    Con la base del sistema de filtrado en su lugar, definamos algunos criterios diferentes:

    • CriteriaMale – Un criterio para buscar empleados varones
    • CriteriaFemale – Un criterio para buscar empleadas
    • CriteriaSenior – Un criterio para buscar empleados senior
    • CriteriaJunior – Un criterio para buscar empleados junior
    • AndCriteria – Un criterio para buscar empleados que superen ambos criterios que aplicamos
    • OrCriteria – Un criterio para buscar empleados que aprueben cualquiera de los criterios que aplicamos.

    Criterios Masculino:

    public class CriteriaMale implements Criteria {
    
        @Override
        public List<Employee> criteria(List<Employee> employeeList) {
            List<Employee> maleEmployees = new ArrayList<>();
            
            for(Employee employee : employeeList) {
                if(employee.getGender().equalsIgnoreCase("Male")) {
                    maleEmployees.add(employee);
                } 
            }
            return maleEmployees;
        }
    }
    

    Simple for bucle que agrega a todos los empleados masculinos a una lista y la devuelve.

    Criterios Femenino:

    public class CriteriaFemale implements Criteria {
    
        @Override
        public List<Employee> criteria(List<Employee> employeeList) {
            List<Employee> femaleEmployees = new ArrayList<>();
    
            for(Employee employee : employeeList) {
                if(employee.getGender().equalsIgnoreCase("Female")) {
                    femaleEmployees.add(employee);
                }
            }
            return femaleEmployees;
        }    
    }
    

    Igual que el anterior, pero para empleadas.

    Criterios Senior:

    public class CriteriaSenior implements Criteria{
    
        @Override
        public List<Employee> criteria(List<Employee> employeeList) {
             List<Employee> seniorEmployees = new ArrayList<>();
    
            for(Employee employee : employeeList) {
                if(employee.getPosition().equalsIgnoreCase("Senior")) {
                    seniorEmployees.add(employee);
                }
            }
            return seniorEmployees;
        }    
    }
    

    Igual que el anterior, pero verifica la posici贸n del empleado, no el g茅nero.

    CriteriaJunior:

    public class CriteriaJunior implements Criteria {
    
        @Override
        public List<Employee> criteria(List<Employee> employeeList) {
                     List<Employee> juniorEmployees = new ArrayList<>();
    
            for(Employee employee : employeeList) {
                if(employee.getPosition().equalsIgnoreCase("Junior")) {
                    juniorEmployees.add(employee);
                }
            }
            return juniorEmployees;
        } 
    }
    

    Igual que el anterior, pero para empleados Junior.

    AndCriteria:

    public class AndCriteria implements Criteria {
        
        private Criteria firstCriteria;
        private Criteria secondCriteria;
        
        public AndCriteria(Criteria firstCriteria, Criteria secondCriteria) {
            this.firstCriteria = firstCriteria;
            this.secondCriteria = secondCriteria;
        }
    
        @Override
        public List<Employee> criteria(List<Employee> employeeList) {
            List<Employee> firstCriteriaEmployees = firstCriteria.criteria(employeeList);
            return secondCriteria.criteria(firstCriteriaEmployees);
        }
    }
    

    La lista de empleados se filtra por el primer criterio, y luego la lista ya filtrada se filtra nuevamente, con el segundo criterio.

    O Criterios:

        private Criteria firstCriteria;
        private Criteria secondCriteria;
        
        public OrCriteria(Criteria firstCriteria, Criteria secondCriteria) {
            this.firstCriteria = firstCriteria;
            this.secondCriteria = secondCriteria;
        }
        
        
        @Override
        public List<Employee> criteria(List<Employee> employeeList) {
            List<Employee> firstCriteriaEmployees = firstCriteria.criteria(employeeList);
            List<Employee> secondCriteriaEmployees = secondCriteria.criteria(employeeList);
            
            for (Employee employee : secondCriteriaEmployees) {
                if(!firstCriteriaEmployees.contains(employee)) {
                    firstCriteriaEmployees.add(employee);
                }
            }
            return firstCriteriaEmployees;
        }
    }
    

    Se elaboran dos listas de empleados, seg煤n los criterios individuales. Si la primera lista no contiene un empleado que la segunda lista contiene, el empleado se agrega a la lista.

    De esta forma, ambas listas se fusionan pr谩cticamente al final.

    Ahora que todos los Criteria implementadas, hagamos una lista de empleados que actuar谩n como una lista recuperada de una base de datos y luego ejecutemos algunos criterios:

    public class Main {
        public static void main(String[] args) {
            List<Employee> employeeList = new ArrayList<>();
            
            //adding employees to the list
            employeeList.add(new Employee("David", "Male", "Senior"));
            employeeList.add(new Employee("Scott", "Male", "Senior"));
            employeeList.add(new Employee("Rhett", "Male", "Junior"));
            employeeList.add(new Employee("Andrew", "Male", "Junior"));
            employeeList.add(new Employee("Susan", "Female", "Senior"));
            employeeList.add(new Employee("Rebecca", "Female", "Junior"));
            employeeList.add(new Employee("Mary", "Female", "Junior"));
            employeeList.add(new Employee("Juliette", "Female", "Senior"));
            employeeList.add(new Employee("Jessica", "Female", "Junior"));
            employeeList.add(new Employee("Mike", "Male", "Junior"));
            employeeList.add(new Employee("Chris", "Male", "Junior"));
            
            //initialization of the different criteria classes
            Criteria maleEmployees = new CriteriaMale();
            Criteria femaleEmployees = new CriteriaFemale();
            Criteria seniorEmployees = new CriteriaSenior();
            Criteria juniorEmployees = new CriteriaJunior();
            //AndCriteria and OrCriteria accept two Criteria as their constructor    
            arguments and return filtered lists
            Criteria seniorFemale = new AndCriteria(seniorEmployees, femaleEmployees);
            Criteria juniorOrMale = new OrCriteria(juniorEmployees, maleEmployees);
            
            System.out.println("Male employees: ");
            printEmployeeInfo(maleEmployees.criteria(employeeList));
            
            System.out.println("nFemale employees: ");
            printEmployeeInfo(femaleEmployees.criteria(employeeList));
            
            System.out.println("nSenior female employees: ");
            printEmployeeInfo(seniorFemale.criteria(employeeList));
            
            System.out.println("nJunior or male employees: ");
            printEmployeeInfo(juniorOrMale.criteria(employeeList));
        }
        
        
        //simple method to print out employee info
        public static void printEmployeeInfo(List<Employee> employeeList) {
            for (Employee employee : employeeList) {
                System.out.println("Employee info: | Name: " 
                        + employee.getName() + ", Gender: " 
                        + employee.getGender() + ", Position: " 
                        + employee.getPosition() + " |");
            }
        }
    }
    
    

    Ejecutar este fragmento de c贸digo producir谩:

    Male employees: 
    Employee info: | Name: David, Gender: Male, Position: Senior |
    Employee info: | Name: Scott, Gender: Male, Position: Senior |
    Employee info: | Name: Rhett, Gender: Male, Position: Junior |
    Employee info: | Name: Andrew, Gender: Male, Position: Junior |
    Employee info: | Name: Mike, Gender: Male, Position: Junior |
    Employee info: | Name: Chris, Gender: Male, Position: Junior |
    
    Female employees: 
    Employee info: | Name: Susan, Gender: Female, Position: Senior |
    Employee info: | Name: Rebecca, Gender: Female, Position: Junior |
    Employee info: | Name: Mary, Gender: Female, Position: Junior |
    Employee info: | Name: Juliette, Gender: Female, Position: Senior |
    Employee info: | Name: Jessica, Gender: Female, Position: Junior |
    
    Senior female employees: 
    Employee info: | Name: Susan, Gender: Female, Position: Senior |
    Employee info: | Name: Juliette, Gender: Female, Position: Senior |
    
    Junior or male employees: 
    Employee info: | Name: Rhett, Gender: Male, Position: Junior |
    Employee info: | Name: Andrew, Gender: Male, Position: Junior |
    Employee info: | Name: Rebecca, Gender: Female, Position: Junior |
    Employee info: | Name: Mary, Gender: Female, Position: Junior |
    Employee info: | Name: Jessica, Gender: Female, Position: Junior |
    Employee info: | Name: Mike, Gender: Male, Position: Junior |
    Employee info: | Name: Chris, Gender: Male, Position: Junior |
    Employee info: | Name: David, Gender: Male, Position: Senior |
    Employee info: | Name: Scott, Gender: Male, Position: Senior |
    

    Compuesto

    El patr贸n compuesto se utiliza cuando necesitamos una forma de tratar un grupo completo de objetos de manera similar o de la misma manera.

    Esto generalmente lo hace la clase que “posee” el grupo de objetos y proporciona un conjunto de m茅todos para tratarlos por igual como si fueran un solo objeto.

    Implementaci贸n

    Empecemos con el Employee clase. Esta clase se instanciar谩 varias veces para formar un grupo de empleados:

    public class Employee {
        private String name;
        private String position;
        private int wage;
        private List<Employee> coworkers;
        
        public Employee(String name, String position, int wage) {
            this.name = name;   
            this.position = position;
            this.wage = wage;
            coworkers = new ArrayList<Employee>();
        }
        
        public void addCoworker(Employee employee) {
            coworkers.add(employee);
        }
        
        public void removeCoworker(Employee employee) {
            coworkers.remove(employee);
        }
        
        public List<Employee> getCoworkers() {
            return coworkers;
        }
        
        public String toString() {
            return "Employee : | Name: " + name + ", Position: " + position + ", Wage: "
                 + wage + " |";
        }
    }
    

    La clase tiene una lista de Employee dentro de 茅l, este es nuestro grupo de objetos que queremos apuntar como un solo objeto.

    public class Pharos.shJavaDesignPatterns {
        public static void main(String[] args) {
            Employee employee1 = new Employee("David", "Programmer", 1500);
            Employee employee2 = new Employee("Scott", "CEO", 3000);
            Employee employee3 = new Employee("Andrew", "Manager", 2000);
            Employee employee4 = new Employee("Scott", "Janitor", 500);
            Employee employee5 = new Employee("Juliette", "Marketing", 1000);
            Employee employee6 = new Employee("Rebecca", "Sales", 2000);
            Employee employee7 = new Employee("Chris", "Programmer", 1750);
            Employee employee8 = new Employee("Ivan", "Programmer", 1200);
    
            employee3.addCoworker(employee1);
            employee3.addCoworker(employee7);
            employee3.addCoworker(employee8);
    
            employee1.addCoworker(employee7);
            employee1.addCoworker(employee8);
            
            employee2.addCoworker(employee3);
            employee2.addCoworker(employee5);
            employee2.addCoworker(employee6);
    
            System.out.println(employee2);
            for (Employee headEmployee : employee2.getCoworkers()) {
                System.out.println(headEmployee);
                
                for(Employee employee : headEmployee.getCoworkers()) {
                    System.out.println(employee);
                }
            }
        }
    }
    

    Aqu铆, se crean instancias de varios empleados. El CEO tiene algunos empleados como compa帽eros de trabajo cercanos, y algunos de ellos tienen sus propios compa帽eros de trabajo cercanos, en posiciones inferiores.

    Al final, los empleados principales son compa帽eros de trabajo cercanos del CEO y los empleados regulares son compa帽eros de trabajo de los empleados principales.

    Ejecutar el c贸digo anterior producir谩:

    Employee : | Name: Scott, Position: CEO, Wage: 3000 |
    Employee : | Name: Andrew, Position: Manager, Wage: 2000 |
    Employee : | Name: David, Position: Programmer, Wage: 1500 |
    Employee : | Name: Chris, Position: Programmer, Wage: 1750 |
    Employee : | Name: Ivan, Position: Programmer, Wage: 1200 |
    Employee : | Name: Juliette, Position: Marketing, Wage: 1000 |
    Employee : | Name: Rebecca, Position: Sales, Wage: 2000 |
    

    Decorador

    El patr贸n Decorator se usa para alterar una instancia individual de una clase en tiempo de ejecuci贸n, creando una clase decoradora que envuelve la clase original.

    De esta manera, cambiar o agregar funcionalidades del objeto decorador no afectar谩 la estructura o las funcionalidades del objeto original.

    Se diferencia de la herencia cl谩sica en el hecho de que se realiza en tiempo de ejecuci贸n y se aplica solo a una instancia individual, mientras que la herencia afectar谩 a todas las instancias y se realiza en tiempo de compilaci贸n.

    Implementaci贸n

    Siguiendo la descripci贸n anterior, definamos una interfaz:

    public interface Computer {
        void assemble();    
    }
    

    Y al implementar esa interfaz, definiremos una clase que, usando el patr贸n Decorator, haremos susceptible de cambiar durante el tiempo de ejecuci贸n:

    public class BasicComputer implements Computer {
        @Override
        public void assemble() {
            System.out.print("Assembling a basic computer.");
        }
    }
    

    Ahora, para la clase de decoradores:

    public abstract class ComputerDecorator implements Computer {
        protected Computer computer;
        
        public ComputerDecorator(Computer computer) {
            this.computer = computer;
        }
        
        @Override
        public void assemble() {
            this.computer.assemble();
        }
    }
    

    Nuestras clases concretas extender谩n esta heredando su funcionalidad y agregando su propia funcionalidad en el proceso:

    public class GamingComputer extends ComputerDecorator {
        public GamingComputer(Computer computer) {
            super(computer);
        }
    
        @Override
        public void assemble() {
            super.assemble();
            System.out.print(" Adding characteristics of a gaming computer! ");
        }
    }
    
    public class WorkComputer extends ComputerDecorator {
        public WorkComputer(Computer computer) {
            super(computer);
        }
    
        @Override
        public void assemble() {
            super.assemble();
            System.out.print(" Adding characteristics of a work computer! ");
        }
    }
    

    Con estas clases concretas completamente definidas, podemos observar el resultado:

    public class Main {
        public static void main(String[] args) {
            Computer gamingComputer = new GamingComputer(new BasicComputer());
            gamingComputer.assemble();
            System.out.println("n");
            
            Computer workComputer = new WorkComputer(new GamingComputer(new 
                BasicComputer()));
            workComputer.assemble();
        }
    }
    

    Ejecutar este fragmento de c贸digo producir谩:

    Assembling a basic computer. Adding characteristics of a gaming computer! 
    
    Assembling a basic computer. Adding characteristics of a gaming computer!  Adding characteristics of a work computer!
    

    Fachada

    El patr贸n Facade proporciona una interfaz simple y de alto nivel para el cliente y le permite acceder al sistema, sin conocer la l贸gica y el funcionamiento interno del sistema.

    Implementaci贸n

    Definiremos un ZooKeeper clase que actuar谩 como una interfaz para el usuario que quiere alimentar a los animales en el zool贸gico.

    Empezamos con un Animal interfaz:

    public interface Animal {
        void feed();
    }
    

    Y clases concretas que lo implementan:

    public class Lion implements Animal {
        @Override
        public void feed() {
            System.out.println("The lion is being fed!");
        }
    }
    
    public class Wolf implements Animal {
        @Override
        public void feed() {
            System.out.println("The wolf is being fed!");
        }    
    }
    
    public class Bear implements Animal {
        @Override
        public void feed() {
            System.out.println("The bear if being fed!");
        }    
    }
    

    Esta es la pista para el ZooKeeper clase:

    public class ZooKeeper {
        private Animal lion;
        private Animal wolf;
        private Animal bear;
        
        public ZooKeeper() {
            lion = new Lion();
            wolf = new Wolf();
            bear = new Bear();
        }
        
        public void feedLion() {
            lion.feed();
        }
        
        public void feedWolf() {
            wolf.feed();
        }
        
        public void feedBear() {
            bear.feed();
        }
    }
    

    Al usar esta interfaz, el cliente no se preocupa por la l贸gica detr谩s de alimentar a los animales.

    Para observar el resultado:

    public class Main {
        public static void main(String[] args) {
            ZooKeeper zookeeper = new ZooKeeper();
            
            zookeeper.feedLion();
            zookeeper.feedWolf();
            zookeeper.feedBear();        
        }
    }
    

    Ejecutar este fragmento de c贸digo producir谩:

    The lion is being fed!
    The wolf is being fed!
    The bear if being fed!
    

    Peso mosca

    El patr贸n Flyweight se ocupa de reducir la tensi贸n en la JVM y su memoria. Esto es crucial para dispositivos sin mucha memoria, as铆 como la optimizaci贸n de la aplicaci贸n.

    Cuando una determinada aplicaci贸n necesita crear muchas instancias de la misma clase, se crea un grupo com煤n para que se puedan reutilizar otras similares, en lugar de crearlas cada vez.

    La implementaci贸n m谩s conocida de este patr贸n de dise帽o es String Pool en Java. Las cadenas se utilizan quiz谩s con m谩s frecuencia que cualquier otro objeto en el lenguaje y, por lo tanto, consumen una gran parte de los recursos. Al crear un conjunto de cadenas comunes y asignar m煤ltiples variables de referencia a las que tienen el mismo contenido, y solo crear nuevas cadenas cuando no se encuentra ninguna coincidencia, tuvo un gran impacto en el rendimiento de Java.

    Implementaci贸n

    Como de costumbre, comencemos con una interfaz:

    public interface Attendee {
        public void listenToConcert();
    }
    

    Una clase concreta implementa esta interfaz:

    public class AttendeeImpl implements Attendee {
        private String name;
        private int age;
    
        public AttendeeImpl(String name) {
            this.name = name;
        }
        
        public void setAge(int age) {
            this.age = age;
        }
        
        @Override
        public void listenToConcert() {
            System.out.println(name + " is listening to concert " + age + " years old!");
        }
    }
    

    Todos estos asistentes ser谩n creados por un AttendeeFactory y poner en un HashMap. Es importante tener en cuenta que el m茅todo crea una nueva AttendeeImpl objeto si no existe ninguno. Por otro lado, si existe, el m茅todo lo devuelve.

    Este es el punto del patr贸n Flyweight. Para devolver un nuevo objeto solo si un objeto coincidente a煤n no existe:

    public class AttendeeFactory {
        private static final HashMap attendees = new HashMap();
        
        public static Attendee getAttendee(String name) {
            AttendeeImpl attendeeImpl = (AttendeeImpl)attendees.get(name);
                if(attendeeImpl == null) {
                    attendeeImpl = new AttendeeImpl(name);
                    attendees.put(name, attendeeImpl);
                    System.out.println("Creating a new attendee: " + name);
                }
             return attendeeImpl;
        }
    }
    

    Y para ver el resultado, crearemos 10 asistentes con nombres aleatorios del grupo de nombres y edad aleatoria.

    public class Pharos.shJavaDesignPatterns {
        
        private static final String[] names = {"David", "Scott", "Andrew", "Rhett"};
        
        public static void main(String[] args) {
            for(int i = 0; i < 10; ++i) {
                AttendeeImpl attendeeImpl = (AttendeeImpl) AttendeeFactory.getAttendee(getRandomName());
                attendeeImpl.setAge(getRandomAge());
                attendeeImpl.listenToConcert();
            }
        }
        
        private static String getRandomName() {
            int randomName = new Random().nextInt(names.length);
            return names[randomName];
        }
        
        private static int getRandomAge() {
            return (int)(Math.random()*80);
        }
    }
    

    La ejecuci贸n de este fragmento de c贸digo producir谩 valores diferentes cada vez, pero deber铆a verse as铆:

    Creating a new attendee: Scott
    Scott is listening to concert 32 years old!
    Scott is listening to concert 1 years old!
    Creating a new attendee: Andrew
    Andrew is listening to concert 8 years old!
    Creating a new attendee: Rhett
    Rhett is listening to concert 58 years old!
    Andrew is listening to concert 76 years old!
    Scott is listening to concert 56 years old!
    Rhett is listening to concert 43 years old!
    Scott is listening to concert 51 years old!
    Creating a new attendee: David
    David is listening to concert 31 years old!
    David is listening to concert 29 years old!
    

    Apoderado

    El patr贸n Proxy se utiliza cuando queremos limitar las capacidades y funcionalidades de una clase, utilizando otra clase que la limita.

    Al usar esta clase de proxy, el cliente usa la interfaz que define para acceder a la clase original. Esto asegura que el cliente no pueda hacer nada fuera de orden con la clase original ya que todas sus solicitudes pasan por nuestra clase de proxy.

    Implementaci贸n

    Definamos una interfaz com煤n para la clase original y proxy:

    public interface MediaFile {
        void printName();
    }
    

    Esta interfaz ser谩 implementada por una clase, para la cual definiremos una clase de proxy:

    public class MediaFileImpl implements MediaFile {
        private String fileName;
    
        public MediaFileImpl(String fileName){
           this.fileName = fileName;
           loadFromDisk(fileName);
        }
    
        @Override
        public void printName() {
           System.out.println("Displaying " + fileName);
        }
    
        private void loadFromDisk(String fileName){
           System.out.println("Loading " + fileName);
        }
    }
    
    public class ProxyMediaFile implements MediaFile {
    
     private MediaFileImpl mediaFileImpl;
       private String fileName;
    
       public ProxyMediaFile(String fileName){
          this.fileName = fileName;
       }
    
       @Override
       public void printName() {
          if(mediaFileImpl == null){
             mediaFileImpl = new MediaFileImpl(fileName);
          }
          mediaFileImpl.printName();
       }
    }
    

    Con estas dos clases concretas terminadas, observemos el resultado:

    public class Main {
        public static void main(String[] args) {
          MediaFile mediaFile = new ProxyMediaFile("movie.mp4");
    
          mediaFile.printName();  
          mediaFile.printName(); 	
        }
    }
    

    Ejecutar este fragmento de c贸digo producir谩:

    Loading movie.mp4
    Displaying movie.mp4
    Displaying movie.mp4
    

    Conclusi贸n

    Con esto, todos Patrones de dise帽o estructural 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 de comportamiento.

    Etiquetas:

    Deja una respuesta

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