Breaking

Post Top Ad

Your Ad Spot

martes, 9 de julio de 2019

Patrones de diseño de comportamiento en Java

Visión general

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

Patrones de comportamiento

Los patrones de comportamiento se ocupan de brindar soluciones relacionadas con la interacción de objetos: cómo se comunican, cómo dependen algunos de los demás y cómo separarlos para que sean tanto dependientes como independientes y brinden tanto flexibilidad como capacidades de prueba.
Los patrones de comportamiento en Java que se tratan en este artículo son:

Interprete

El patrón de intérprete se utiliza en cualquier momento en que necesitamos evaluar cualquier tipo de gramática o expresión del lenguaje. Un buen ejemplo de este patrón sería Google Translate , que interpreta la entrada y nos muestra la salida en otro idioma.
Otro ejemplo sería el compilador de Java. El compilador interpreta el código Java y lo traduce a un código de bytes que la JVM utiliza para realizar operaciones en el dispositivo en el que se ejecuta.
Este patrón también representa una excelente manera de escribir programas simples que entienden la sintaxis de tipo humano.
Implementación
Haremos una implementación simple con una gramática simple, de lo contrario, sería complicada y demasiado compleja para este tutorial.
Para realizar este patrón de diseño, tendremos que definir un motor de intérprete, acompañado de diferentes expresiones que utilizará para interpretar el comando.
Vamos a definir una interfaz para todas estas expresiones:
public interface Expression {  
    public int interpret(InterpreterEngine engine);
}
Este motor de intérprete es simple:
public class InterpreterEngine {  
    public int add(String input) {
        String[] tokens = interpret(input);
        int num1 = Integer.parseInt(tokens[0]);
        int num2 = Integer.parseInt(tokens[1]);
        return (num1+num2);
    }

    public int multiply(String input) {
        String[] tokens = interpret(input);
        int num1 = Integer.parseInt(tokens[0]);
        int num2 = Integer.parseInt(tokens[1]);
        return (num1*num2);
    }

    private String[] interpret(String input) {
        String string = input.replaceAll("[^0-9]", " ");
        string = string.replaceAll("( )+", " ").trim();
        String[] tokens = string.split(" ");
        return tokens;
    }
}
Reemplaza todos los caracteres que no son dígitos con caracteres vacíos, y divide la entrada en tokens. Esto básicamente nos deja sin dígitos.
Ahora, implementemos la Expressioninterfaz con algunas clases concretas:
public class AddExpression implements Expression {  
    private String expression;

    public AddExpression(String expression) {
        this.expression = expression;
    }

    @Override
    public int interpret(InterpreterEngine engine) {
        return engine.add(expression);
    }
}

public class MultiplyExpression implements Expression {  
    private String expression;

    public MultiplyExpression(String expression) {
        this.expression = expression;
    }

    @Override
    public int interpret(InterpreterEngine engine) {
        return engine.multiply(expression);
    }
}
Y para ilustrar el punto del patrón:
public class Main {  
    private InterpreterEngine engine;

    public Main(InterpreterEngine engine) {
        this.engine = engine;
    }

    public int interpret(String input) {
        Expression expression = null;

        if(input.contains("add")) {
            expression = new AddExpression(input);
        } else if(input.contains("multiply")) {
            expression = new MultiplyExpression(input);
        }

        int result = expression.interpret(engine);
        System.out.println(input);
        return result;
    }

    public static void main(String[] args) {
        Main main = new Main(new InterpreterEngine());

        System.out.println("Result: " + main .interpret("add 15 and 25"));
        System.out.println("Result: " + main .interpret("multiply " + main .interpret("add 5 and 5") + " and 10"));
    }
}
Ya que descartamos todos los caracteres que no son dígitos, aquí está el lugar para evaluar si el intérprete debe agregar o multiplicar la entrada.
Ejecutar este fragmento de código dará como resultado:
add 15 and 25  
Result: 40  
add 5 and 5  
multiply 10 and 10  
Result: 100  

Método de plantilla

El Método de Plantilla, también conocido como Patrón de Plantilla está a nuestro alrededor. Se reduce a definir una clase abstracta que proporciona formas predefinidas de ejecutar sus métodos. Las subclases que heredan estos métodos también deben seguir la forma definida en la clase abstracta.
En algunos casos, es posible que la clase abstracta ya incluya una implementación de método, no solo instrucciones, si se trata de una funcionalidad que se compartirá entre todas o la mayoría de las subclases.
Implementación
En una empresa, todos los empleados tienen algunas responsabilidades compartidas:
public abstract class Employee {  
    abstract void work();
    abstract void takePause();
    abstract void getPaid();

