【行为型模式十一】访问者模式

一、对不同的客户设置不同的价钱

现实生活中,不同类型的客户购买同一件商品时,价钱可能会不同。例如企业客户购买时通常会购买的比较多,买的越多则看到显示的单价也越便宜。而个人客户看到显示的单价则贵一些。且不仅购买的时候价格不同,修理、退货等各种场景时的价格可能都不同。像这种将数据操作和数据结构分离的需求,可使用访问者模式实现。

二、访问者模式

访问者模式是23中设计模式中最复杂的设计模式,并且使用频率不高,《设计模式》的作者评价为:大多情况下,你不需要使用访问者模式,但是一旦需要使用它时,那就真的需要使用了。

访问者(Visitor)模式讲的是一个作用于某对象中各个元素的操作,在不改变元素的类前提下,定义作用于元素的新操作。是一种行为型模式。

适用于被访问的元素抽象类的子类数量稳定,不会增加或减少子类。但对这些子类的操作可能会经常需要增加或修改,这时候就需要使用访问者模式了。

2.1 访问者模式的角色

使用访问者模式实现对不同的客户设置不同价钱的类图:
访问者模式

  • 抽象访问者(Visitor):定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。例如Action类,每一个访问者也可以看做是一种操作。
  • 具体访问者(ConcreteVisitor):实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。例如RepairAction和BuyAction类。
  • 抽象元素(Element):声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。例如Customer类,Customer的子类相对是稳定的,一般就分为企业客户和个人客户类。
  • 具体元素(ConcreteElement):实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。例如PersonalCustomer和EnterpriseCustomer类。
  • 对象结构(Object Structure):是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

三、访问者模式实现对不同客户设置不同价格

3.1 抽象元素类

public abstract class Customer {
    protected String name;
    protected double price;
    public Customer(String name,double price) {
        this.name = name;
        this.price = price;
    }
    public abstract void accept(Action action);
}

3.2 具体元素类

  • 个人客户类
public class PersonalCustomer extends Customer {
    public PersonalCustomer(String name, double price) {
        super(name, price);
    }

    @Override
    public void accept(Action action) {
        action.priceForPersonal(this);
    }

    public double getUnitPrice(int count) {
        if (count < 2) {
            return price;
        }else {
            return 0.98 * price;
        }
    }

    public double getTotalPrice(int count) {
        return getUnitPrice(count) * count;
    }

    public double unitRefund() {
        return price * 0.9;
    }

}
  • 企业客户类
public class EnterpriseCustomer extends Customer {
    public EnterpriseCustomer(String name, double price) {
        super(name, price);
    }

    @Override
    public void accept(Action action) {
        action.priceForEnterprise(this);
    }

    public double getUnitPrice(int count) {
        if (count < 100) {
            return price;
        } else if (count < 200) {
            return 0.85 * price;
        } else {
            return 0.8 * price;
        }
    }

    public double getTotalPrice(int count) {
        return getUnitPrice(count) * count;
    }

    public double unitRepairPrice() {
        return price * 0.1;
    }

}

3.4 抽象访问者类

public abstract class Action {
    public abstract void priceForEnterprise(EnterpriseCustomer enterpriseCustomer);
    public abstract void priceForPersonal(PersonalCustomer personalCustomer);
}

3.5 具体访问者类

  • 购买操作类
public class BuyAction extends Action {
    private int count;
    public BuyAction(int count) {
        this.count = count;
    }
    @Override
    public void priceForEnterprise(EnterpriseCustomer enterpriseCustomer) {
        System.out.println(enterpriseCustomer.name + "公司购买了" + count + "个计算器");
        System.out.println("单价为:"+ enterpriseCustomer.getUnitPrice(count) + "元");
        System.out.println("总价为:"+ enterpriseCustomer.getTotalPrice(count) + "元");
    }

    @Override
    public void priceForPersonal(PersonalCustomer personalCustomer) {
        System.out.println(personalCustomer.name + "顾客购买了" + count + "个计算器");
        System.out.println("单价为:"+ personalCustomer.getUnitPrice(count) + "元");
        System.out.println("总价为:"+ personalCustomer.getTotalPrice(count) + "元");
    }
}
  • 修理操作类
public class RepairAction extends Action {
    @Override
    public void priceForEnterprise(EnterpriseCustomer enterpriseCustomer) {
        System.out.println("为" + enterpriseCustomer.name + "公司修理每个计算器的价格为:" + enterpriseCustomer.unitRepairPrice() + "元");
    }

    @Override
    public void priceForPersonal(PersonalCustomer personalCustomer) {
        System.out.println(personalCustomer.name + "顾客送回坏计算器,每个退款为:"+personalCustomer.unitRefund() + "元");
    }
}

3.6 对象结构类

public class ReportUtil {
    private static Map<Customer,List<Action>> actionMap = new HashMap<>();
    public static void addAction(Customer customer,Action action) {
        List<Action> actionList;
        if (actionMap.get(customer) == null) {
            actionList = new ArrayList<>();
            actionMap.put(customer,actionList);
        }
        actionList = actionMap.get(customer);
        actionList.add(action);
    }
    public static void removeAction(Customer customer,Action action) {
        if (actionMap.get(customer) == null) {
            return;
        }
        List<Action> actionList = actionMap.get(customer);
        actionList.remove(action);
        if (actionList.size() == 0) {
            actionMap.remove(customer);
        }
    }
    public static void report(Customer customer) {
        if (actionMap.get(customer) == null) {
            return;
        }
        List<Action> actionList = actionMap.get(customer);
        for (Action action:actionList) {
            customer.accept(action);
        }
    }
}

3.7 客户端类

public class Client {
    /**
     * 使用访问者模式为企业客户和个人客户添加各种不同的行为
     */
    public static void addActionForCustomer() {
        EnterpriseCustomer enterpriseCustomer = new EnterpriseCustomer("阿里",10);
        ReportUtil.addAction(enterpriseCustomer,new BuyAction(1000));
        ReportUtil.addAction(enterpriseCustomer,new RepairAction());
        ReportUtil.report(enterpriseCustomer);

        PersonalCustomer personalCustomer = new PersonalCustomer("pxy",10);
        ReportUtil.addAction(personalCustomer,new BuyAction(2));
        ReportUtil.addAction(personalCustomer,new RepairAction());
        ReportUtil.report(personalCustomer);
    }

    public static void main(String[] args) {
        addActionForCustomer();
    }
}
  • 运行结果
阿里公司购买了1000个计算器
单价为:8.0元
总价为:8000.0元
为阿里公司修理每个计算器的价格为:1.0元
pxy顾客购买了2个计算器
单价为:9.8元
总价为:19.6元
pxy顾客送回坏计算器,每个退款为:9.0元

四、访问者模式总结

4.1 优点

  • 各角色职责分离,符合单一职责原则。
  • 具有优秀的扩展性
    如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。
    使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
    员工属性(数据结构)和CEO、CTO访问者(数据操作)的解耦。

4.2 缺点

  • 具体元素对访问者公布细节,违反了迪米特原则

  • 具体元素变更时导致修改成本大
    变更客户属性时,多个访问者都要修改。

  • 违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有依赖抽象
    访问者 Action中的方法,而依赖了具体客户的具体方法。

五、访问者模式的使用场景

  • 被访问的对象结构比较稳定,但经常需要在此对象结构上定义新的操作。

  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。

我们要根据具体情况来评估是否适合使用访问者模式,例如,我们的对象结构是否足够稳定,是否需要经常定义新的操作,使用访问者模式是否能优化我们的代码,而不是使我们的代码变得更复杂。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值