访问者模式在实际的开发中使用非常少,因为它比较难以实现,该模式可能导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用。
访问者模式是行为型设计模式的一种,它的原始定义是:允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离。
访问者模式主要解决的是数据与算法的藕合问题,尤其是在数据结构比较稳定,而算法多变的情况下,为了不污染数据本身,访问者会将多算法进行独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展。
访问者模式由以下几个角色组成
①抽象访问者:定义了一系列的操作方法,用来处理数据元素,通常都是同名方法,重载形式出现,以具体传入的数据元素类型进行区分,选择对应的重载方法调用。
②抽象元素接口:被访问的数据元素接口,定义了一个接收访问者的方法。这么做的目的是要每一个元素都可以被访问者访问。
③具体的元素角色:对抽象元素接口进行实现,通常情况下是使用访问者提供的的访问该元素的方法。
④具体的访问者:实现抽象访问者,重写对应的方法。
场景:我们以超市购物为例,假如超市中有三类商品:水果,糖果,酒水进行售卖,我们可以忽略每种商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中增加计价的方法是不合理的设计。
1)产品抽象类
/**
* 抽象产品类
*/
public abstract class Product {
private String name; //产品名称
private LocalDate previousDate; //生产日期
private double price; //单价
private int count; //数量
public Product(String name, LocalDate previousDate, double price, int count) {
this.name = name;
this.previousDate = previousDate;
this.price = price;
this.count = count;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getPreviousDate() {
return previousDate;
}
public void setPreviousDate(LocalDate previousDate) {
this.previousDate = previousDate;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
2)具体的产品
/**
* 糖果类
*/
public class Candy extends Product{
public Candy(String name, LocalDate previousDate, double price, int count) {
super(name, previousDate, price, count);
}
}
/**
* 水果类
*/
public class Fruit extends Product {
private double width; //重量
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public Fruit(String name, LocalDate previousDate, double price, int count, double width) {
super(name, previousDate, price, count);
this.width = width;
}
}
/**
* 酒水类
*/
public class Wine extends Product{
public Wine(String name, LocalDate previousDate, double price, int count) {
super(name, previousDate, price, count);
}
}
3)抽象访问者类:提供多个重载的方法
/**
* 抽象访问者
*/
public interface Viector {
//多个重载方法
void viecte(Candy candy);
void viecte(Wine wine);
void viecte(Fruit fruit);
}
4)具体的访问者类
public class DiscountViector implements Viector {
@Override
public void viecte(Candy candy) {
System.out.println("糖果打八折:" + candy.getName()+"价格:"+candy.getCount()*candy.getPrice()*0.8);
}
@Override
public void viecte(Wine wine) {
System.out.println("酒水打6折:" + wine.getName()+"价格:"+wine.getCount()*wine.getPrice()*0.6);
}
@Override
public void viecte(Fruit fruit) {
System.out.println("水果原价出售:" + fruit.getName()+"价格:"+fruit.getCount()*fruit.getPrice()*fruit.getWidth());
}
}
通过上述代码测试发现,我们并不能动态的执行访问者中的方法,因此我们引入抽象元素接口,改造产品类,由产品类去实现它。
5)抽象元素接口
/**
*抽象元素接口
*/
public interface Adeapter {
void accept(Viector viector);
}
改造的产品
/**
* 糖果类
*/
public class Candy extends Product implements Adeapter{
public Candy(String name, LocalDate previousDate, double price, int count) {
super(name, previousDate, price, count);
}
@Override
public void accept(Viector viector) {
viector.viecte(this);
}
}
...............
6) 客户端
public class Client {
public static void main(String[] args) {
//创建折扣访问者
DiscountViector discountViector = new DiscountViector();
//创建产品
List products = Arrays.asList(
new Candy("巧克力", LocalDate.MIN, 12, 3),
new Wine("九粮液", LocalDate.MIN, 200, 2),
new Fruit("橘子", LocalDate.MIN, 12, 4, 1.5));
//我们直接通过产品类的集合并不能一次计算出所有数据,因此我们引入抽象元素接口,提供一个方法,使产品类实现它,并返回自己
for (Adeapter product : products) {
product.accept(discountViector);
}
/**
* 糖果打八折:巧克力价格:28.8
* 酒水打6折:九粮液价格:240.0
* 水果原价出售:橘子价格:72.0
*/
}
}
①扩展性好:在不修改对象结构中元素的情况下,为对象结构中的元素添加新的功能。
②复用性好:通过访问者来定义整个对象结构的通用功能,从而提高复用程度
③分离无关行为:通过访问者来分离无关行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
①对象结构变化很困难:在访问者模式中,每增加一个新的元素,都要在每一个具体的访问者类中增加具体的操作,违背了开闭原则。
②违反了依赖倒转原则:访问者模式具体类,而没有依赖抽象类。
①当对象的数据结构相对稳定,而操作经常变化的时候。
②需要将数据结构与不常用的操作进行分离的时候。
③需要再运行时动态决定使用那些对象和方法的时候。