    public final void comeToWork() {
        work();
        takePause();
        work();
        getPaid();
    }
}
Todos vienen a trabajar, todos consiguen un descanso y les pagan.
Diferentes empleados hacen diferentes tipos de trabajo:
public class Programmer extends Employee {

    @Override
    void work() {
        System.out.println("Writing code.");
    }

    @Override
    void takePause() {
        System.out.println("Taking a small break from writing code.");
    }

    @Override
    void getPaid() {
        System.out.println("Getting paid for developing the project.");
    }
}
public class Manager extends Employee {

    @Override
    void work() {
        System.out.println("Managing other employees.");
    }

    @Override
    void takePause() {
        System.out.println("Taking a small break from managing employees.");
    }

    @Override
    void getPaid() {
        System.out.println("Getting paid for overseeing the development of the project.");
    }
}
Pero aún siguen la plantilla de trabajar, hacer una pausa y cobrar, que está todo establecido por la interfaz.
Para ilustrar el punto de este patrón:
public class Main {  
    public static void main(String[] args) {
        Employee employee = new Programmer();
        employee.comeToWork();

        System.out.println();

        employee = new Manager();
        employee.comeToWork();
    }
}
Ejecutar este fragmento de código dará como resultado:
Writing code.  
Taking a small break from writing code.  
Writing code.  
Getting paid for developing the project.

Managing other employees.  
Taking a small break from managing employees.  
Managing other employees.  
Getting paid for overseeing the development of the project.  

Cadena de responsabilidad

El patrón de la Cadena de Responsabilidad es ampliamente utilizado y adoptado. Define una cadena de objetos, que colectivamente, uno tras otro, procesan la solicitud, donde cada procesador de la cadena tiene su propia lógica de procesamiento.
Cada una de estas unidades de procesamiento decide quién debe continuar procesando la solicitud a continuación, y cada una tiene una referencia a la siguiente en línea.
Es importante tener en cuenta que es muy útil para desacoplar el remitente del receptor.
Implementación
Como de costumbre, definamos una clase abstracta:
public abstract class Employee {  
    public static int PROGRAMER = 1;
    public static int LEAD_PROGRAMER = 2;
    public static int MANAGER = 3;

    protected int authorityLevel;

    protected Employee nextEmployee;

    public void setNextEmployee(Employee employee) {
        this.nextEmployee = employee;
    }

    public void doWork(int authorityLevel, String message) {
        if(this.authorityLevel <= authorityLevel) {
            write(message);
        }
        if(nextEmployee != null) {
            nextEmployee.doWork(authorityLevel, message);
        }
    }

    abstract protected void write(String message);
}
Esta clase abstracta contiene niveles de autoridad para todos los empleados. Un programador está ubicado menos en la jerarquía que un programador líder, que a su vez es más bajo que un administrador.
También hemos incluido una referencia al siguiente empleado, que verá por qué es lo suficientemente importante pronto.
Se define un método común para todas estas clases, con una verificación de autoridad. Si una determinada clase no tiene la autoridad, pasa la solicitud a la siguiente en la cadena de responsabilidad.
Ahora, vamos a extender esta clase:
public class Programmer extends Employee {

    public Programmer(int authorityLevel) {
        this.authorityLevel = authorityLevel;
    }

    @Override
    protected void write(String message) {
        System.out.println("Programmer is working on project: " + message);
    }
}

public class LeadProgrammer extends Employee {

    public LeadProgrammer(int authorityLevel) {
        this.authorityLevel = authorityLevel;
    }

    @Override
    protected void write(String message) {
         System.out.println("Lead programmer is working on project: " + message);
    }
}

public class Manager extends Employee {

    public Manager(int authorityLevel) {
        this.authorityLevel = authorityLevel;
    }

    @Override
    protected void write(String message) {
         System.out.println("Manager is working on project: " + message);
    }
}
Como se mencionó anteriormente, cada una de estas unidades proporciona su propia lógica de procesamiento.
Para ilustrar el punto de este patrón:
public class Main {  
    private static Employee getChainOfEmployees() {
        Employee programmer = new Programmer(Employee.PROGRAMER);
        Employee leadProgrammer = new LeadProgrammer(Employee.LEAD_PROGRAMER);
        Employee manager = new Manager(Employee.MANAGER);

        programmer.setNextEmployee(leadProgrammer);
        leadProgrammer.setNextEmployee(manager);

        return programmer;
    }

