访问者模式
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。
简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。简单关系图:
角色分析
VIsitor:抽象访问者,为该对象结构中ConcreteElement每个声明一个visit操作
ConcreteVisitor:具体的访问者,实现每个有visitor声明的操作,是每个操作实现的部分
ObjectStructure:能枚举他的元素,可以提供一个高层接口,用来允许访问元素
Element:定义一个accept方法,接收一个visitor对象
ConcreteElement:具体的元素,实现accept方法,将自己委托给visitor
实例代码一:
//访问者
public interface Visitor {
public void visit(Subject subject);
}
******************************************************************
public class MyVisitor implements Visitor {
@Override
public void visit(Subject subject) {
System.out.println("visitor the Subject:"+subject.getSubject());
}
}
******************************************************************
/**
* 被访问者
*/
public interface Subject {
public void accept(Visitor visitor);
public String getSubject();
}
******************************************************************
public class MySubject implements Subject{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
@Override
public String getSubject() {
return "shanshan";
}
}
******************************************************************
/**
*访问者模式
* 该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:
* 1、新功能会不会与现有功能出现兼容性问题?
* 2、以后会不会再需要添加?
* 3、如果类不允许修改代码怎么办?
* 面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦,
*/
public class MainClass {
public static void main(String[] args) {
MySubject subject = new MySubject();
MyVisitor visitor = new MyVisitor();
//访问者visitor开始访问被访问者subject
subject.accept(visitor);
}
}
******************************************************************
测试结果:
visitor the Subject:shanshan
实例代码二:
public interface Visitor {
public void visit(Park park);
public void visit(ParkA parkA);
public void visit(ParkB parkB);
}
********************************************************
public class VisitorA implements Visitor{
@Override
public void visit(Park park) {
}
@Override
public void visit(ParkA parkA) {
System.out.println("负责公园A区域卫生");
}
@Override
public void visit(ParkB parkB) {
}
}
********************************************************
public class VisitorB implements Visitor {
@Override
public void visit(Park park) {
}
@Override
public void visit(ParkA parkA) {
}
@Override
public void visit(ParkB parkB) {
System.out.println("负责公园B区域卫生");
}
}
********************************************************
public class VisitorManager implements Visitor {
@Override
public void visit(Park park) {
System.out.println("管理员:负责公园卫生检查");
}
@Override
public void visit(ParkA parkA) {
System.out.println("管理员:负责公园A区域卫生检查");
}
@Override
public void visit(ParkB parkB) {
System.out.println("管理员:负责公园B区域卫生检查");
}
}
********************************************************
/**
* 公园每一部分的抽象
*/
public interface ParkElement {
//用来接纳访问者
public void accept(Visitor visitor);
}
********************************************************
/**
* 公园A
*/
public class ParkA implements ParkElement{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
********************************************************
public class ParkB implements ParkElement {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
********************************************************
public class Park implements ParkElement{
private ParkA parkA;
private ParkB parkB;
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
visitor.visit(parkA);
visitor.visit(parkB);
}
}
********************************************************
public class MainClass {
public static void main(String[] args) {
Park p = new Park();
Visitor visitorA = new VisitorA();
p.accept(visitorA);
Visitor visitorB = new VisitorB();
p.accept(visitorB);
Visitor visitorM = new VisitorManager();
p.accept(visitorM);
}
}
********************************************************
测试结果:
负责公园A区域卫生
负责公园B区域卫生
管理员:负责公园卫生检查
管理员:负责公园A区域卫生检查
管理员:负责公园B区域卫生检查
三、事例
visitor 抽象访问者
/**
* @auhtor
* @create 2023-02-15-11:26
*/
public interface Visitor {
// 糖果重载方法
void visit(Candy candy);
// 酒类重载方法
void visit(Wine wine);
// 水果重载方法
void visit(Fruit fruit);
}
实际访问者
public class DiscountVisitor implements Visitor {
private LocalDate billDate;
public DiscountVisitor(LocalDate billDate) {
this.billDate = billDate;
System.out.println("结算日期:" + billDate);
}
@Override
public void visit(Candy candy) {
System.out.println("=====糖果【" + candy.getName() + "】打折后价格=====");
float rate = 0;
long days = billDate.toEpochDay() - candy.getProductDate().toEpochDay();
if (days > 180) {
System.out.println("超过半年过期糖果,请勿食用!");
} else {
rate = 0.9f;
}
float discountPrice = candy.getPrice() * rate;
System.out.println(NumberFormat.getCurrencyInstance().format(discountPrice));
}
@Override
public void visit(Wine wine) {
System.out.println("=====酒品【" + wine.getName() + "】无折扣价格=====");
System.out.println(NumberFormat.getCurrencyInstance().format(wine.getPrice()));
}
@Override
public void visit(Fruit fruit) {
System.out.println("=====水果【" + fruit.getName() + "】打折后价格=====");
float rate = 0;
long days = billDate.toEpochDay() - fruit.getProductDate().toEpochDay();
if (days > 7) {
System.out.println("¥0.00元(超过一周过期水果,请勿食用!)");
} else if (days > 3) {
rate = 0.5f;
} else {
rate = 1;
}
float discountPrice = fruit.getPrice() * fruit.getWeight() * rate;
System.out.println(NumberFormat.getCurrencyInstance().format(discountPrice));
}
}
Element 元素,基本产品,具体的产品如糖果,水果,酒水等
@Data
public class Product {
private String name;
private LocalDate productDate;
private float price;
public Product(String name, LocalDate productDate, float price) {
this.name = name;
this.productDate = productDate;
this.price = price;
}
}
具体元素,产品
/**
* @auhtor
* @create 2023-02-15-11:13
* 糖果
*/
public class Candy extends Product implements Acceptable{
public Candy(String name, LocalDate productDate, float price) {
super(name, productDate, price);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
/**
* @auhtor
* @create 2023-02-15-11:14
* 水果
*/
public class Fruit extends Product implements Acceptable{
private float weight;
public Fruit(String name, LocalDate productDate, float price, float weight) {
super(name, productDate, price);
this.weight = weight;
}
public float getWeight() {
return weight;
}
public Fruit setWeight(float weight) {
this.weight = weight;
return this;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
/**
* @auhtor
* @create 2023-02-15-11:14
* 酒水
*/
public class Wine extends Product implements Acceptable{
public Wine(String name, LocalDate productDate, float price) {
super(name, productDate, price);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
定义一个接口活着在Element元素中定义accept方法接收visitor参数,
此处代码是访问者精髓
两次委派:
此处让商品主动接待来访者 ,我不认识你,你告诉我你是谁。 此处、visitor不知道调用那个product,所以委托product主动接待,然后把自己(this)分派给visitor访问,请看打印事例
public interface Acceptable {
void accept(Visitor visitor);
}
客户端调用:打印结果调用
public static void main(String[] args) {
//testDemo1();
List<Acceptable> products = Arrays.asList(
new Candy("小黑兔奶糖", LocalDate.of(2018, 10, 1), 20.00f),
new Wine("猫泰白酒", LocalDate.of(2017, 1, 1), 1000.00f),
new Fruit("草莓", LocalDate.of(2018, 12, 26), 10.00f, 2.5f)
);
Visitor discountVisitor2 = new DiscountVisitor(LocalDate.of(2018, 1, 1));
// 迭代购物车轮流结算,此处让商品主动接待来访者 ,我不认识你,你告诉我你是谁。
for (Acceptable product : products) {
// 此处discountVisitor2不知道调用那个product,所以委托product主动接待,然后把自己(this)分派给discountVisitor2访问
product.accept(discountVisitor2);
}
}
结算日期:2018-01-01
=====糖果【小黑兔奶糖】打折后价格=====
¥18.00
=====酒品【猫泰白酒】无折扣价格=====
¥1,000.00
=====水果【草莓】打折后价格=====
¥25.00
与之对比,如果没有定义accept方法的话,调用discountVisitor2.visit(product)就会报错,此处不知道具体调用的visit参数是哪个或者不知道调用哪个方法,编译不通过
static void testDemo2() {
// 三件商品加入购物车
List<Product> products = Arrays.asList(
new Candy("小黑兔奶糖", LocalDate.of(2018, 10, 1), 20.00f),
new Wine("猫泰白酒", LocalDate.of(2017, 1, 1), 1000.00f),
new Fruit("草莓", LocalDate.of(2018, 12, 26), 10.00f, 2.5f)
);
Visitor discountVisitor2 = new DiscountVisitor(LocalDate.of(2018, 1, 1));
// 迭代购物车轮流结算
for (Product product : products) {
//discountVisitor2.visit(product);// 此处报错,因为不知道据具体访问的是哪个
// Cannot resolve method 'visit(com.yubin.cn.demo.controller.visitor.demo2.Product)'
}
}
四、事例
visitor 访问者
public abstract class Action {
public abstract void getManResult(Man person);
public abstract void getWonmanResult(Woman person);
}
实际的访问者:Success , Fail ,Wait 成功失败等待
public class Success extends Action{
@Override
public void getManResult(Man person) {
System.out.println("男人给出歌手的评价是成功");
}
@Override
public void getWonmanResult(Woman person) {
System.out.println("女任给出歌手的评价是成功");
}
}
public class Fial extends Action{
@Override
public void getManResult(Man person) {
System.out.println("男人给出歌手的评价是失败");
}
@Override
public void getWonmanResult(Woman person) {
System.out.println("女人给出歌手的评价是失败");
}
}
public class Wait extends Action{
@Override
public void getManResult(Man person) {
System.out.println("男人给出歌手的评价是等待");
}
@Override
public void getWonmanResult(Woman person) {
System.out.println("女人给出歌手的评价是等待");
}
}
Element对象,accept用来接收访问者
public abstract class Person {
// 提供一个方法,让访问者可以访问
// 这使用双分派,首先在客户端程序中,将具体状态作为参数传递Woman(Man 或者Woman)中(第一次分派)
// 然后Woman类调用参数的具体方法中的方法getManResult(this)、getWomanResult(this),同时将自己this作为参数传入,完成第二次分派
public abstract void accept(Action action);
}
实际的元素 Man Woman 男人女人
public class Man extends Person{
@Override
public void accept(Action action) {
action.getManResult(this);
}
}
public class Woman extends Person{
@Override
public void accept(Action action) {
action.getWonmanResult(this);
}
}
ObjectStructure 数据结构对象,用来聚合Element,对Element进行增删改查遍历等操作
/**
* @auhtor
* @create 2023-02-15-10:39
* 数据结构,管理很多人(男人,女人)
*/
public class ObjectStruture {
private List<Person> persons = new ArrayList<>();
// 添加
public void attach(Person person) {
persons.add(person);
}
// 移除
public void detach(Person person) {
persons.remove(person);
}
// 显示评测信息
public void display(Action action) {
for (Person person : persons) {
person.accept(action);
}
}
}
实际打印
public class MainClient {
public static void main(String[] args) {
// 访问者模式
ObjectStruture objectStruture = new ObjectStruture();
objectStruture.attach(new Man());
objectStruture.attach(new Woman());
// 成功
Success success = new Success();
objectStruture.display(success);
// 失败
Fial fial = new Fial();
objectStruture.display(fial);
}
}
男人给出歌手的评价是成功
女任给出歌手的评价是成功
男人给出歌手的评价是失败
女人给出歌手的评价是失败