23种设计模式之访问者模式

23种设计模式之访问者模式

参考资料

下文如有错漏之处,敬请指正

一、简介

定义

在不改变集合元素的前提下,为集合中的每个元素提供多种访问操作,即可以扩展操作元素的功能。

特点

  • 访问者模式是一种行为型模式
  • 访问者模式将数据结构与数据操作分离,解决数据结构和操作耦合性的问题。

通用类图

在这里插入图片描述

访问者模式的主要角色:

  • Visitor

    抽象访问者角色

    抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的。

  • ConcreteVisitor

    具体访问者角色

    实现抽象访问者角色中声明的所有访问操作,确定访问者访问一个元素时该做什么,即对元素执行什么操作

  • Element

    抽象元素角色

    接口或者抽象类,声明一个包含 accept() 的接口,accept()方法的参数由具体访问者角色进行确定。

  • ConcreteElement

    具体元素角色

    实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。

  • ObjectStructure

    对象结构角色是一个包含元素角色的容器,通常由 List、Set、Map 等类实现。

优点

  • 扩展性好,能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

  • 复用性好,可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。

  • 灵活性好,访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。

  • 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

缺点

  • 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  • 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  • 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

应用场景

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就说是用迭代器模式已经不能胜任的情景。
  • 需要对一个对象结构中的对象进行很多不同且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
  • 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作,即针对访问的对象不同,执行不同的操作。

二、访问者模式

需求:

超市现在有三种产品,按斤称的水果,按瓶卖的饮料,按袋数卖的薯片,现在超市要搞促销活动(分为春季和冬季促销活动),三种产品都有各自的折扣程度,使用访问者模式解决超市打折活动。

抽象访问者:

package visitor;

import java.math.BigDecimal;
import java.math.RoundingMode;

public interface Visitor {

	void visit(Fruit fruit);

	void visit(Drinks drinks);

	void visit(Crisps crisps);

	//  统计总价
	void getTotalPay();

	//  将数值进行四舍五入
	default float halfAdjust(float price) {
		BigDecimal bigDecimal = new BigDecimal(price);
		return bigDecimal.setScale(2, RoundingMode.HALF_UP).floatValue();
	}
}

具体访问者:

package visitor;

public class DiscountBySpringVisitor implements Visitor {
	//  统计所有商品打折前的总价
	private float preDiscountTotalPay = 0.0f;
	//  统计所有商品打折后的总价
	private float afterDiscountTotalPay = 0.0f;


	@Override
	public void visit(Fruit fruit) {
		//  水果按每斤30%折扣
		float discount = fruit.getWeight() * fruit.getPrice() * 0.7f;
		//  四舍五入
		discount = halfAdjust(discount);
		System.out.println(fruit.getName() + "春季打折前的价格是:" + fruit.getWeight() * fruit.getPrice() + "----->" + "春季打折后的价格是:" + discount);
		//  统计总价
		preDiscountTotalPay += halfAdjust(fruit.getWeight() * fruit.getPrice());
		afterDiscountTotalPay += discount;
	}

	@Override
	public void visit(Drinks drinks) {
		//  饮料每瓶降价0.5元
		float discount = drinks.getPrice() - 0.5f;
		//  四舍五入
		discount = halfAdjust(discount);
		System.out.println(drinks.getName() + "春季打折前的价格是:" + drinks.getPrice() + "----->" + "春季打折后的价格是:" + discount);
		//  统计总价
		preDiscountTotalPay += halfAdjust(drinks.getPrice());
		afterDiscountTotalPay += discount;
	}

	@Override
	public void visit(Crisps crisps) {
		//  薯片每包打对折
		float discount = crisps.getPrice() * 0.5f;
		//  四舍五入
		discount = halfAdjust(discount);
		System.out.println(crisps.getName() + "春季打折前的价格是:" + crisps.getPrice() + "----->" + "春季打折后的价格是:" + discount);
		//  统计总价
		preDiscountTotalPay += halfAdjust(crisps.getPrice());
		afterDiscountTotalPay += discount;
	}

	@Override
	public void getTotalPay() {
		System.out.println("所有商品打折前的总价是:" + halfAdjust(preDiscountTotalPay) + "----->" + "所有商品打折后的总价是:" + halfAdjust(afterDiscountTotalPay));
	}


}
package visitor;

public class DiscountByWinterVisitor implements Visitor {

   //  统计所有商品打折前的总价
   private float preDiscountTotalPay = 0.0f;
   //  统计所有商品打折后的总价
   private float afterDiscountTotalPay = 0.0f;


   @Override
   public void visit(Fruit fruit) {
      //  水果按每斤10%折扣
      float discount = fruit.getWeight() * fruit.getPrice() * 0.9f;
      //  四舍五入
      discount = halfAdjust(discount);
      System.out.println(fruit.getName() + "冬季打折前的价格是:" + fruit.getWeight() * fruit.getPrice() + "----->" + "冬季打折后的价格是:" + discount);
      //  统计总价
      preDiscountTotalPay += halfAdjust(fruit.getWeight() * fruit.getPrice());
      afterDiscountTotalPay += discount;
   }

   @Override
   public void visit(Drinks drinks) {
      //  饮料每瓶降价1.0元
      float discount = drinks.getPrice() - 1.0f;
      //  四舍五入
      discount = halfAdjust(discount);
      System.out.println(drinks.getName() + "冬季打折前的价格是:" + drinks.getPrice() + "----->" + "冬季打折后的价格是:" + discount);
      //  统计总价
      preDiscountTotalPay += halfAdjust(drinks.getPrice());
      afterDiscountTotalPay += discount;
   }