    public static void main(String[] args) {
        Employee employeeChain = getChainOfEmployees();

        employeeChain.doWork(Employee.PROGRAMER, "This is basic programming work.");
        employeeChain.doWork(Employee.LEAD_PROGRAMER, "This is marginally more 
            sophisticated programming work.");
        employeeChain.doWork(Employee.MANAGER, "This is the work for a manager.");
    }
}
En primer lugar, getChainOfEmployees()se define un método estático Este método se utiliza para establecer los niveles de autoridad de cada unidad, a través de sus constructores, y para definir el orden de responsabilidad.
Al configurar el siguiente Employeepara Programmer, básicamente le estamos diciendo a quién acudir, si la solicitud está fuera de su alcance.
Naturalmente, un programador se dirigirá a su designado LeadProgrammerSi la solicitud es demasiado parejo para que la manejen, recurrirán a su Managerayuda.
Ejecutar este fragmento de código dará como resultado:
Programmer is working on project: This is basic programming work.  
Programmer is working on project: This is marginally more sophisticated programming work.  
Lead programmer is working on project: This is marginally more sophisticated programming work.  
Programmer is working on project: This is the work for a manager.  
Lead programmer is working on project: This is the work for a manager.  
Manager is working on project: This is the work for a manager.  
Programmerse asigna para trabajar en una solicitud en su propio nivel de autoridad, y lo hacen con gracia.
Luego, entra una nueva solicitud, que necesita la autoridad de a LeadProgrammer, por lo que asumen el control.
Finalmente, llega otra petición, que necesita la autoridad de a ManagerEl programador le pide ayuda a su programador líder designado, que a su vez decide pedirle ayuda a su gerente, y el gerente cumple con su trabajo y cumple con su cometido.

Mando

Otro patrón de diseño de desacoplamiento, el patrón de Comando funciona envolviendo la solicitud del remitente en un objeto llamado comando . Este comando luego se pasa al objeto invocador, que busca la forma adecuada de procesar la solicitud.
Una vez que encuentra la forma adecuada, pasa el comando, donde se ejecutará.
Implementación
Simulemos el trabajo de un programador para este patrón. Un cliente puede enviar un Order- un comando, para una Application- una solicitud. El programador puede hacer la aplicación y venderla al cliente.
Hagamos nuestro comando:
public interface Order {  
    void placeOrder();
}
Y nuestra petición:
public class Application {  
    private String name = "Computer Application";
    private int quantity = 2;

    public void make() {
        System.out.println(quantity + " application(s) are made for the client.");
    }

    public void sell() {
        System.out.println(quantity + "application(s) are sold to the client.");
    }
}
Suponiendo que el programador aceptó trabajar con el cliente, sería apropiado hacer la aplicación:
public class MakeApplication implements Order {  
    private Application application;

    public MakeApplication(Application application) {
        this.application = application;
    }

    @Override
    public void placeOrder() {
        application.make();
    }
}
Y después de hacerlo, el programador procederá a venderlo:
public class SellApplication implements Order {  
    private Application application;

    public SellApplication(Application application) {
        this.application = application;
    }

    @Override
    public void placeOrder() {
        application.sell();
    }
}
Se necesita un objeto invocador, al que enviamos la solicitud:
public class Programmer {  
    private List<Order> orderList = new ArrayList<>();

    public void takeOrder(Order order) {
        orderList.add(order);
    }

