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 *