Шаблон посетителя является одним из шаблонов поведенческого дизайна . Шаблон посетителя используется, когда мы должны выполнить операцию над группой объектов подобного типа. С помощью шаблона посетителя мы можем переместить операционную логику из объектов в другой класс.
Например, представьте корзину покупок, в которую мы можем добавить различные типы товаров (элементы), когда мы нажимаем кнопку «Оформить заказ», она вычисляет общую сумму, подлежащую оплате. Теперь у нас может быть логика вычисления в классах элементов или мы можем переместить эту логику в другой класс, используя шаблон посетителя. Давайте реализуем это в нашем примере шаблона посетителя.
Для реализации шаблона посетителя, прежде всего, мы создадим различные типы элементов (элементов), которые будут использоваться в корзине.
ItemElement.java
1
2
3
4
5
6
|
package com.journaldev.design.visitor; public interface ItemElement { public int accept(ShoppingCartVisitor visitor); } |
Обратите внимание, что метод accept принимает аргумент Visitor, у нас могут быть и другие методы, специфичные для элементов, но для простоты я не буду вдаваться в подробности и остановлюсь только на шаблонах посетителей.
Давайте создадим несколько конкретных классов для разных типов предметов.
Book.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package com.journaldev.design.visitor; public class Book implements ItemElement { private int price; private String isbnNumber; public Book( int cost, String isbn){ this .price=cost; this .isbnNumber=isbn; } public int getPrice() { return price; } public String getIsbnNumber() { return isbnNumber; } @Override public int accept(ShoppingCartVisitor visitor) { return visitor.visit( this ); } } |
Fruit.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.journaldev.design.visitor; public class Fruit implements ItemElement { private int pricePerKg; private int weight; private String name; public Fruit( int priceKg, int wt, String nm){ this .pricePerKg=priceKg; this .weight=wt; this .name = nm; } public int getPricePerKg() { return pricePerKg; } public int getWeight() { return weight; } public String getName(){ return this .name; } @Override public int accept(ShoppingCartVisitor visitor) { return visitor.visit( this ); } } |
Обратите внимание на реализацию метода accept () в конкретных классах, вызов метода метода vis () Visitor и передачу себя в качестве аргумента.
У нас есть метод visit () для различных типов элементов в интерфейсе посетителя, который будет реализован конкретным классом посетителя.
ShoppingCartVisitor.java
1
2
3
4
5
6
7
|
package com.journaldev.design.visitor; public interface ShoppingCartVisitor { int visit(Book book); int visit(Fruit fruit); } |
Теперь мы реализуем интерфейс посетителя, и у каждого элемента будет своя логика для расчета стоимости.
ShoppingCartVisitorImpl.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package com.journaldev.design.visitor; public class ShoppingCartVisitorImpl implements ShoppingCartVisitor { @Override public int visit(Book book) { int cost= 0 ; //apply 5$ discount if book price is greater than 50 if (book.getPrice() > 50 ){ cost = book.getPrice()- 5 ; } else cost = book.getPrice(); System.out.println( "Book ISBN::" +book.getIsbnNumber() + " cost =" +cost); return cost; } @Override public int visit(Fruit fruit) { int cost = fruit.getPricePerKg()*fruit.getWeight(); System.out.println(fruit.getName() + " cost = " +cost); return cost; } } |
Давайте посмотрим, как мы можем использовать его в клиентских приложениях.
ShoppingCartClient.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package com.journaldev.design.visitor; public class ShoppingCartClient { public static void main(String[] args) { ItemElement[] items = new ItemElement[]{ new Book( 20 , "1234" ), new Book( 100 , "5678" ), new Fruit( 10 , 2 , "Banana" ), new Fruit( 5 , 5 , "Apple" )}; int total = calculatePrice(items); System.out.println( "Total Cost = " +total); } private static int calculatePrice(ItemElement[] items) { ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl(); int sum= 0 ; for (ItemElement item : items){ sum = sum + item.accept(visitor); } return sum; } } |
Когда мы запускаем вышеуказанную программу, мы получаем следующий вывод.
1
2
3
4
5
|
Book ISBN::1234 cost =20 Book ISBN::5678 cost =95 Banana cost = 20 Apple cost = 25 Total Cost = 160 |
Обратите внимание, что реализация, если метод accept () во всех элементах одинаков, но может отличаться, например, может быть логика для проверки, свободен ли элемент, тогда вообще не вызывайте метод visit ().
Диаграмма классов шаблона посетителя
Диаграмма классов для нашей реализации шаблона посетителя:
Преимущество этого шаблона заключается в том, что если логика работы изменяется, то нам нужно вносить изменения только в реализацию посетителя, а не делать это во всех классах элементов.
Еще одно преимущество заключается в том, что добавить новый элемент в систему легко, для этого потребуется изменить только интерфейс и реализацию посетителя, и существующие классы элементов не будут затронуты.
Недостаток шаблона посетителя заключается в том, что мы должны знать возвращаемый тип методов visit () во время разработки, в противном случае нам придется изменить интерфейс и все его реализации. Другим недостатком является то, что если существует слишком много реализаций интерфейса посетителя, это затрудняет расширение.