    public void placeOrders() {
        for(Order order : orderList) {
            order.placeOrder();
        }
        orderList.clear();
    }
}
La solicitud, a pesar de que se trata de una Applicationse envuelve como un Order- un comando , tal como se describe antes de la aplicación.
Y para ilustrar el punto de este patrón:
public class Main {  
    public static void main(String[] args) {
        // command
        Application application = new Application();

        / /wrapping requests
        MakeApplication makeApplication = new MakeApplication(application);
        SellApplication sellApplication = new SellApplication(application);

        // invoker
        Programmer programmer = new Programmer();
        programmer.takeOrder(makeApplication);
        programmer.takeOrder(sellApplication);

        // invoker processed the wrapped request
        programmer.placeOrders();
    }
}
Ejecutar este fragmento de código dará como resultado:
2 application(s) are made for the client.  
2 application(s) are sold to the client.  

Iterador

El patrón de iterador se usa como el patrón central del Marco de Colección de Java . Se utiliza para acceder a los miembros de las colecciones mientras oculta la implementación subyacente.
Implementación
Esta es una implementación bastante simple y se utiliza como patrón central en múltiples marcos, incluido el marco mencionado anteriormente.
Haremos un iterador simple para imprimir los nombres de nuestros empleados.
Todos nuestros empleados tienen su propio sector en el que operan. Así que trabajar en un sector también incluye un iterador para todos ellos.
Así que vamos a seguir adelante y definir nuestro Iterator:
public interface Iterator {  
    public boolean hasNext();
    public Object next();
}
Este iterador será almacenado en un contenedor de clases. En nuestro caso, una obra Sector:
public interface Sector {  
    public Iterator getIterator();
}
Ahora, definamos un repositorio para nuestros empleados:
public class EmployeeRepository implements Sector {  
    public String[] employees = {"David", "Scott", "Rhett", "Andrew", "Jessica"};

    @Override
    public Iterator getIterator() {
        return new EmployeeIterator();
    }

    private class EmployeeIterator implements Iterator {
        int index;

        @Override
        public boolean hasNext() {
            if(index < employees.length) {
                return true;
            } 
            return false;
        }

        @Override
        public Object next() {
            if(this.hasNext()) {
                return employees[index++];
            }
            return null;
        }
    }
}
En aras de la simplicidad, solo hemos utilizado una serie de cadenas y hemos evitado definir una Employeeclase separada .
Para ilustrar el punto de este patrón:
public class Main {  
    public static void main(String[] args) {

        EmployeeRepository employeeRepository = new EmployeeRepository();

        for(Iterator iterator = employeeRepository.getIterator(); 
                iterator.hasNext();) {
            String employee = (String)iterator.next();
            System.out.println("Employee: " + employee);
        }
    }
}
Ejecutar este fragmento de código dará como resultado:
Employee: David  
Employee: Scott  
Employee: Rhett  
Employee: Andrew  
Employee: Jessica  

Mediador

Similar al patrón Adaptador, pero con un objetivo diferente. El patrón de mediador actúa como un puente y, como su nombre lo indica, el mediador entre diferentes objetos que se comunican de cualquier manera. En aplicaciones a gran escala, la comunicación directa significa un acoplamiento apretado que dificulta la prueba, el mantenimiento y la escala.
El patrón de Mediador aborda este problema actuando como un tercero sobre el cual se realiza la comunicación, desacoplando en el proceso.
Implementación
Esta es una implementación bastante simple, y probablemente la más notoria es una conversación entre dos personas.
Un Userobjeto desea comunicarse con otro, por lo que utilizan una plataforma de mediador entre ellos para hacerlo Chat:
public class Chat {  
    public static void showMessage(User user, String message) {
        System.out.println(new Date().toString() + "[" + user.getName() + "]: " + message);
    }
}
Esta clase contiene solo un método y, aceptando a Usery a String, formatea los parámetros y muestra el mensaje.
public class User {  
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }   

    public void sendMessage(String message) {
        Chat.showMessage(this, message);
    }
}
Nuestra Userclase define un sendMessage()método. Este método invoca el staticmétodo de la Chatclase con la thisinstancia del usuario y a Stringcomo los argumentos.
Para ilustrar el punto de este patrón:
public class Main {  
    public static void main(String[] args) {
        User david = new User("David");
        User scott = new User("Scott");

        david.sendMessage("Hi Scott! How are you?");
        scott.sendMessage("I'm great! Thanks for asking. How are you?");
    }
}
Estos dos objetos no se comunican directamente. Ninguno de ellos apunta a una variable de referencia u otro objeto, sin embargo, la Chatclase actúa como un mediador y los conecta.
Ejecutar este fragmento de código dará como resultado:
Fri Aug 31 14:14:19 CEST 2018[David]: Hi Scott! How are you?  
Fri Aug 31 14:14:19 CEST 2018[Scott]: I'm great! Thanks for asking. How are you?  

Recuerdo

