设计模式之访问者模式

亦称:Visitor Design Pattern

🔥

意图:访问者模式是一种行为设计模式, 它能将算法与其所作用的对象隔离开来。


一、代码示例

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

public interface ItemElement {

	public int accept(ShoppingCartVisitor visitor);
}
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);
	}

}

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

}

将新行为放入一个名为访问者的独立类中,而不是试图将其整合到已有类中,

这里的新行为指的是:计算不同商品的价格

ShoppingCartVisitor既然要计算不同商品的价格,就必须要知道商品的属性。

这里作为参数进行传入。

注意 :这里元素之间可能并没有什么共同的接口,属性也不尽相同。计算价格的时候自然也不一致,

所以我们↘️ 不能使用多态机制。

但它们显然都有计算价格这一行为。

package com.journaldev.design.visitor;

public interface ShoppingCartVisitor {

	int visit(Book book);
	int visit(Fruit fruit);
}

访问者的具体实现:

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

}

客户端该如何调用这些方法呢?

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

}

When we run above visitor pattern client program, we get following output.

Book ISBN::5678 cost =95
Banana cost = 20
Apple cost = 25
Total Cost = 160

二、类图

image-20201101134955560

三、小结

  • 开闭原则。 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。

  • 单一职责原则。 可将同一行为的不同版本移到同一个类中。

  • 访问者对象可以在与各种对象交互时收集一些有用的信息。 当你想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。

四、思考

为何要有accpet()方法呢?如果没有该方法,即我们的book、fruit中没有accept方法

那现在客户端要计算总的价格,该如何去调用呢?

可以参考:https://refactoringguru.cn/design-patterns/visitor-double-dispatch

posted @ 2020-11-01 14:19  HeliusKing  阅读(176)  评论(6编辑  收藏  举报