Рубрики
Без рубрики

Поведенческие шаблоны проектирования в Java

Автор оригинала: David Landup.

Обзор

Это третья статья в короткой серии , посвященной шаблонам проектирования в Java, и прямое продолжение предыдущей статьи – Структурные шаблоны проектирования в Java.

Поведенческие паттерны

Поведенческие паттерны связаны с предоставлением решений, касающихся взаимодействия объектов – как они взаимодействуют, как одни зависят от других и как разделить их, чтобы они были как зависимыми, так и независимыми и обеспечивали гибкость и возможности тестирования.

Поведенческие паттерны в Java, которые рассматриваются в этой статье, следующие:

  • Переводчик
  • Метод шаблона/Шаблон
  • Цепочка ответственности
  • Команда
  • Итератор
  • Посредник
  • Сувенир
  • Наблюдатель
  • Государство
  • Стратегия
  • Посетитель

Переводчик

Шаблон интерпретатора используется в любое время, когда нам нужно оценить грамматику или выражения любого языка. Хорошим примером этого шаблона может быть Google Translate , который интерпретирует ввод и показывает нам вывод на другом языке.

Другим примером может быть компилятор Java. Компилятор интерпретирует код Java и переводит его в байт-код, который JVM использует для выполнения операций на устройстве, на котором он работает.

Этот шаблон также представляет собой отличный способ написания простых программ, которые понимают синтаксис, подобный человеческому.

Реализация

Мы будем создавать простую реализацию с простой грамматикой, иначе она стала бы запутанной и слишком сложной для этого урока.

Чтобы реализовать этот шаблон проектирования, нам нужно будет определить механизм интерпретатора, сопровождаемый различными выражениями, которые он будет использовать для интерпретации команды.

Давайте определим интерфейс для всех этих выражений:

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

Этот механизм интерпретатора прост:

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;
    }
}

Он заменяет все нецифровые символы пустыми символами и разбивает входные данные на токены. Это в основном оставляет нас без цифр.

Теперь давайте реализуем интерфейс Выражение с несколькими конкретными классами:

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);
    }
}

И чтобы проиллюстрировать суть шаблона:

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"));
    }
}

Поскольку мы отбросили все нецифровые символы, здесь можно оценить, должен ли интерпретатор добавлять или умножать входные данные.

Запуск этого фрагмента кода приведет к:

add 15 and 25
Result: 40
add 5 and 5
multiply 10 and 10
Result: 100

Метод шаблона

Метод шаблона, иначе известный как Шаблон шаблона, повсюду вокруг нас. Это сводится к определению абстрактного класса, который предоставляет предопределенные способы запуска своих методов. Подклассы, которые наследуют эти методы, также должны следовать пути, определенному в абстрактном классе.

В некоторых случаях абстрактный класс может уже включать реализацию метода, а не только инструкции, если это функциональность, которая будет использоваться совместно всеми или большинством подклассов.

Реализация

В компании у всех сотрудников есть несколько общих обязанностей:

public abstract class Employee {
    abstract void work();
    abstract void takePause();
    abstract void getPaid();
    
    public final void comeToWork() {
        work();
        takePause();
        work();
        getPaid();
    }
}

Они все приходят на работу, все отдыхают и получают зарплату.

Разные сотрудники выполняют разные виды работы:

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.");
    }
}

Но они по-прежнему следуют шаблону работы, делают паузу и получают деньги, что все изложено в интерфейсе.

Чтобы проиллюстрировать суть этой модели:

public class Main {
    public static void main(String[] args) {
        Employee employee = new Programmer();
        employee.comeToWork();
     
        System.out.println();
        
        employee = new Manager();
        employee.comeToWork();
    }
}

Запуск этого фрагмента кода приведет к:

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.

Цепочка ответственности

Модель цепочки ответственности широко используется и принимается. Он определяет цепочку объектов, которые коллективно, один за другим, обрабатывают запрос, где каждый процессор в цепочке имеет свою собственную логику обработки.

Каждое из этих подразделений обработки решает, кто должен продолжить обработку запроса следующим, и каждое из них имеет ссылку на следующее в строке.

Важно отметить, что это очень удобно для отделения отправителя от получателя.

Реализация

