Post Top Ad

Your Ad Spot

martes, 9 de julio de 2019

Patrones de diseño estructural en Java

Visión general

Este es el segundo artículo de una breve serie dedicada a los patrones de diseño en Java y una continuación directa del artículo anterior: los patrones de diseño creacionales en Java.

Patrones estructurales

Los patrones estructurales están preocupados por brindar soluciones y estándares eficientes con respecto a las composiciones de clase y las estructuras de objetos. Además, se basan en el concepto de herencia e interfaces para permitir que múltiples objetos o clases trabajen juntos y formen un solo conjunto de trabajo.
Los patrones estructurales en Java que se tratan en este artículo son:

Adaptador

El patrón del 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
La Builderinterfaz 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);
}
La AdvancedBuilderinterfaz 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 respecta al código.
En este punto, AdvancedBuilderse crea una clase concreta que implementa 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, se realiza una BuilderAdapterimplementación Builder:
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 en funcionamiento, finalmente podemos implementar la solución y usar el Buildermétodo de la interfaz con el BuilderAdapterfin de 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 la pieza 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 de puente se utiliza para separar las clases abstractas de sus implementaciones y actuar como un puente entre ellas. De esta manera, 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);
    }
}
Usando la FeedingAPIinterfaz, Animalse crea una clase abstracta :
public abstract class Animal {  
    protected FeedingAPI feedingAPI;

    protected Animal(FeedingAPI feedingAPI) {
        this.feedingAPI = feedingAPI;
    }
    public abstract void feed();
}
Aquí es donde se activa el patrón de Bridge. Se crea una clase de bridge que separa a la Animalclase abstracta de 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 dará como resultado:
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, que se realiza de manera desacoplada.
Implementación
Partiendo de una Employeeclase que filtraremos utilizando 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
}
La Criteriainterfaz 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 mujeres empleadas.
  • CriteriaSenior - Un criterio para buscar empleados de alto nivel.
  • CriteriaJunior - Un criterio para buscar empleados junior.
  • AndCriteria - Un criterio para buscar empleados que pasan ambos criterios que aplicamos.
  • OrCriteria - Un criterio para buscar empleados que cumplan cualquiera de los criterios que aplicamos.
CriteriosMale:
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;
    }
}
forBucle simple que agrega todos los empleados masculinos a una lista y los devuelve.
Criterios de mujer:
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 las empleadas.
CriterioSenior:
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.
CriteriosJunior:
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.
YCriteria:
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 a través del primer criterio, y luego la lista ya filtrada se filtra nuevamente, con el segundo criterio.
OrCriteria:
    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 hacen dos listas de empleados, basadas en los criterios individuales. Si la primera lista no contiene un empleado que la segunda lista, el empleado se agrega a la lista.
De esta manera, ambas listas se fusionan prácticamente al final.
Ahora que todas las Criteriaimplementaciones están en su lugar, hagamos una lista de los 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 dará como resultado:
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 usa cuando necesitamos una forma de tratar a un grupo completo de objetos de una manera similar o similar.
Esto generalmente lo hace la clase que "posee" el grupo de objetos y proporciona un conjunto de métodos para tratarlos como si fueran un solo objeto.
Implementación
Vamos a empezar con la Employeeclase. 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 Employeedentro de ella, este es nuestro grupo de objetos a los que queremos apuntar como un solo objeto.
public class StackAbuseJavaDesignPatterns {  
    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í, varios empleados son instanciados. El CEO tiene algunos empleados como colaboradores cercanos, y algunos de ellos tienen sus propios colaboradores cercanos, en posiciones más bajas.
Al final, los empleados principales son colaboradores cercanos del CEO, y los empleados regulares son colaboradores 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 Decorador 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, con el patrón de 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 decorador:
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 ampliará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 totalmente 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 dará como resultado:
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 de Fachada proporciona una interfaz simple y de nivel superior para el cliente y le permite acceder al sistema, sin conocer la lógica del sistema ni el funcionamiento interno.
Implementación
Definiremos una ZooKeeperclase que actuará como una interfaz para el usuario que desea alimentar a los animales en el zoológico.
Estamos empezando con una Animalinterfaz:
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 señal para la ZooKeeperclase:
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 dará como resultado:
The lion is being fed!  
The wolf is being fed!  
The bear if being fed!  

Peso mosca

El patrón de peso de mosca se refiere a reducir la tensión en la JVM y su memoria. Esto es crucial para dispositivos sin mucha memoria, así como para 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 las similares se puedan reutilizar, en lugar de crearlas cada vez.
La implementación más conocida de este patrón de diseño es el String Pool en Java. Las cadenas se usan quizás más a menudo que cualquier otro objeto en el lenguaje y, por lo tanto, consumen una gran parte de los recursos. Al crear un grupo de cadenas comunes y asignar múltiples variables de referencia a las que tienen el mismo contenido, y solo crear cadenas nuevas 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 AttendeeFactoryy puestos en un HashMapEs importante tener en cuenta que el método crea un nuevo AttendeeImplobjeto si ya no existe ninguno. Por otro lado, si existe, el método lo devuelve.
Este es el punto del patrón de peso mosca. Para devolver un nuevo objeto solo si un objeto coincidente no existe ya:
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 la edad aleatoria.
public class StackAbuseJavaDesignPatterns {

    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 tener un aspecto similar al siguiente:
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 usa cuando queremos limitar las capacidades y las funcionalidades de una clase, utilizando otra clase que lo limita.
Al usar esta clase de proxy, el cliente usa la interfaz que define para acceder a la clase original. Esto garantiza que el cliente no pueda hacer nada fuera de orden con la clase original, ya que todas sus solicitudes pasan a través de nuestra clase proxy.
Implementación
Vamos a definir 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 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 dará como resultado:
Loading movie.mp4  
Displaying movie.mp4  
Displaying movie.mp4  

Conclusión

Con esto, todos los 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 .

No hay comentarios.:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

outbrain

Páginas