El patrón Memento se ocupa de los estados anteriores del objeto. Esto significa que el patrón se utiliza cuando queremos guardar algún estado de un objeto, en el caso de que queramos restaurar el objeto a ese estado más adelante.
Implementación
Este patrón se basa en el trabajo de tres clases, también conocidas como clases de actores . El Mementoobjeto contiene un estado que deseamos guardar para su uso posterior. El Originatorobjeto crea y almacena estados en los Mementoobjetos, mientras que el CareTakerobjeto se encarga del proceso de restauración.
Primero definamos nuestro recuerdo:
public class Memento {  
    private String state;

    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }
}
Entonces nuestro creador y cuidador:
public class Originator {  
    private String state;

    public void setState(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public Memento saveStateToMemento() {
        return new Memento(state);
    }

    public void getStateFromMemento(Memento memento) {
        state = memento.getState();
    }
}
public class CareTaker {  
    private List<Memento> mementoList = new ArrayList<>();

    public void add(Memento memento) {
        mementoList.add(memento);
    }
    public Memento get(int index) {
        return mementoList.get(index);
    }
}
Y para ilustrar el punto del patrón:
public class Main {  
    public static void main(String[] args) {
        Originator originator = new Originator();
        CareTaker careTaker = new CareTaker();

        originator.setState("State 1 at " + System.currentTimeMillis());
        originator.setState("State 2 at " + System.currentTimeMillis());
        careTaker.add(originator.saveStateToMemento());

        originator.setState("State 3 at " + System.currentTimeMillis());
        careTaker.add(originator.saveStateToMemento());

        System.out.println("Current state: " + originator.getState());

        originator.getStateFromMemento(careTaker.get(0));
        System.out.println("First saved state: " + originator.getState());
        originator.getStateFromMemento(careTaker.get(1));
        System.out.println("Second saved state: " + originator.getState());
    }
}
Ejecutar este fragmento de código dará como resultado:
Current state: State 3 at 1535705131218  
First saved state: State 2 at 1535705131218  
Second saved state: State 3 at 1535705131218  

Observador

El patrón Observer se usa para monitorear el estado de un determinado objeto, a menudo en un grupo o una relación de uno a muchos. En tales casos, la mayoría de las veces, el estado modificado de un solo objeto puede afectar el estado del resto, por lo que debe haber un sistema para observar el cambio y alertar a los otros objetos.
Si bien Java proporciona una clase y una interfaz con este patrón en mente, no está muy extendido porque no se realizó de una manera ideal.
Implementación
Para ilustrar este patrón, vamos a construir una pequeña oficina con una CEOManagerLeadProgrammerProgrammer.
El programador será observado por sus superiores, que tienen una opinión de él basada en lo bien que hace su trabajo:
public class Programmer {  
    private List<Observer> observers = new ArrayList<>();
    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
        notifyObservers();
    }

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}
Hay una relación de uno a muchos con sus observadores, y cada cambio de estado los notifica a todos.
Todos estos observadores tienen un par de cosas en común:
public abstract class Observer {  
    protected Programmer programmer;
    public abstract void update();
}
Pero cada uno tiene su propia implementación:
public class CEO extends Observer {

    public CEO(Programmer programmer) {
        this.programmer = programmer;
        this.programmer.attach(this);
    }

    @Override
    public void update() {
        if(this.programmer.getState().equalsIgnoreCase("Successful")) {
            System.out.println("CEO is happy with Manager and Lead Programmer.");
        } else {
            System.out.println("CEO is unhappy with Manager and Lead Programmer.");
        }
    }
}

public class Manager extends Observer {

    public Manager(Programmer programmer) {
        this.programmer = programmer;
        this.programmer.attach(this);
    }

    @Override
    public void update() {
        if(this.programmer.getState().equalsIgnoreCase("Successful")) {
            System.out.println("Manager is happy with Lead Programmer and this Programmer.");
        } else {
            System.out.println("Manager is unhappy with Lead Programmer and this Programmer.");
        }
    }
}

public class LeadProgrammer extends Observer {

    public LeadProgrammer(Programmer programmer) {
        this.programmer = programmer;
        this.programmer.attach(this);
    }