Как обычно, давайте определим абстрактный класс:

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);
}

Этот абстрактный класс содержит уровни полномочий для всех сотрудников. Программист занимает меньшее место в иерархии, чем ведущий программист, который, в свою очередь, находится ниже менеджера.

Мы также включили ссылку на следующего сотрудника, и вы скоро поймете, почему это важно.

Определен общий метод для всех этих классов с проверкой полномочий. Если у определенного класса нет полномочий, он передает запрос следующему в цепочке ответственности.

Теперь давайте расширим этот класс:

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);
    }
}

Как упоминалось выше, каждый из этих блоков обеспечивает свою собственную логику обработки.

Чтобы проиллюстрировать суть этой модели:

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.");
    }
}

Во-первых, определен статический метод get Chain Of Employees () . Этот метод используется для задания уровней полномочий каждого подразделения с помощью их конструкторов и определения порядка ответственности.

Задавая следующего Сотрудника для Программиста , мы в основном указываем ему, к кому обратиться, если запрос выходит за рамки его возможностей.

Естественно, программист обратится к назначенному им Ведущему программисту . Если запрос окажется слишком большим даже для них, они обратятся за помощью к своему Менеджеру .

Запуск этого фрагмента кода приведет к:

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.

Программисту назначается работа над запросом на их собственном уровне полномочий, и они делают это изящно.

Затем поступает новый запрос, требующий полномочий Ведущего программиста , поэтому они берут на себя управление.

Наконец, поступает еще один запрос, требующий полномочий Менеджера . Программист обращается за помощью к назначенному им ведущему программисту, который, в свою очередь, решает обратиться за помощью к своему менеджеру, и менеджер с радостью соглашается и выполняет свою работу.

Команда

Другой шаблон проектирования развязки, шаблон команды, работает путем обертывания запроса от отправителя в объект, называемый командой /. Затем эта команда передается объекту-вызывателю, который продолжает поиск подходящего способа обработки запроса.

Как только он найдет подходящий способ, он передаст команду, где она будет выполнена.

Реализация

Давайте смоделируем работу программиста для этого шаблона. Клиент может отправить Заказ – команду, для Приложения – запрос. Затем программист может создать приложение и продать его клиенту.

Давайте сделаем нашу команду:

public interface Order {
    void placeOrder();
}

И наша просьба:

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.");
    }
}

Предполагая, что программист согласился работать с клиентом, было бы уместно сделать приложение:

public class MakeApplication implements Order {
    private Application application;
    
    public MakeApplication(Application application) {
        this.application = application;
    }
    
    @Override
    public void placeOrder() {
        application.make();
    }
}

И после его создания программист приступит к его продаже:

public class SellApplication implements Order {
    private Application application;
    
    public SellApplication(Application application) {
        this.application = application;
    }

    @Override
    public void placeOrder() {
        application.sell();
    }
}

Необходим объект-вызыватель, на который мы отправляем запрос:

public class Programmer {
    private List orderList = new ArrayList<>();
    
    public void takeOrder(Order order) {
        orderList.add(order);
    }
    
    public void placeOrders() {
        for(Order order : orderList) {
            order.placeOrder();
        }
        orderList.clear();
    }
}

Запрос, даже если это Приложение , завернут в Заказ – a команда , как описано до реализации.

И чтобы проиллюстрировать суть этой модели:

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();
    }
}

Запуск этого фрагмента кода приведет к:

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

Итератор

Шаблон итератора используется в качестве основного шаблона Java Collection Framework . Он используется для доступа ко всем элементам темы коллекций, скрывая базовую реализацию.

Реализация

Это довольно простая реализация, и она используется в качестве основного шаблона во многих фреймворках, включая упомянутый выше фреймворк.

Мы создадим простой итератор для печати имен наших сотрудников.

Все наши сотрудники имеют свой собственный сектор, в котором они работают. Таким образом, работа в секторе также включает итератор для всех из них.

Итак, давайте продолжим и определим наш Итератор :

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

Этот итератор будет храниться в своего рода контейнере. В нашем случае это работа Сектор :

public interface Sector {
    public Iterator getIterator();
}

Теперь давайте определим хранилище для наших сотрудников:

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;
        }
    }
}