   @Override
   public void visit(Crisps crisps) {
      //  薯片每包打8折
      float discount = crisps.getPrice() * 0.8f;
      //  四舍五入
      discount = halfAdjust(discount);
      System.out.println(crisps.getName() + "冬季打折前的价格是:" + crisps.getPrice() + "----->" + "冬季打折后的价格是:" + discount);
      //  统计总价
      preDiscountTotalPay += halfAdjust(crisps.getPrice());
      afterDiscountTotalPay += discount;
   }


   @Override
   public void getTotalPay() {
      System.out.println("所有商品打折前的总价是:" + halfAdjust(preDiscountTotalPay) + "----->" + "所有商品打折后的总价是:" + halfAdjust(afterDiscountTotalPay));
   }
}

抽象元素:

package visitor;

public interface Element {
	public void accept(Visitor visitor);
}

具体元素:

package visitor;

public abstract class Product {
	private String name; //商品名字
	private float price; //商品价格

	public Product(String name, float price) {
		this.name = name;
		this.price = price;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public float getPrice() {
		return price;
	}

	public void setPrice(float price) {
		this.price = price;
	}
}

package visitor;

public class Crisps extends Product implements Element {
	public Crisps(String name, float price) {
		super(name, price);
	}

	//  该方法为双派发,执行接收参数的方法,并将当前对象作为该方法的参数(即动态绑定)
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
}

package visitor;

public class Drinks extends Product implements Element {
	public Drinks(String name, float price) {
		super(name, price);
	}


	//  该方法为双派发,执行接收参数的方法,并将当前对象作为该方法的参数(即动态绑定)
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
}

package visitor;

public class Fruit extends Product implements Element {
	private float weight;

	public Fruit(String name, float price, float weight) {
		super(name, price);
		this.weight = weight;
	}

	public float getWeight() {
		return weight;
	}

	public void setWeight(float weight) {
		this.weight = weight;
	}

	//  该方法为双派发,执行接收参数的方法,并将当前对象作为该方法的参数(即动态绑定)
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
}

对象结构:

package visitor;

import java.util.ArrayList;
import java.util.List;

public class ObjectStructure {
	// 定义购物车,购物车里面保存产品,即元素
	private List<Element> carts = new ArrayList<>();

	// 添加产品,即添加元素
	public void addProduct(Element element) {
		carts.add(element);
	}

	// 打印购物结算单
	public void checkBill(Visitor visitor) {
		for (Element product : carts) {
			//  使用打折访问者进行计算
			product.accept(visitor);
		}
		//  显示总价
		visitor.getTotalPay();
	}
}

Client:

package visitor;

public class Client {
    public static void main(String[] args) {

        //  模拟用户买了1瓶可乐,1斤苹果,1斤芒果,1包乐事薯片

        Drinks cola=new Drinks("百事可乐",2.8f);

        Fruit apple=new Fruit("苹果",10.2f,1);
        Fruit mango=new Fruit("芒果",15.2f,2);

        Crisps lays=new Crisps("乐事薯片",8.9f);

        ObjectStructure carts=new ObjectStructure();
        carts.addProduct(cola);
        carts.addProduct(apple);
        carts.addProduct(mango);
        carts.addProduct(lays);

        //  春季购物
        System.out.println("==========春季购物==========");
        carts.checkBill(new DiscountBySpringVisitor());
        //  冬季购物
        System.out.println("==========冬季购物==========");
        carts.checkBill(new DiscountByWinterVisitor());

        /**
         * 输出结果:
         *==========春季购物==========
         * 百事可乐春季打折前的价格是:2.8----->春季打折后的价格是:2.3
         * 苹果春季打折前的价格是:10.2----->春季打折后的价格是:7.14
         * 芒果春季打折前的价格是:30.4----->春季打折后的价格是:21.28
         * 乐事薯片春季打折前的价格是:8.9----->春季打折后的价格是:4.45
         * 所有商品打折前的总价是:52.3----->所有商品打折后的总价是:35.17
         * ==========冬季购物==========
         * 百事可乐冬季打折前的价格是:2.8----->冬季打折后的价格是:1.8
         * 苹果冬季打折前的价格是:10.2----->冬季打折后的价格是:9.18
         * 芒果冬季打折前的价格是:30.4----->冬季打折后的价格是:27.36
         * 乐事薯片冬季打折前的价格是:8.9----->冬季打折后的价格是:7.12
         * 所有商品打折前的总价是:52.3----->所有商品打折后的总价是:45.46
         */

    }
}

  • 此处的具体访问者的功能存在算法冗余,可以结合模板方法模式,封装步骤相同的部分,将特定(不同)步骤的部分交由子类完成,以此达到代码复用。

  • 当有新的打折活动后,只需要扩展一个具体访问者类,然后在对象结构中增加相应的操作方法即可,对原有的代码没有影响。

  • 如果要新增一个元素,如家电产品,会比较麻烦,要在抽象访问者中增加一个visit重载方法,每个具体访问者都要重写该visit()方法,不符合开闭原则。

三、总结

访问者模式是一种集中管理模式,特别适用于大规模重构的项目,通过访问者模式可以很容易把功能集中化管理,如一个统一的报表运算、UI展现等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值