     @Override
    public void update() {
        if(this.programmer.getState().equalsIgnoreCase("Successful")) {
            System.out.println("Lead Programmer is proud of his Programmer.");
        } else {
            System.out.println("Lead Programmer is not proud of his Programmer.");
        }
    }
}
El CEOno se preocupa por el programador, sino por el resultado, dejándolo en manos capaces del Managery el LeadProgrammerAl gerente le preocupa sobre todo si el programador principal puede guiarlo para que haga su trabajo. Y finalmente, el programador principal está preocupado principalmente por lo que hace el programador.
Para ilustrar el punto de este patrón:
public class Main {  
    public static void main(String[] args) {
        Programmer programmer = new Programmer();

        new CEO(programmer);
        new Manager(programmer);
        new LeadProgrammer(programmer);

        System.out.println("Programmer successfully did his job!");
        programmer.setState("Successful");
        System.out.println("Programmer failed his new assignment.");
        programmer.setState("Failed");
    }
}
Ejecutar este fragmento de código dará como resultado:
Programmer successfully did his job!  
CEO is happy with Manager and Lead Programmer.  
Manager is happy with Lead Programmer and this Programmer.  
Lead Programmer is proud of his Programmer.  
Programmer failed his new assignment.  
CEO is unhappy with Manager and Lead Programmer.  
Manager is unhappy with Lead Programmer and this Programmer.  
Lead Programmer is not proud of his Programmer.  

Estado

El patrón de estado se utiliza cuando un objeto específico necesita cambiar su comportamiento, en función de su estado. Esto se logra proporcionando a cada uno de estos objetos uno o más objetos de estado.
Basándonos en estos objetos de estado, podemos cambiar completamente el comportamiento del objeto en cuestión.
Implementación
Vamos a definir una interfaz simple:
public interface State {  
    public void doAction(Context context);
}
Este estado se llevará a través de un contexto:
public class Context {  
    private State state;

    public Context() {
        state = null;
    }

    public void setState(State state) {
        this.state = state;
    }

    public State getState() {
        return state;
    }
}
Y dos clases concretas lo implementan:
public class ApplicationStart implements State {

    @Override
    public void doAction(Context context) {
        System.out.println("The application is in the starting state of development.");
        context.setState(this);
    }
    public String toString() {
        return "Starting state.";
    }
}

public class ApplicationFinish implements State {

    @Override
    public void doAction(Context context) {
        System.out.println("The application is in the finished state of development.");
        context.setState(this);
    }
    public String toString() {
        return "Finished state.";
    }    
}
Ahora para ilustrar el punto de este patrón:
public class Main {  
    public static void main(String[] args) {
        Context context = new Context();

        ApplicationStart start = new ApplicationStart();
        start.doAction(context);

        System.out.println(context.getState());

        ApplicationFinish finish = new ApplicationFinish();
        finish.doAction(context);

        System.out.println(context.getState());
    }
}
Ejecutar este fragmento de código dará como resultado:
The application is in the starting state of development.  
Starting state.  
The application is in the finished state of development.  
Finished state.  
Como puede ver, el estado del transportista cambia el comportamiento del transportista.

Estrategia

El patrón de estrategia se emplea en situaciones donde los algoritmos o el comportamiento de la clase deben ser dinámicos. Esto significa que tanto el comportamiento como los algoritmos se pueden cambiar en tiempo de ejecución, según la entrada del cliente.
Similar al patrón de estado, este patrón emplea múltiples objetos de estrategia que definen diferentes estrategias para la clase objetivo. La clase objetivo adapta sus algoritmos y comportamientos basados ​​en estas estrategias.
Implementación
Vamos a empezar definiendo una estrategia:
public interface Strategy {  
    public String build(String location);
}
Esta estrategia se utilizará para construir diferentes tipos de edificios, en diferentes ubicaciones. Estos tipos de edificios implementan la estrategia cada uno de una manera diferente:
public class Skyscraper implements Strategy {

    @Override
    public String build(String location) {
        return "Building a skyscraper in the " + location + " area.";
    }
}

public class House implements Strategy {

    @Override
    public String build(String location) {
        return "Building a house in the " + location + " area.";
    }
}

public class Mall implements Strategy {

    @Override
    public String build(String location) {
        return "Building a mall in the " + location + " area.";
    }
}
Similar al patrón estatal, una Contextclase usará la estrategia:
public class BuildContext {  
    private Strategy strategy;

    public BuildContext(Strategy strategy) {
        this.strategy = strategy;
    }