Для простоты мы использовали только массив строк и избегали определения отдельного класса Employee .

Чтобы проиллюстрировать суть этой модели:

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

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);
        }
    }
}

Запуск этого фрагмента кода приведет к:

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

Посредник

Аналогично шаблону адаптера, но с другой целью. Шаблон посредника действует как мост и, как следует из названия, посредник между различными объектами, которые каким-либо образом взаимодействуют. В крупномасштабных приложениях прямая связь означает тесную связь, что затрудняет тестирование, обслуживание и масштабирование.

Шаблон посредника решает эту проблему, выступая в качестве третьей стороны, через которую осуществляется связь, отделяя их в процессе.

Реализация

Это довольно простая реализация, и, вероятно, самая известная из них-это чат между двумя людьми.

Пользователь объект желает общаться с другим, поэтому они используют для этого посредническую платформу между собой -/| Чат :

public class Chat {
    public static void showMessage(User user, String message) {
        System.out.println(new Date().toString() + "[" + user.getName() + "]: " + message);
    }
}

Этот класс содержит только один метод и, принимая Пользователя и Строку , форматирует параметры и отображает сообщение.

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);
    }
}

Наш класс User определяет метод SendMessage () . Этот метод вызывает статический метод из класса Chat с этим экземпляром пользователя и Строкой в качестве аргументов.

Чтобы проиллюстрировать суть этой модели:

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?");
    }
}

Эти два объекта не взаимодействуют напрямую. Ни один из них не указывает на какую-либо ссылочную переменную или другой объект, однако класс Chat действует как посредник и соединяет их.

Запуск этого фрагмента кода приведет к:

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?

Сувенир

Шаблон памятки связан с предыдущими состояниями объекта. Это означает, что шаблон используется, когда мы хотим сохранить какое-либо состояние объекта, в случае, если мы захотим восстановить объект в это состояние позже.

Реализация

Этот шаблон основан на работе трех классов, также известных как классы актеров . Объект Memento содержит состояние, которое мы хотим сохранить для последующего использования. Создатель объект создает и сохраняет состояния в Памятных объектах, в то время как Хранитель объект заботится о процессе восстановления.

Давайте сначала определим наш сувенир:

public class Memento {
    private String state;
    
    public Memento(String state) {
        this.state = state;
    }
    
    public String getState() {
        return state;
    }
}

Затем наш создатель и хранитель:

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 mementoList = new ArrayList<>();
    
    public void add(Memento memento) {
        mementoList.add(memento);
    }
    public Memento get(int index) {
        return mementoList.get(index);
    }
}

И чтобы проиллюстрировать суть шаблона:

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());
    }
}

Запуск этого фрагмента кода приведет к:

Current state: State 3 at 1535705131218
First saved state: State 2 at 1535705131218
Second saved state: State 3 at 1535705131218

Наблюдатель

Шаблон наблюдателя используется для мониторинга состояния определенного объекта, часто находящегося в группе или в отношениях “один ко многим”. В таких случаях большую часть времени измененное состояние одного объекта может повлиять на состояние остальных, поэтому должна существовать система, которая будет отмечать изменение и предупреждать другие объекты.

Хотя Java предоставляет как класс, так и интерфейс с учетом этого шаблона, он не получил широкого распространения, потому что не был реализован идеальным способом.

Реализация

Чтобы проиллюстрировать эту закономерность, мы построим небольшой офис с генеральным директором , Менеджером , Ведущим программистом и Программистом .

За программистом будет наблюдать его начальство, которое имеет о нем мнение, основанное на том, насколько хорошо он выполняет свою работу:

public class Programmer {
    private List 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();
        }
    }
}

С его наблюдателями существуют отношения “один ко многим”, и каждое изменение состояния уведомляет их всех.

У всех этих наблюдателей есть пара общих черт:

public abstract class Observer {
    protected Programmer programmer;
    public abstract void update();
}

Но у каждого есть своя реализация:

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.");
        }
    }
}

Генеральный директор заботится не о программисте, а о результате, оставляя его в надежных руках Менеджера и Ведущего программиста . Менеджер в основном озабочен тем, может ли ведущий программист руководить программистом в выполнении его работы. И, наконец, ведущий программист в основном озабочен тем, что делает программист.

