访问者模式是什么?
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作
适用场合
当一个对象结构包括很多类对象,它们有不同的接口,而系统要求这些对象实施一些依赖于某具体类的操作时,就可以使用访问者模式
结构图
通过上图可以看到他有如下角色:
-
抽象访问者(Visitor)角色
:定义接口,声明一个或多个访问操作。 -
具体访问者(ConcreteVisitor)角色
:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作。 -
抽象元素(Visitable/Element)角色
:声明一个接受操作,接受一个访问者(Visitor)对象作为一个参数。 -
具体元素结点(ConcreteElement)角色
:实现抽象元素(Visitable/Element)所规定的accept操作。 -
数据结构对象(Object Structure)角色
:可以遍历结构中的所有元素,提供一个接口让访问者对象都可以访问每一个元素。
优点:
使用访问者模式,对于原来的类层次增加新的操作只需要实现一个具体访问者角色,而不必改变整个类层次。每个具体的访问者角色都对应于一个相关操作。
缺点:
不适合具体元素角色经常发生变化的情况。每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。
具体案例1
小巩要设计超市收银系统,碰到了困难,有的按重量计价,有的按物件计价,还有其他计价方式。
这时候可以使用访问者模式。
【1】首先设计抽象元素(Visitable/Element)
,提供访问者需要的accept方法
public interface Goods {
double accept(Visitor visitor);
}
【2】设计具体元素类,包括猪肉类、酒类、电视机类
public class Pig implements Goods {
public double accountByUnit() {
System.out.println("猪肉按斤计价,购买的数量为:" + getCount() + "斤,购买的单价为:" + getPrice() + ",总价为:" + getCount() * getPrice());
return getCount() * getPrice();
}
public double accept(Visitor visitor) {
return visitor.visit(this);
}
public float getCount(){ return count; }
public void setCount(float count){ this.count = count; }
public float getPrice(){ return price; }
public void setPrice(float price){ this.price = price; }
private float count;
private float price;
}
public class Wine implements Goods {
public double accountByBottle() {
System.out.println("酒按瓶计价,购买的数量为:" + getCount() + "瓶,购买的单价为:" + getPrice() + ",总价为:" + getCount() * getPrice());
return getCount() * getPrice();
}
public double accept(Visitor visitor) {
return visitor.visit(this);
}
public int getCount(){
return count;
}
public void setCount(int count){
this.count = count;
}
public float getPrice(){ return price; }
public void setPrice(float price){ this.price = price; }
private int count;
private float price;
}
public class Television implements Goods {
public double accountByPiece() {
System.out.println("电视按台计价,购买的数量为:" + getCount() + "台,购买的单价为:" + getPrice() + ",总价为:" + getCount() * getPrice());
return getCount() * getPrice();
}
public double accept(Visitor visitor) {
return visitor.visit(this);
}
public int getCount(){
return count;
}
public void setCount(int count){
this.count = count;
}
public float getPrice(){ return price; }
public void setPrice(float price){ this.price = price; }
private int count;
private float price;
}
【3】需要一个购物车类(Object Structure)收集要买的具体对象
import java.util.List;
import java.util.ArrayList;
public class ShoppingCart {
public void add(Object object) {
list.add(object);
}
public void remove(Object object) {
list.remove(object);
}
public List getList() {
return list;
}
private List list = new ArrayList();
}
【4】Visitor接口
public interface Visitor {
double visit(Wine wine);
double visit(Pig pig);
double visit(Television television);
}
【5】Visitor实现类
public class VisitorImpl implements Visitor {
public double visit(Wine wine) {
return wine.accountByBottle();
}
public double visit(Pig pig) {
return pig.accountByUnit();
}
public double visit(Television television) {
return television.accountByPiece();
}
}
【6】设计收银机类可以调用具体物件供访问的收费方法
public class AccountMachine {
private double amt;
public void account(List list) {
Visitor visitor = new VisitorImpl();
for (int i = 0; i < list.size(); i++) {
amt += ((Goods)list.get(i)).accept(visitor);
}
}
public double getAmt() {
return amt;
}
}
【7】客户端调用的代码(Client)
public class Client {
public static void main(String[] argv) {
Wine wine = new Wine();
wine.setCount(10);
wine.setPrice(20f);
Pig pig = new Pig();
pig.setCount(10f);
pig.setPrice(15f);
Television television = new Television();
television.setCount(1);
television.setPrice(2000f);
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.add(wine);
shoppingCart.add(pig);
shoppingCart.add(television);
AccountMachine accountMachine = new AccountMachine();
accountMachine.account(shoppingCart.getList());
System.out.println("本次购物车内所有物品的总价为:" + accountMachine.getAmt());
}
}
具体案例2
针对同一个数据接口有不同的Visitor
interface CarElement {
void accept(CarElementVisitor visitor);
}
interface CarElementVisitor {
void visit(Body body);
void visit(Car car);
void visit(Engine engine);
void visit(Wheel wheel);
}
class Car implements CarElement {
CarElement[] elements;
public Car() {
this.elements = new CarElement[] {
new Wheel("front left"), new Wheel("front right"),
new Wheel("back left"), new Wheel("back right"),
new Body(), new Engine()
};
}
public void accept(final CarElementVisitor visitor) {
for (CarElement elem : elements) {
elem.accept(visitor);
}
visitor.visit(this);
}
}
class Body implements CarElement {
public void accept(final CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Engine implements CarElement {
public void accept(final CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Wheel implements CarElement {
private String name;
public Wheel(final String name) {
this.name = name;
}
public String getName() {
return name;
}
public void accept(final CarElementVisitor visitor) {
/*
* accept(CarElementVisitor) in Wheel implements
* accept(CarElementVisitor) in CarElement, so the call
* to accept is bound at run time. This can be considered
* the *first* dispatch. However, the decision to call
* visit(Wheel) (as opposed to visit(Engine) etc.) can be
* made during compile time since 'this' is known at compile
* time to be a Wheel. Moreover, each implementation of
* CarElementVisitor implements the visit(Wheel), which is
* another decision that is made at run time. This can be
* considered the *second* dispatch.
*/
visitor.visit(this);
}
}
class CarElementDoVisitor implements CarElementVisitor {
public void visit(final Body body) {
System.out.println("Moving my body");
}
public void visit(final Car car) {
System.out.println("Starting my car");
}
public void visit(final Wheel wheel) {
System.out.println("Kicking my " + wheel.getName() + " wheel");
}
public void visit(final Engine engine) {
System.out.println("Starting my engine");
}
}
class CarElementPrintVisitor implements CarElementVisitor {
public void visit(final Body body) {
System.out.println("Visiting body");
}
public void visit(final Car car) {
System.out.println("Visiting car");
}
public void visit(final Engine engine) {
System.out.println("Visiting engine");
}
public void visit(final Wheel wheel) {
System.out.println("Visiting " + wheel.getName() + " wheel");
}
}
public class VisitorDemo {
public static void main(final String[] args) {
final Car car = new Car();
car.accept(new CarElementPrintVisitor());
car.accept(new CarElementDoVisitor());
}
}
参考文档
https://en.wikipedia.org/wiki/Visitor_pattern
http://blog.csdn.net/zhi_fu/article/details/77725864
http://alaric.iteye.com/blog/1942517