概念:
访问者模式(Visitor)行为设计模式。访问者模式被用在针对一组相同类型对象的操作。优点是,可以把针对此对象的操作逻辑转移到另外一个类上。用于数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。
案例:
本文将展示如何利用访问者模式去实现电商购物车系统,平台上的商品的销售活动(操作行为)可能经常改变,但商品本身基本数据却相对稳定,这里就可以应用访问者模式,将行为与数据分离开来,达到解耦的目的。下面的例子并对访问者模式进行改进,让增加数据与改变操作行为一样方便。
先来看类图:
先定义商品项接口:两个方法等实现
public interface GoodsItem {
public double accept(ShoppingCartVisitor visitor);
public ShoppingCartVisitor getSelfVisitor();
}
定义购物接口:
public interface ShoppingCartVisitor {
public double visitor(GoodsItem goodsItem);
}
定义商品类,实现GoodsItem 接口:
奶粉类:
public class Milk implements GoodsItem { private String brand; private double price; private int number; public int getNumber() { return number; } public Milk(String brand, double price, int number) { this.brand = brand; this.price = price; this.number = number; } public String getBrand() { return brand; } public double getPrice() { return price; } @Override public double accept(ShoppingCartVisitor visitor) { return visitor.visitor(this); } @Override public ShoppingCartVisitor getSelfVisitor() { return new MilkVisitor(); } }
樱桃:
public class Cherry implements GoodsItem { private double price; private int weight; public double getPrice() { return price; } public int getWeight() { return weight; } public Cherry(double price, int weight) { this.price = price; this.weight = weight; } @Override public double accept(ShoppingCartVisitor visitor) { return visitor.visitor(this); } @Override public ShoppingCartVisitor getSelfVisitor() { return new CherryVisitor(); } }
增加奶粉Visitor
public class MilkVisitor implements ShoppingCartVisitor { @Override public double visitor(GoodsItem goodsItem) { Milk milk = (Milk)goodsItem; double cost = milk.getPrice()*milk.getNumber(); System.out.println(String.format("%s 单盒价:%s,盒数%s 总价:%s",milk.getBrand(),milk.getPrice(),milk.getNumber(),cost)); //奶粉满300减50 if(cost>=300){ cost-=50; } System.out.println(String.format("%s 今日满300减50,优惠后总价:%s",milk.getBrand(),cost)); return cost; } }
增加樱桃Visitor
public class CherryVisitor implements ShoppingCartVisitor { @Override public double visitor(GoodsItem goodsItem) { Cherry cherry = (Cherry)goodsItem; double cost = cherry.getPrice()*cherry.getWeight(); System.out.println(String.format("Cherry 单价:%s 重量:%s 总价:%s",cherry.getPrice(),cherry.getWeight(),cost)); //进口樱桃 8折 cost*=0.8; System.out.println(String.format("Cherry 今日8折,折后总价%s",cost)); return cost; } }
最后购物平台类:
public class ShopingClient { private List<GoodsItem> list; public ShopingClient(List<GoodsItem> list) { this.list = list; } public List<GoodsItem> getList() { return list; } public void setList(List<GoodsItem> list) { this.list = list; } public double perchase() { double costTotal = 0.0; for (GoodsItem goodsItem : list) { costTotal += goodsItem.accept(goodsItem.getSelfVisitor()); } System.out.println(String.format("购物总价%s", costTotal)); return costTotal; } }
写代码测试:
public class WorkClass { public void test() { List<GoodsItem> list = new ArrayList<GoodsItem>(); list.add(new Milk("某品牌奶粉",160.0,2)); list.add(new Cherry(80.0,5)); ShopingClient shopingClient = new ShopingClient(list); double totalcost=shopingClient.perchase(); } }
测试结果输出:
I/System.out: 某品牌奶粉 单盒价:160.0,盒数2 总价:320.0
I/System.out: 某品牌奶粉 今日满300减50,优惠后总价:270.0
I/System.out: Cherry 单价:80.0 重量:5 总价:400.0
I/System.out: Cherry 今日8折,折后总价320.0
I/System.out: 购物总价590.0
结语:
上面的例子,新增商品数据也非常方便。增加一个商品类和它的购物访问类就可以了。不用改到已有的类和接口。
如果项目需要为一个现有的类增加新功能,会考虑以下几个事情:1、新功能会不会与现有功能出现兼容性问题?2、以后会不会再需要添加?3、如果类不允许修改现代码怎么办?面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦。这样修改功能不会影响到数据本身。