    public String executeStrategy(String location) {
        return strategy.build(location);
    }
}
Y para ilustrar el punto de este patrón:
public class Main {  
    public static void main(String[] args) {
        BuildContext buildContext = new BuildContext(new Skyscraper());
        System.out.println("Requesting a skyscraper: " + buildContext.executeStrategy("Downtown"));

        buildContext = new BuildContext(new House());
        System.out.println("Requesting a house: " + buildContext.executeStrategy("Outskirts"));

        buildContext = new BuildContext(new Mall());
        System.out.println("Requesting a mall: " + buildContext.executeStrategy("City Centre"));
    }
}
Ejecutar este fragmento de código dará como resultado:
Requesting a skyscrapper: Building a skyscrapper in the Downtown area.  
Requesting a house: Building a house in the Outskirts area.  
Requesting a mall: Building a mall in the City Centre area.  

Visitante

El patrón de visitante se usa para mover la lógica operativa de cada elemento individual de un grupo a una nueva clase, que realiza la operación para ellos utilizando los datos de cada elemento individual.
Esto se hace haciendo que todos los elementos acepten un "visitante". Este visitante realizará cambios en una clase separada, sin cambiar la estructura de la clase visitada en absoluto. Esto hace que sea fácil agregar una nueva funcionalidad sin cambiar las clases visitadas.
Dicho esto, los objetos no tienen que ser iguales y pueden no estar relacionados, implementando diferentes interfaces, etc. Un ejemplo sería una aplicación que cuenta el número de usuarios en un sitio web. Algunos de estos usuarios son administradores, otros son clientes, otros son moderadores, etc.
A pesar de que pueden implementar diferentes interfaces y cumplir diferentes funciones, este patrón puede ayudar a adquirir el número correcto de usuarios.
Implementación
Cada artículo en nuestra tienda podrá aceptar un visitante:
public interface Item {  
    public int accept(Visitor visitor);
}
Y aquí está nuestro visitante:
public interface Visitor {  
    int visit(Pen pen);
    int visit(Notebook notebook);
}
Definamos clases concretas para nuestros artículos de la tienda:
public class Pen implements Item {  
    private int price;
    private String model;

    public Pen(int price, String model) {
        this.price = price;
        this.model = model;
    }

    public int getPrice() {
        return price;
    }

    public String getModel() {
        return model;
    }

    @Override
    public int accept(Visitor visitor) {
        return visitor.visit(this);
    }
}
public class Notebook implements Item {  
    private int price;
    private int numberOfPages;

    public Notebook(int price, int numberOfPages) {
        this.price = price;
        this.numberOfPages = numberOfPages;
    }

    public int getPrice() {
        return price;
    }

    public int getNumberOfPages() {
        return numberOfPages;
    }

    @Override
    public int accept(Visitor visitor) {
        return visitor.visit(this);
    }
}
Y ahora implementemos la interfaz de visitante y mostremos este patrón de diseño. La clase de implementación tendrá su propia lógica para calcular el precio de los artículos, no los artículos en sí mismos:
public class VisitorImpl implements Visitor {

    @Override
    public int visit(Pen pen) {
        int price = pen.getPrice();
        System.out.println(pen.getModel() + " costs " + price);
        return price;
    }

    @Override
    public int visit(Notebook notebook) {
        int price = 0;
        if(notebook.getNumberOfPages() > 250) {
            price = notebook.getPrice()-5;
        } else {
            price = notebook.getPrice();
        }
        System.out.println("Notebook costs " + price);

        return price;
    }
}
Y para ilustrar el punto del patrón:
public class StackAbuseJavaDesignPatterns {  
    public static void main(String[] args) {
        Item[] items = new Item[]{new Pen(10, "Parker"), new Pen(5, "Pilot"), new Notebook(50, 150), new Notebook(75, 300)};

        int total = getTotalPrice(items);
        System.out.println("Total price of items: " + total);
    }

    private static int getTotalPrice(Item[] items) {
        Visitor visitor = new VisitorImpl();
        int result = 0;
        for(Item item : items) {
            result = result + item.accept(visitor);
        }
        return result;
    }
}
Ejecutar este fragmento de código dará como resultado:
Parker costs 10  
Pilot costs 5  
Notebook costs 50  
Notebook costs 70  
Total price of items: 135  

Conclusión

Con esto, todos los patrones de diseño de comportamiento en Java están completamente cubiertos, con ejemplos prácticos.
Si desea continuar leyendo sobre los patrones de diseño en Java, el siguiente artículo cubre los patrones de diseño de J2EE .

No hay comentarios.:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

Post Top Ad

Your Ad Spot

Páginas