Обзор
Это третья статья из короткой серии, посвященной шаблонам проектирования в Java , и прямое продолжение предыдущей статьи - Шаблоны структурного проектирования в Java.
Поведенческие модели
Поведенческие паттерны связаны с предоставлением решений, касающихся взаимодействия объектов - как они взаимодействуют, как некоторые из них зависят от других, и как разделить их, чтобы они были как зависимыми, так и независимыми и обеспечивали как гибкость, так и возможности тестирования.
Поведенческие паттерны в Java, которые рассматриваются в этой статье:
- Устный переводчик
- Шаблон Метод / Шаблон
- Цепочка ответственности
- Командование
- Итератор
- Посредник
- Memento
- Наблюдатель
- Состояние
- Стратегия
- Посетитель
Устный переводчик
Шаблон интерпретатора используется всякий раз, когда нам нужно оценить любую грамматику или выражения языка. Хорошим примером этого шаблона может быть 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;
}
}
Он заменяет все нецифровые символы пустыми символами и разбивает ввод на токены. Это в основном оставляет нас без цифр.
Теперь давайте реализуем Expression
с несколькими конкретными
классами:
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.");
}
}
Прежде всего, определяется статический метод getChainOfEmployees()
.
Этот метод используется для установки уровней полномочий каждого
подразделения через их конструкторы и для определения порядка
ответственности.
Устанавливая следующего Employee
для Programmer
, мы, по сути,
сообщаем ему, к кому обратиться, если запрос выходит за рамки его
возможностей.
Естественно, программист обратится к назначенному им LeadProgrammer
.
Если запрос слишком велик, они даже не могут его обработать, они
обратятся за помощью Manager
Выполнение этого фрагмента кода даст:
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.
Programmer
назначается работа над запросом на его собственном уровне
полномочий, и они делают это изящно.
Затем поступает новый запрос, требующий полномочий LeadProgrammer
,
поэтому они вступают во владение.
Наконец, приходит еще один запрос, требующий полномочий Manager
.
Программист обращается за помощью к назначенному ведущему программисту,
который, в свою очередь, решает обратиться за помощью к своему
менеджеру, и тот с радостью соглашается и выполняет свою работу.
Командование
Другой шаблон проектирования развязки, шаблон Command работает, заключая запрос от отправителя в объект, называемый командой . Затем эта команда передается объекту-инициатору, который приступает к поиску адекватного способа обработки запроса.
Найдя подходящий способ, он передает команду, где она будет выполнена.
Выполнение
Смоделируем работу программиста по этому шаблону. Клиент может отправить
Order
- команду, для Application
- запрос. Затем программист может
создать приложение и продать его клиенту.
Составим нашу команду:
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();
}
}
Нужен объект invoker, на который мы отправляем запрос:
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();
}
}
Запрос, даже если он является Application
как Order
- команда ,
как описано перед реализацией.
И чтобы проиллюстрировать суть этого паттерна:
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.
Итератор
Шаблон Iterator используется в качестве основного шаблона Java Collection Framework . Он используется для доступа к членам коллекций, при этом скрывая базовую реализацию.
Выполнение
Это довольно простая реализация, которая используется в качестве основного шаблона во многих фреймворках, включая упомянутый выше фреймворк.
Мы сделаем простой итератор для вывода имен наших сотрудников.
У всех наших сотрудников есть собственный сектор, в котором они работают. Таким образом, работа в секторе также включает в себя итератор для всех них.
Итак, давайте продолжим и определим наш Iterator
:
public interface Iterator {
public boolean hasNext();
public Object next();
}
Этот итератор будет храниться в своего рода контейнере. В нашем случае
Sector
работы:
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
Чтобы проиллюстрировать суть этого паттерна:
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
Посредник
Подобно паттерну «Адаптер», но с другой целью. Паттерн Посредник действует как мост и, как следует из названия, посредник между различными объектами, которые взаимодействуют каким-либо образом. В крупномасштабных приложениях прямая связь означает тесную связь, что затрудняет тестирование, обслуживание и масштабирование.
Шаблон «Посредник» решает эту проблему, выступая в качестве третьей стороны, через которую осуществляется обмен данными, разделяя их в процессе.
Выполнение
Это довольно простая реализация, и, вероятно, самая известная - это чат между двумя людьми.
Объект User
желает общаться с другим, поэтому они используют для этого
платформу-посредник между собой - Chat
:
public class Chat {
public static void showMessage(User user, String message) {
System.out.println(new Date().toString() + "[" + user.getName() + "]: " + message);
}
}
Этот класс содержит только один метод и, принимая User
и String
,
форматирует параметры и показывает сообщение.
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()
Этот метод вызывает
static
метод из класса Chat
this
экземпляром пользователя и
String
в качестве аргументов.
Чтобы проиллюстрировать суть этого паттерна:
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
Паттерн Memento связан с предыдущими состояниями объекта. Это означает, что шаблон используется, когда мы хотим сохранить некоторое состояние объекта, в случае, если мы, возможно, захотим восстановить объект в это состояние позже.
Выполнение
Этот шаблон основан на работе трех классов, также известных как классы
акторов . Объект Memento
содержит состояние, которое мы хотим
сохранить для дальнейшего использования. Объект Originator
создает и
сохраняет состояния в Memento
, а CareTaker
заботится о процессе
восстановления.
Давайте сначала определим наш сувенир:
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<Memento> 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 действительно предоставляет и класс, и интерфейс с учетом этого шаблона, он не получил широкого распространения, потому что не был реализован идеальным образом.
Выполнение
Чтобы проиллюстрировать этот образец, мы построим небольшой офис с CEO
, Manager
, LeadProgrammer
и Programmer
.
За программистом будут наблюдать начальство, которое оценивает его по тому, насколько хорошо он выполняет свою работу:
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();
}
}
}
С его наблюдателями существуют отношения «один ко многим», и каждое изменение состояния уведомляет их всех.
У всех этих наблюдателей есть несколько общих черт:
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.");
}
}
}
Генеральный CEO
заботится не о программисте, а о результате, оставляя
его в умелых руках Manager
и LeadProgrammer
. Менеджер в основном
озабочен тем, может ли ведущий программист направлять программиста в
выполнении его работы. И, наконец, ведущего программиста больше всего
волнует то, что делает программист.
Чтобы проиллюстрировать суть этого паттерна:
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.";
}
}
Подобно паттерну State, Context
будет использовать стратегию:
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 .