设计模式之访问者模式

设计模式之访问者模式

什么是访问者模式

访客设计模式是行为设计模式之一。当我们必须对一组相似类型的对象执行操作时,将使用访问者模式。 借助访问者模式,我们可以将操作逻辑从对象移动到另一个类。

例如,考虑一个购物车,我们可以在其中添加不同类型的项目(元素)。 当我们点击结帐按钮时,它将计算要支付的总金额。 现在,我们可以将计算逻辑包含在项目类中,或者可以使用访问者模式将此逻辑移到另一个类中。 让我们在访问者模式示例中实现此功能。

访问者设计模式Java示例

为了实现访客模式,首先我们将创建要在购物车中使用的不同类型的项目(元素)。

ItemElement.java

1
2
3
4
5
6
7
/**
* @author Mr.zxb
* @date 2020-09-19 18:55:06
*/
public interface ItemElement {
int accept(ShoppingCartVisitor visitor);
}

请注意,accept()方法采用Visitor参数。 我们可以有一些其他的方法也特定于项目,但是为了简单起见,我并没有讨论太多细节,而只关注访问者模式。

让我们为不同类型的项目创建一些具体的类。

Book.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Book
* @author Mr.zxb
* @date 2020-09-19 19:01:16
*/
public class Book implements ItemElement {
private int price;
private String isbnNumber;

public Book(int price, String isbnNumber) {
this.price = price;
this.isbnNumber = isbnNumber;
}

public int getPrice() {
return price;
}

public String getIsbnNumber() {
return isbnNumber;
}

@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}

Fruit.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Fruit
* @author Mr.zxb
* @date 2020-09-19 19:02:45
*/
public class Fruit implements ItemElement {
private int pricePerKg;
private int weight;
private String name;

public Fruit(int pricePerKg, int weight, String name) {
this.pricePerKg = pricePerKg;
this.weight = weight;
this.name = name;
}

public int getPricePerKg() {
return pricePerKg;
}

public int getWeight() {
return weight;
}

public String getName() {
return name;
}

@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}

注意具体类中accept()方法的实现,它调用Visitorvisit()方法并将其自身作为参数传递。

我们在Visitor界面中有针对不同类型项目的visit()方法,将由具体的访问者类实现。

ShoppingCartVisitor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 访问者
* @author Mr.zxb
* @date 2020-09-19 18:55:46
*/
public interface ShoppingCartVisitor {
/**
* 访问方法
* @param element
* @return
*/
int visit(ItemElement element);
}

现在,我们将实现访问者,并且每个项目都有其自己的逻辑来计算费用。

ShoppingCartVisitorImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 访问者实现
* @author Mr.zxb
* @date 2020-09-19 19:04:36
*/
public class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
@Override
public int visit(ItemElement element) {
int cost = 0;
// apply 5$ discount if book price is greater than 50
if (element instanceof Book) {
Book book = (Book) element;
if (book.getPrice() > 50) {
cost = book.getPrice() - 5;
} else {
cost = book.getPrice();
}
System.out.println("Book ISBN:: " + book.getIsbnNumber() + " cost = " + cost);
}
// fruit
if (element instanceof Fruit) {
Fruit fruit = (Fruit) element;
cost = fruit.getPricePerKg() + fruit.getWeight();
System.out.println(fruit.getName() + " cost = " + cost);
}
return cost;
}
}

让我们看看如何在客户端应用程序中使用访问者模式示例。

ShoppingCartClient.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @author Mr.zxb
* @date 2020-09-19 19:08:50
*/
public class ShoppingCartClient {
public static void main(String[] args) {
// items
ItemElement[] items = new ItemElement[]{
new Book(20, "1234"),
new Book(100, "5678"),
new Fruit(10, 2, "Banana"),
new Fruit(5, 5, "Apple")
};

// total price
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 += item.accept(visitor);
}
return sum;
}
}

当我们在访问者模式客户端程序之上运行时,我们得到以下输出。

1
2
3
4
5
Book ISBN:: 1234 cost = 20
Book ISBN:: 5678 cost = 95
Banana cost = 12
Apple cost = 10
Total Cost = 137

请注意,所有项目中的accept()方法的实现都是相同的,但可以有所不同,例如,可以使用逻辑检查项目是否可用,然后根本不调用visit()方法。

访问者设计模式类图

我们的访问者设计模式实现的类图是:

访问者模式的好处

这种模式的好处是,如果操作逻辑发生变化,那么我们仅需要在访问者实现中进行更改,而无需在所有项目类中进行更改。

另一个好处是,将新项目添加到系统很容易,它仅需要在访问者界面和实现中进行更改,而现有项目类将不受影响。

访问者模式限制

访客模式的缺点在于,在设计时我们应该知道visit()方法的返回类型,否则我们将不得不更改接口及其所有实现。 另一个缺点是,如果访问者接口的实现过多,则很难扩展。