温故而知新
还是先复习前面学到的知识:
行为型模式:描述多个类或对象之间怎么协助共同完成单个对象无法实现的任务,涉及算法与对象间的职责分配
模板方法模式:定义一个操作流程中的算法骨架,特定的步骤方法的具体实现延迟到子类,即方便与重定义一个算法的特定步骤(请客过程具体吃什么有子类决定,父类只定义一个请客流程:点单 - 》 吃饭 - 》买单)
命令模式:将请求命令封装成对象,并将执行者聚合到命令中,发送者维护一个保存命令的容器,调用发送者去使用命令对象即可完成命令,使得命令发送者和命令执行者完全解耦(点击遥控器上的命令按键即可完成具体开关灯–遥控器并不知道如何执行,只管发送命令)
接下来,是一个比较复杂抽象而且使用也较少的设计模式:访问者模式
购物车的问题:
顾客在超市中将选择的商品,如苹果、图书等放在购物车中,然后到收银员处付款。
在购物过程中,顾客需要对这些商品进行访问,以便确认这些商品的质量。
收银员计算价格时也需要访问购物车内顾客所选择的商品。
此时,购物车作为一个ObjectStructure(对象结构)用于存储各种类型的商品,而顾客和收银员作为访问这些商品的访问者,他们需要对商品进行检查和计价。
不同类型的商品其访问形式也可能不同,如苹果需要过秤之后再计价,而图书不需要。
这个场景,可以思考一下什么去描述?
学习过程中,掌握主动性,学会提问,让疑问带领着去学习
用传统方法,会创建对象:苹果、图书、顾客、收银员、购物车,然后互相调用方法???
传统方法可以解决,但是要是又增加了商品:可乐,需要修改很多类
学了这节访问者模式,会有很好的解决方案
先大致了解一下访问者模式的概念,再经过案例分析
访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式
访问者模式的目的是封装一些施加于某种对象结构的元素之上的操作,一旦这些操作需要修改的话,接受这个操作的对象结构可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式
模式角色:
用访问者模式实现上面说的购物车案例
简单实现:
package com.company.Behavioral.visitor;
import java.util.ArrayList;
import java.util.List;
//抽象访问者
abstract class Visitor {
public abstract void visit(Apple apple);
public abstract void visit(Book book);
}
//具体访问者:顾客
class Customer extends Visitor{
@Override
public void visit(Apple apple) {
System.out.println("-----查看苹果质量----");
}
@Override
public void visit(Book book) {
System.out.println("-----查看书的名字-----");
}
}
//具体
class Cashier extends Visitor{
@Override
public void visit(Apple apple) {
System.out.println(" 查看苹果的价钱。。。");
}
@Override
public void visit(Book book) {
System.out.println(" 查看书本的价钱。。。");
}
}
//抽象产品
abstract class Product {
public abstract void accept(Visitor visitor);
}
//用到了双分派:在后面的程序中具体的访问者作为参数传递到Apple(第一次分派)
//Apple类调用了作为参数的“具体访问者”中的方法visit,同时将自己(this)作为参数传入,完成第二次的分派
class Apple extends Product{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Book extends Product{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
//数据结构,管理了很多产品(Customer,Woman)
class ShoppingCart{
//维护一个集合
private List<Product> products = new ArrayList<>();
//增加到集合
public void attach(Product p){
products.add(p);
}
//从集合移除
public void detach(Product p){
products.remove(p);
}
//访问者访问购物车的情况
public void display(Visitor visitor){
for (Product p : products){
p.accept(visitor);
}
}
}
class Client{
public static void main(String[] args) {
//创建ShoppingCart
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.attach(new Apple());
shoppingCart.attach(new Apple());
shoppingCart.attach(new Book());
//顾客查看购物车
Customer customer = new Customer();
shoppingCart.display(customer);
//收银员查看购物车
Cashier cashier = new Cashier();
shoppingCart.display(cashier);
}
}
访问者模式可以使我们在不改变元素(具体的产品)的情况下,定义一下作用于这些产品的新操作(新的访问者)
如果我们创建一个新的访问者:调查者,调查者任务是调查顾客购物的类型
只需要新建一个具体访问者继承访问者接口即可
仔细想想,其实上面的购物车例子有点问题。。。
因为访问的物品类型已经在访问者类中写死了,造成
从这个缺点看出,访问者模式应该是使用在元素类种类稳定的系统中
什么?不知道DOM4J?可以看看真的了解XML吗? - XML解析方式
DOM4J解析XML利用访问者(VisitorSupport)解析XML文件。通过重写各种类型的visit()方法解析各种节点,文本内容
具体使用:
//解析器
SAXReader saxReader = new SAXReader();
//解析具体的xml文件
Document doc = saxReader.read(new File("book.xml"));
//Visitor是自定义的访问者,里面重写了VisitorSupport的visit方法
doc.accept(new Visitor());
class Visitor extends VisitorSupport{
@Override
public void visit(Attribute node){
//自定义访问结点Attribute的操作
}
@Override
public void visit(Element node){
//自定义访问结点Element操作
}
}
程序代码是被访问的对象,它包括变量定义、变量赋值、逻辑运算、算术运算等语句,编译器需要对代码进行分析
可以将不同的操作封装在不同的类中,如检查变量定义的类、检查变量赋值的类、检查算术运算是否合法的类,这些类就是具体访问者,可以访问程序代码中不同类型的语句
可以将每一个不同编译阶段的操作封装到了跟该阶段有关的一个访问者类中
访问者模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式
这样联用已经比较复杂了
我们的访问者的对象结构类中维护了一个元素对象的集合,因此访问者模式经常需要与迭代器模式联用,在对象结构中使用迭代器来遍历元素对象(案例中使用for-each循环遍历也是用到了迭代器)
访问者模式以一种倾斜的方式支持“开闭原则”,增加新的访问者方便,但是增加新的元素很困难
还记得前面也有一个“开闭原则”倾斜:抽象工厂模式
抽象工厂模式中增加产品族很简单,增加产品等级很困难
这是因为,我们必须在一个对象结构(抽象工厂)写死完整的体系,只能做到针对一个方面,所以选择设计模式是需要根据实际情况抉择
访问者模式开闭原则倾斜,使用时需要根据实际情况选择