Чтобы проиллюстрировать суть этой модели:

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");
    }
}

Запуск этого фрагмента кода приведет к:

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.

Государство

Шаблон состояния используется, когда конкретному объекту необходимо изменить свое поведение в зависимости от его состояния. Это достигается путем предоставления каждому из этих объектов одного или нескольких объектов состояния.

Основываясь на этих объектах состояния, мы можем полностью изменить поведение соответствующего объекта.

Реализация

Давайте определим простой интерфейс:

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

Это состояние будет перенесено через контекст:

public class Context {
    private State state;
    
    public Context() {
        state = null;
    }
    
    public void setState(State state) {
        this.state = state;
    }
    
    public State getState() {
        return state;
    }
}

И два конкретных класса реализуют его:

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.";
    }    
}

Теперь, чтобы проиллюстрировать суть этой модели:

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());
    }
}

Запуск этого фрагмента кода приведет к:

The application is in the starting state of development.
Starting state.
The application is in the finished state of development.
Finished state.

Как вы можете видеть, поведение перевозчика изменяется государством.

Стратегия

Шаблон стратегии используется в ситуациях, когда алгоритмы или поведение класса должны быть динамичными. Это означает, что как поведение, так и алгоритмы могут быть изменены во время выполнения на основе входных данных клиента.

Подобно шаблону состояния, этот шаблон использует несколько объектов стратегии, которые определяют различные стратегии для целевого класса. Целевой класс адаптирует свои алгоритмы и поведение на основе этих стратегий.

Реализация

Давайте начнем с определения стратегии:

public interface Strategy {
    public String build(String location);
}

Эта стратегия будет использоваться для строительства различных типов зданий в разных местах. Эти типы зданий реализуют стратегию каждый по – своему:

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.";
    }
}

Аналогично шаблону состояния, Контекст класс будет использовать стратегию:

public class BuildContext {
    private Strategy strategy;
    
    public BuildContext(Strategy strategy) {
        this.strategy = strategy;
    }
    
    public String executeStrategy(String location) {
        return strategy.build(location);
    }
}

И чтобы проиллюстрировать суть этой модели:

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"));
    }
}

Запуск этого фрагмента кода приведет к:

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.

Посетитель

Шаблон посетителя используется для перемещения операционной логики из каждого отдельного элемента группы в новый класс, который выполняет для них операции, используя данные из каждого отдельного элемента.

Это делается путем того, что все элементы принимают “посетителя”. Этот посетитель будет выполнять изменения в отдельном классе, не изменяя структуру посещаемого класса вообще. Это позволяет легко добавлять новые функциональные возможности без изменения посещаемых классов вообще.

При этом объекты не обязательно должны быть одинаковыми и могут быть несвязанными, реализующими разные интерфейсы и т. Д. Примером может служить приложение, которое подсчитывает количество пользователей на веб-сайте. Некоторые из этих пользователей являются администраторами, некоторые-клиентами, некоторые-модераторами и т.д.

Несмотря на то, что они могут реализовывать разные интерфейсы и выполнять разные функции, этот шаблон может помочь набрать нужное количество пользователей.

Реализация

Каждый товар в нашем магазине сможет принять посетителя:

public interface Item {
    public int accept(Visitor visitor);
}

А вот и наш посетитель:

public interface Visitor {
    int visit(Pen pen);
    int visit(Notebook notebook);
}

Давайте определим конкретные классы для товаров нашего магазина:

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);
    }
}

А теперь давайте реализуем интерфейс для посетителей и продемонстрируем этот шаблон дизайна. Класс реализации будет иметь свою собственную логику расчета цены товаров, а не самих товаров:

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;
    }
}

И чтобы проиллюстрировать суть шаблона:

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;
    }
}

Запуск этого фрагмента кода приведет к:

Parker costs 10
Pilot costs 5
Notebook costs 50
Notebook costs 70
Total price of items: 135

Вывод

Таким образом, все поведенческие шаблоны проектирования в Java полностью описаны с рабочими примерами.

Если вы хотите продолжить чтение о шаблонах проектирования в Java, в следующей статье рассматриваются шаблоны проектирования J2EE .