本文来自李明子csdn博客(http://blog.csdn.net/free1985),商业转载请联系博主获得授权,非商业转载请注明出处!
1 目的
访问者(Visitor)模式将一组对象与作用于这组对象的行为分离,使作用于这组对象的行为可以在不改变对象定义的情况下自由扩充。
2 基本形态
访问者的基本形态如类图2-1所示。
图2-1 访问者类图
访问者的典型时序如图2-2所示。
图2-2 访问者时序图
从上图中可以看出,ObjectStructure控制着其包含的各ConcreteElement的遍历。当Client(图中未画出)调用ObjectStructure对象的Accept方法时,向ObjectStructure对象传入Visitor;ObjectStructure对象遍历其包含的各ConcreteElement(图中的ConcreteElementA和ConcreteElementB),分别调用其Accept方法,传入Visitor;在ConcreteElement的方法内部将自己作为参数,调用传入的Visitor为当前ConcreteElement类型创建的具体访问方法(对于ConcreteElementA是VisitConcreteElementA,对于ConcreteElementB是VisitConcreteElementB)。而在Visitor的具体元素访问方法的实现中,又会调用ConcreteElement的相关方法(对于VisitConcreteElementA是OperationA,对于VisitConcreteElementB是OperationB)。
3 参与者
结合图2-1,下面介绍各类在访问者设计模式中扮演的角色。
3.1 Element
Element是元素抽象类,定义了元素的共有成员及其访问方法。Element还定义了抽象方法Accept,以Visitor接口类型对象为参数接受访问者访问。
3.2 ConcreteElementX
ConcreteElementX是具体元素类,派生于元素抽象类Element。ConcreteElementX除了包含个性化的成员及访问成员的方法外,还实现了Element中定义的抽象方法Accept。在Accept的实现中,ConcreteElementX将自己作为参数调用Visitor接口为当前的ConcreteElementX类型定制的访问接口方法VisitConcreteElementX。
3.3 ObjectStructure
ObjectStructure是对象结构类,内部维护了Element的集合及遍历方法。ObjectStructure类定义了以Visitor接口类型对象为参数接受访问者访问的方法Accept,内部实现时遍历其包含的Element,以Visitor对象为参数调用Element对象的Accept方法。
3.4 Visitor
Visitor是访问者接口,为每个具体元素ConcreteElementX定义一个访问该元素的接口方法。
从Visitor的接口方法与Element的实现类的耦合关系不难看出,Visitor设计模式特别适用于Element子类稳定,而Element的相关行为不稳定的场景。
3.5 ConcreteVisitor
ConcreteVisitor是具体访问者类,对应一种访问目的,实现了访问者接口Visitor。
请注意,这里ConcreteVisitor对应的是一种“访问目的”而非“访问者身份”。有些开发者被“具体访问者”这个参与者名称和部分设计模式教程中的示例误导,误以为ConcreteVisitor应该对应访问者身份,在业务中只有“学生”、“老师”,“普通会员”、“VIP会员”这样的场景才适用于Visitor设计模式。其实,Visitor设计模式的初衷恰恰与访问者的身份无关,而仅与访问目的有关。在后面的代码实践中,读者可以看到具体访问者是如何体现“访问目的”的。
3.6 Client
Client是客户类,是Visitor设计模式的使用者。Client根据业务场景,获取包含Element集合的对象结构ObjectStructure,并根据场景决定要实施在对象结构ObjectStructure中包含的各元素的访问从而实例化具体访问者ConcreteVisitor,调用ObjectStructure的Accept方法完成相应功能。
4 代码实践
下面我们用一个业务场景实例来进一步讲解访问者的使用。
4.1 场景介绍
某购物网站的商品分为自营和联营两种。对于不同类型的商品,价格计算方式等行为不尽相同。网站1024购物节期间对购物车功能进行了扩展,在打印商品清单的同时还加入了活动信息。
以下各节将介绍该场景各类的具体实现及其在访问者设计模式中所对应的参与者角色。
4.2 AbstractProduct
AbstractProduct是抽象商品类,声明了商品的共有属性及属性的访问方法等,并定义了接受访问的接口方法。对应于访问者模式的参与者,AbstractProduct是元素抽象类Element。下面的代码给出了AbstractProduct的声明。
package demo.designpattern.visitor;
/**
* 商品抽象类
* Created by LiMingzi on 2017/12/7.
*/
public abstract class AbstractProduct {
/**
* 商品名
*/
private String name;
/**
* 价格
*/
private double price;
/**
* 促销信息
*/
private String promotionalInfo;
/**
* 获取商品名
* @return 商品名
*/
public String getName() {
return name;
}
/**
* 获取商品价格
* @return 商品价格
*/
public double getPrice() {
return price;
}
/**
* 获取促销信息
* @return 促销信息
*/
public String getPromotionalInfo() {
return promotionalInfo;
}
/**
* 设置促销信息
* @param promotionalInfo 促销信息
*/
public void setPromotionalInfo(String promotionalInfo) {
this.promotionalInfo = promotionalInfo;
}
/**
* 构造方法
* @param name 商品名
* @param price 商品价格
*/
public AbstractProduct(String name, double price) {
this.name = name;
this.price = price;
this.promotionalInfo = "";
}
/**
* 接受访问
* @param productVisitor 产品访问者
*/
abstract void accept(IProductVisitor productVisitor);
/**
* 打印商品信息
*/
public void print(){
if(!promotionalInfo.isEmpty()){
System.out.println(promotionalInfo);
}
System.out.println("商品名称:"+name);
System.out.println("商品价格:"+price+"元");
}
}
上述代码中,11行、16行、21行分别声明了商品共有的属性商品名、价格和促销信息,并提供了这些属性的访问方法;70行,定义了接受访问接口方法accept,该方法以商品访问者接口类型实例为参数;75行,声明了打印商品信息方法print。
4.3 SelfProduct
SelfProduct是自营商品类,派生于商品抽象类AbstractProduct。对应于访问者模式的参与者,SelfProduct是具体元素类ConcreteElement。下面的代码给出了SelfProduct的声明。
package demo.designpattern.visitor;
/**
* 自营商品类
* Created by LiMingzi on 2017/12/7.
*/
public class SelfProduct extends AbstractProduct{
/**
* 折扣率
*/
private double discountRate;
/**
* 获取折扣率
* @return 折扣率
*/
public double getDiscountRate() {
return discountRate;
}
/**
* 构造方法
* @param name 商品名
* @param price 商品单价
* @param discountRate 商品折扣率
*/
public SelfProduct(String name, double price, double discountRate) {
super(name, price);
this.discountRate = discountRate;
}
/**
* 接受访问
*
* @param productVisitor 产品访问者
*/
@Override
void accept(IProductVisitor productVisitor) {
productVisitor.visitSelfProduct(this);
}
/**
* 打印商品信息
*/
@Override
public void print() {
super.print();
System.out.println("折扣率:"+discountRate);
}
}
上述代码中,11行,声明了自营商品的私有属性折扣率;38行,实现了接口方法accept,39行,以自身为参数调用商品访问者接口为访问自营商品声明的接口方法visitSelfProduct;46行,重写了打印商品信息方法,加入了个性化信息。
4.4 AssociateProduct
AssociateProduct是联营商品类,派生于商品抽象类AbstractProduct。对应于访问者模式的参与者,AssociateProduct是具体元素类ConcreteElement。下面的代码给出了AssociateProduct的声明。
package demo.designpattern.visitor;
/**
* 联营商品类
* Created by LiMingzi on 2017/12/7.
*/
public class AssociateProduct extends AbstractProduct{
/**
* 邮费
*/
private double postage;
/**
* 获取商品邮费
* @return 商品邮费
*/
public double getPostage() {
return postage;
}
/**
* 构造方法
* @param name 商品名
* @param price 商品价格
* @param postage 邮费
*/
public AssociateProduct(String name, double price, double postage) {
super(name, price);
this.postage = postage;
}
/**
* 接受访问
*
* @param productVisitor 产品访问者
*/
@Override
void accept(IProductVisitor productVisitor) {
productVisitor.visitAssociateProduct(this);
}
/**
* 打印商品信息
*/
@Override
public void print() {
super.print();
System.out.println("邮费:"+postage+"元");
}
}
上述代码中,11行,声明了联营商品的私有属性邮费;38行,实现了接口方法accept,39行,以自身为参数调用商品访问者接口为访问联营商品声明的接口方法visitAssociateProduct;46行,重写了打印商品信息方法,加入了个性化信息。
4.5 ShoppingCart
ShoppingCart是购物车类。对应于访问者模式的参与者,ShoppingCart是对象结构类ObjectStructure。下面的代码给出了ShoppingCart的声明。
package demo.designpattern.visitor;
import java.util.ArrayList;
import java.util.List;
/**
* 购物车
* Created by LiMingzi on 2017/12/7.
*/
public class ShoppingCart {
/**
* 商品集合
*/
private List<AbstractProduct>products = new ArrayList<AbstractProduct>();
/**
* 添加商品
* @param product 商品
*/
public void addProduct(AbstractProduct product){
products.add(product);
}
/**
* 接受访问
* @param productVisitor 访问者
*/
public void accept(IProductVisitor productVisitor){
for (AbstractProduct product : products) {
product.accept(productVisitor);
}
}
/**
* 打印购物车商品明细
*/
public void print(){
System.out.println("购物车商品明细:");
for (AbstractProduct product : products) {
product.print();
System.out.println("-------------------------------------------------------");
}
}
}
上述代码中,14行,成员变量products声明了购物车中包含的商品集合;28行,接受访问方法accept以访问者接口类型对象为参数;29行,遍历购物车中的商品,调用商品对象的接受访问方法accept(30行)实现对商品的访问。
4.6 IProductVisitor
IProductVisitor是商品访问者接口,定义了访问各类型具体商品的方法。对应于访问者模式的参与者,IProductVisitor是访问者接口。下面的代码给出了IProductVisitor的声明。
package demo.designpattern.visitor;
/**
* 商品访问者接口
* Created by LiMingzi on 2017/12/7.
*/
public interface IProductVisitor {
/**
* 访问联营商品
* @param associateProduct 联营商品对象
*/
void visitAssociateProduct(AssociateProduct associateProduct);
/**
* 访问自营商品
* @param selfProduct 自营商品对象
*/
void visitSelfProduct(SelfProduct selfProduct);
}
上述代码中,12行,访问联营商品方法visitAssociateProduct以联营商品类型对象为参数访问联营商品;18行,访问自营商品方法visitSelfProduct以自营商品类型对象为参数访问自营商品。
4.7 PriceProductVisitor
PriceProductVisitor是价格——商品访问者类,实现了商品访问者接口IProductVisitor。价格——商品访问者用于访问并累计商品价格。对应于访问者模式的参与者,PriceProductVisitor是具体访问者ConcreteVisitorX。下面的代码给出了PriceProductVisitor的声明。
package demo.designpattern.visitor;
/**
* 价格——商品访问者
* Created by LiMingzi on 2017/12/7.
*/
public class PriceProductVisitor implements IProductVisitor{
/**
* 价格
*/
private double price = 0.0d;
/**
* 获取价格
* @return 价格
*/
public double getPrice() {
return price;
}
/**
* 重置价格
*/
public void resetPrice(){
price = 0.0d;
}
/**
* 访问联营商品
*
* @param associateProduct 联营商品对象
*/
@Override
public void visitAssociateProduct(AssociateProduct associateProduct) {
price += associateProduct.getPrice()+associateProduct.getPostage();
}
/**
* 访问自营商品
*
* @param selfProduct 自营商品对象
*/
@Override
public void visitSelfProduct(SelfProduct selfProduct) {
price += selfProduct.getPrice()*(1-selfProduct.getDiscountRate());
}
}
上述代码中,11行,成员变量price用于记录访问者访问的累计结果,即总价;34行,实现访问联营商品方法visitAssociateProduct,计算方法为商品原价与邮费之和;44行,实现访问自营商品方法visitSelfProduct,计算商品折后价格。
4.8 PromoInfoFor1024ProductVisitor
PromoInfoFor1024ProductVisitor是1024购物节促销商品访问者类,实现了商品访问者接口IProductVisitor。1024购物节促销商品访问者用于访问并设置商品促销信息。对应于访问者模式的参与者,PromoInfoFor1024ProductVisitor是具体访问者ConcreteVisitor。下面的代码给出了PromoInfoFor1024ProductVisitor的声明。
package demo.designpattern.visitor;
/**
* 1024购物节促销商品访问者
* Created by LiMingzi on 2017/12/7.
*/
public class PromoInfoFor1024ProductVisitor implements IProductVisitor{
/**
* 访问联营商品
*
* @param associateProduct 联营商品对象
*/
@Override
public void visitAssociateProduct(AssociateProduct associateProduct) {
associateProduct.setPromotionalInfo("1024购物节万余厂商百万商品大展销");
}
/**
* 访问自营商品
*
* @param selfProduct 自营商品对象
*/
@Override
public void visitSelfProduct(SelfProduct selfProduct) {
selfProduct.setPromotionalInfo("1024购物节自营商品满1000返200全品类购物券");
}
}
上述代码中,我们通过调用被访问对象——具体商品类的设置促销信息方法为不同类型的商品设置了相应的促销信息。
4.9 ShoppingCartMgmt
ShoppingCartMgmt是购物车管理类,用于管理购物车。对应于访问者模式的参与者,ShoppingCartMgmt是客户类Client。下面的代码给出了ShoppingCartMgmt的声明。
package demo.designpattern.visitor;
/**
* 购物车管理类
* Created by LiMingzi on 2017/12/9.
*/
public class ShoppingCartMgmt {
/**
* 打印商品明细
* @param userId 用户id
*/
public void print(String userId){
// 购物车
ShoppingCart shoppingCart = getShoppingCart(userId);
// 1024购物节活动访问者
PromoInfoFor1024ProductVisitor promoInfoFor1024ProductVisitor= new PromoInfoFor1024ProductVisitor();
shoppingCart.accept(promoInfoFor1024ProductVisitor);
// 价格访问者
PriceProductVisitor priceProductVisitor = new PriceProductVisitor();
shoppingCart.accept(priceProductVisitor);
shoppingCart.print();
System.out.println("总价:"+priceProductVisitor.getPrice()+"元");
}
/**
* 获取指定用户的购物车(demo)
* @param userId 用户id
* @return 购物车
*/
public ShoppingCart getShoppingCart(String userId){
// 购物车
ShoppingCart shoppingCart = new ShoppingCart();
if("001".equals(userId)){
shoppingCart.addProduct(new SelfProduct("空气炸锅",499.0,0.2));
shoppingCart.addProduct(new SelfProduct("扫地机器人",1899.0,0.1));
shoppingCart.addProduct(new AssociateProduct("训练勺",38,10));
shoppingCart.addProduct(new AssociateProduct("儿童坐便训练器",350,20));
}
return shoppingCart;
}
}
上述代码中,12行,打印商品明细方法print用于打印指定用户的购物车详细信息。16行,声明1024购物节活动访问者对象promoInfoFor1024ProductVisitor;17行,以promoInfoFor1024ProductVisitor为实参调用购物车接受访问方法accept访问购物车中各商品设置促销信息;19行,声明价格访问者对象priceProductVisitor;20行,以priceProductVisitor为实参调用购物车接受访问方法accept访问购物车中各商品并计算总价;21行,输出购物车中商品明细;22行,输出购物车中商品总价。
4.10 测试代码
为了测试本文中的代码,我们可以编写如下测试代码。测试代码中,打印了我们虚构的购物车信息。
/**
* 访问者测试
*/
public static void visitorTest(){
// 购物车管理对象
ShoppingCartMgmt shoppingCartMgmt = new ShoppingCartMgmt();
shoppingCartMgmt.print("001");
}
编译运行后,得到如下测试结果:
购物车商品明细:
1024购物节自营商品满1000返200全品类购物券
商品名称:空气炸锅
商品价格:499.0元
折扣率:0.2
1024购物节自营商品满1000返200全品类购物券
商品名称:扫地机器人
商品价格:1899.0元
折扣率:0.1
1024购物节万余厂商百万商品大展销
商品名称:训练勺
商品价格:38.0元
邮费:10.0元
1024购物节万余厂商百万商品大展销
商品名称:儿童坐便训练器
商品价格:350.0元
邮费:20.0元
总价:2526.3元