设计模式-访问者模式(24)
定义
访问者(Visitor Pattern)的目的是封装一些施加于某种数据结构元素之上的操作,即使这些操作需要修改,接收这个操作的数据结构却可以保持不变。
英文原话:Represent an operation to be performed on the elements of an object structure.Visitor lets you define a new operation without changing the classes of the elements on which it operates.
翻译:封装一些作用于某种数据结构的各个元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作。
角色
抽象访问者(Visitor)角色:该角色声明一个或多个访问操作,定义访问者可以访问哪些元素。
具体访问者(Concrete Visitor)角色:该角色实现抽象访问者角色中的各个访问操作。
抽象元素(Element)角色:该角色声明一个接收操作,接收一个访问者的对象。
具体元素(Concrete Element)角色:该角色实现抽象元素角色中的接收操作。
结构对象(Object Structure)角色:该角色有以下责任,可以遍历结构中的所有元素。如果需要,该角色会提供一个高层次的接口让访问者对象可以访问每一个元素,也可以设计一个复合对象或者一个集合,如List或Set。
/** * 抽象元素角色 */ public abstract class Element { //接收操作 public abstract void accept(Visitor vi); } /** * 具体元素1 */ public class ConcreteElement1 extends Element{ //接收操作 @Override public void accept(Visitor vi) { vi.visit(this); } //业务逻辑方法 public void operation(){ System.out.println("访问元素一"); } } /** * 具体元素2 */ public class ConcreteElement2 extends Element{ //接收操作 @Override public void accept(Visitor vi) { vi.visit(this); } //业务逻辑方法 public void operation(){ System.out.println("访问元素二"); } } /** * 抽象访问者 */ public interface Visitor { //可以访问哪些元素对象 public void visit(ConcreteElement1 el1); public void visit(ConcreteElement2 el2); } /** * 具体访问者角色 */ public class ConcreteVisitor implements Visitor { //访问元素1 @Override public void visit(ConcreteElement1 el1) { el1.operation(); } //访问元素2 @Override public void visit(ConcreteElement2 el2) { el2.operation(); } }
public class Client { public static void main(String[] args) { //创建一个结构对象,类似于列表结构的os ObjectStructure os = new ObjectStructure(); //生成元素,给os生成十个元素 os.createElements(); //创建一个访问者对象 Visitor vi = new ConcreteVisitor(); //结构对象调用访问者对结构对象进行访问,访问者对结构中的元素进行访问(执行访问) //1.先把访问者vi传入结构体中, //2.循环结构体每一个元素将vi传到每一个具体元素中, //3.元素方法中调用vi.visit(this)将自己作为参数传到访问者中, //4.访问者获取到具体元素即可进行操作 os.action(vi); } }
优点
- 访问者模式使得增加新的操作变得很容易,只需增加新的访问者类即可。
- 访问者模式将有关的行为集中到一个访问者对象中,而不是分散到很多个元素类中。
- 访问者模式可以跨过几个类的等级结构范根属于不同的等级结构的成员类。
- 累积状态。每一个单独的访问者对象都集中了相关的行为,从而也就可以在访问的过程中将执行操作的状态积累在自己的内部,而不是分散到很多的元素对象中,易于系统的维护。
缺点
- 新增加的元素类变得困难。每增加一个新的元素类就意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。
- 破坏封装性。访问者模式要求访问者对象访问每一个元素对象并调用每一个元素对象的操作,这隐含了一个对所有元素对象的要求,即访问者对象必须暴露一些自己的操作和内部状态,否则访问者的访问就变得没有意义。由于访问者对象自己会积累访问操作所需的状态,从而使得这些状态不在存储在元素对象汇总,破坏了类的封装性。
- 违背了依赖倒置原则。访问者模式依赖的是具体的元素,而不是抽象的元素,这破坏了依赖倒置的原则,特别是在面向对象的编程中,访问者对象抛弃了对接口的依赖,而直接依赖实现类,扩展比较难。
应用场景
- 一个对象结构包含很多类对象,它们有不同的接口,当对这些对象实施依赖于具体类的操作时,即在迭代器模式不能胜任的场景下,可以采用访问者模式。
- 需要一个对象结构中的对象进行很多不同并且不相关的操作,避免操作污染类。
- 业务规则要求遍历过个不同的对象,这本身也是访问者模式的出发点。迭代器模式只能访问同类或同接口的数据,而访问者模式是对迭代器模式的扩充,它可以遍历不同的对象,执行不同的操作。
/** * 药品--抽象元素 */ public abstract class Medicine { private String name; //药名 private Double price; //药价 public Medicine(String name, Double price) { this.name = name; this.price = price; } //接受访问者 public abstract void accept(IVisitor visitor); public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } } /** * 药品A--数据结构元素 */ public class MedicineA extends Medicine{ public MedicineA(String name, Double price) { super(name, price); } //元素接收访问者 @Override public void accept(IVisitor visitor) { visitor.doVisit(this); //元素将自己交给访问者 } } /** * 药品B-数据结构元素 */ public class MedicineB extends Medicine{ public MedicineB(String name, Double price) { super(name, price); } //对药品进行操作 @Override public void accept(IVisitor visitor) { visitor.doVisit(this); } } /** * 抽象访问者-里面的方法访问数据结构中的元素 */ public interface IVisitor { //对元素A进行操作 public void doVisit(MedicineA a); //对元素B进行操作 public void doVisit(MedicineB b); } /** * 对药品收费---访问者对元素进行访问 */ public class CasherVisitor implements IVisitor { //访问数据结构中的元素A @Override public void doVisit(MedicineA a) { System.out.println("收费员对:"+a.getName()+"进行收费"+a.getPrice()+"元"); } //访问数据结构中的元素B @Override public void doVisit(MedicineB b) { System.out.println("收费员对:"+b.getName()+"进行收费"+b.getPrice()+"元"); } } /** * 药剂师取药--访问者 */ public class PharmacistsVisitor implements IVisitor{ //访问数据结构中的元素A @Override public void doVisit(MedicineA a) { System.out.println("药剂师拿药:"+a.getName()); } //访问数据结构中的元素B @Override public void doVisit(MedicineB b) { System.out.println("药剂师拿药:"+b.getName()); } } /** * 药单---数据结构(元素为药品) */ public class MedicineList { private MedicineA a; private MedicineB b; //构造函数初始化元素 public MedicineList() { this.a = new MedicineA("板蓝根",8.8); this.b = new MedicineB("白加黑",16.0); } public void accept(IVisitor visitor){ this.a.accept(visitor); this.b.accept(visitor); } } /** * 结构对象角色 */ public class ObjectStructure { private Vector<Element> elements; //构造函数 public ObjectStructure() { this.elements = new Vector<Element>(); } //执行访问操作,循环每一个元素对象,并将访问者对象传入到元素中去 public void action(Visitor vi){ for (Element e:elements){ e.accept(vi); } } //添加新元素 public void add(Element e){ elements.add(e); } //元素生成器,这里通过一个工厂方法进行模拟 public void createElements(){ Random rand = new Random(); for (int i=0;i<10;i++){ if(rand.nextInt(100)>50){ this.add(new ConcreteElement1()); }else { //添加元素二 this.add(new ConcreteElement2()); } } } } public class Main { public static void main(String[] args) { MedicineList medicineList = new MedicineList(); //数据结构 IVisitor casherVisitor = new CasherVisitor(); //收费访问者,对每个药品进行收费 IVisitor pharmacistsVisitor = new PharmacistsVisitor(); //药剂师访问者,拿药 medicineList.accept(casherVisitor); //数据结构.accept(访问者)->每一个元素.accept(访问者)->元素把自己交给访问者,访问者对元素进行操作 medicineList.accept(pharmacistsVisitor); } }
个人总结
数据结构.accept(访问者)->每一个元素.accept(访问者)->元素把自己交给访问者,访问者对具体某个元素进行访问操作 = 遍历每一个元素交给访问者操作
如果这篇文章对你有用,可以关注本人微信公众号获取更多ヽ(^ω^)ノ ~