Patrones de dise帽o de comportamiento en Java

    Visi贸n general

    Este es el tercer 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 estructural en Java.

    Patrones de comportamiento

    Los patrones de comportamiento se preocupan por proporcionar soluciones con respecto a la interacci贸n de objetos: c贸mo se comunican, c贸mo algunos dependen de otros y c贸mo segregarlos para que sean tanto dependientes como independientes y proporcionen flexibilidad y capacidades de prueba.

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

    • Interprete
    • M茅todo / patr贸n de plantilla
    • Cadena de responsabilidad
    • Mando
    • Iterador
    • Mediador
    • Recuerdo
    • Observador
    • Estado
    • Estrategia
    • Visitante

    Interprete

    El patr贸n de int茅rprete se utiliza en cualquier momento que necesitemos para evaluar cualquier tipo de gram谩tica o expresiones 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 al c贸digo de bytes que utiliza la JVM 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 comprendan la sintaxis similar a la humana.

    Implementaci贸n

    Haremos una implementaci贸n simple con gram谩tica simple, de lo contrario, se volver铆a complicado y demasiado complejo por el bien de este tutorial.

    Para realizar este patr贸n de dise帽o, tendremos que definir un motor de interpretaci贸n, acompa帽ado de diferentes expresiones que utilizar谩 para interpretar el comando.

    Definamos una interfaz para todas estas expresiones:

    public interface Expression {
        public int interpret(InterpreterEngine engine);
    }
    

    Este motor de interpretaci贸n 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"));
        }
    }
    

    Dado que descartamos todos los caracteres que no son d铆gitos, este es el lugar para evaluar si el int茅rprete debe sumar o multiplicar la entrada.

    Ejecutar este fragmento de c贸digo producir谩:

    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 proporcione 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, la clase abstracta puede incluir ya una implementaci贸n de m茅todo, no solo instrucciones, si es 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 descansan y se les paga.

    Diferentes empleados realizan 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 todav铆a siguen la plantilla de trabajar, hacer una pausa y cobrar, todo lo cual est谩 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 producir谩:

    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 Cadena de Responsabilidad es ampliamente utilizado y adoptado. Define una cadena de objetos que, en conjunto, 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 la fila.

    Es importante tener en cuenta que es muy 煤til para desvincular 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 gerente.

    Tambi茅n hemos incluido una referencia al pr贸ximo empleado, por lo que pronto ver谩 por qu茅 es importante.

    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, ampliemos 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 establecer el siguiente Employeepara Programmer, b谩sicamente le decimos a qui茅n acudir, si la solicitud est谩 fuera de su alcance.

    Naturalmente, un programador recurrir谩 a su designado LeadProgrammer. Si la solicitud es demasiado para ellos, acudir谩n a ellos en Managerbusca de ayuda.

    Ejecutar este fragmento de c贸digo producir谩:

    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.
    

    A Programmerest谩 asignado para trabajar en una solicitud en su propio nivel de autoridad, y lo hacen con elegancia.

    Luego, entra una nueva solicitud, que necesita la autoridad de a LeadProgrammer, por lo que se hace cargo.

    Finalmente, llega otra solicitud, que necesita la autoridad de a Manager. El programador pide ayuda a su programador l铆der designado, que a su vez decide pedir ayuda a su gerente, y el gerente cumple felizmente y hace el trabajo.

    Mando

    Otro patr贸n de dise帽o de desacoplamiento, el patr贸n Command funciona envolviendo la solicitud del remitente en un objeto llamado comando. Este comando se pasa luego al objeto invocador, que procede a buscar la forma adecuada de procesar la solicitud.

    Una vez que encuentra el camino adecuado, 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 entonces crear la aplicaci贸n y venderla al cliente.

    Hagamos nuestro comando:

    public interface Order {
        void placeOrder();
    }
    

    Y nuestra solicitud:

    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 luego 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, aunque es un, Applicationest谩 envuelto como un Ordercomando, como se describe antes de la implementaci贸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 producir谩:

    2 application(s) are made for the client.
    2 application(s) are sold to the client.
    

    Iterador

    El patr贸n Iterator se utiliza como patr贸n central del marco de colecci贸n de Java . Se usa 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. Entonces, trabajar bajo un sector tambi茅n incluye un iterador para todos ellos.

    As铆 que sigamos adelante y definamos nuestro Iterator:

    public interface Iterator {
        public boolean hasNext();
        public Object next();
    }
    

    Este iterador se almacenar谩 en una especie de contenedor. 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 matriz de cadenas y evitamos 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 producir谩:

    Employee: David
    Employee: Scott
    Employee: Rhett
    Employee: Andrew
    Employee: Jessica
    

    Mediador

    Similar al patr贸n Adaptador, pero con un objetivo diferente. El patr贸n Mediator act煤a como un puente y, como su nombre lo indica, el mediador entre diferentes objetos que se comunican de cualquier forma. En aplicaciones a gran escala, la comunicaci贸n directa significa un acoplamiento estrecho que dificulta la prueba, el mantenimiento y la escala.

    El patr贸n Mediador aborda este problema actuando como un tercero sobre el que se realiza la comunicaci贸n, desacoplando a los mismos 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: a 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 un Usery un 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 llama al staticm茅todo de la Chatclase con thisinstancia del usuario y a Stringcomo 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 ninguna variable de referencia u otro objeto, sin embargo, la Chatclase act煤a como mediador y los conecta.

    Ejecutar este fragmento de c贸digo producir谩:

    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 usa 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 producir谩:

    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 en una relaci贸n de uno a muchos. En tales casos, la mayor铆a de las veces, el estado cambiado de un solo objeto puede afectar el estado del resto, por lo que debe haber un sistema para notar 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 CEO, Manager, LeadProgrammery Programmer.

    El programador ser谩 observado por sus superiores, los cuales tienen una opini贸n de 茅l en funci贸n de 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();
            }
        }
    }
    

    Existe una relaci贸n de uno a muchos con sus observadores, y cada cambio de estado les 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 las manos capaces del Managery del LeadProgrammer. Al gerente le preocupa principalmente si el programador l铆der puede guiar al programador para que haga su trabajo. Y finalmente, al programador principal le preocupa principalmente 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 producir谩:

    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

    Definamos una interfaz simple:

    public interface State {
        public void doAction(Context context);
    }
    

    Este estado se llevar谩 a cabo 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 producir谩:

    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 cambia el comportamiento del transportista.

    Estrategia

    El patr贸n de estrategia se emplea en situaciones en las que los algoritmos o el comportamiento de las clases deben ser din谩micos. Esto significa que tanto el comportamiento como los algoritmos se pueden cambiar en tiempo de ejecuci贸n, en funci贸n de 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 en funci贸n de estas estrategias.

    Implementaci贸n

    Empecemos por definir 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 de estado, 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 producir谩:

    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 utiliza para mover la l贸gica operativa de cada elemento individual de un grupo a una nueva clase, que realiza la operaci贸n por 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 facilita la adici贸n de nuevas funciones sin cambiar las clases visitadas en absoluto.

    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, algunos son clientes, algunos son moderadores, etc.

    Aunque pueden implementar diferentes interfaces y realizar diferentes funciones, este patr贸n puede ayudar a adquirir la cantidad correcta de usuarios.

    Implementaci贸n

    Cada art铆culo de 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 los art铆culos de nuestra 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铆:

    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 Pharos.shJavaDesignPatterns {
        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 producir谩:

    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 de trabajo.

    Si desea continuar leyendo sobre los patrones de dise帽o en Java, el siguiente art铆culo trata sobre los patrones de dise帽o J2EE.

     

    Etiquetas:

    Deja una respuesta

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