鸣谢:《大话设计模式》

简介

是一种行为型设计模式,用于表示一个作用于对象结构中的各个元素的操作。它将操作与对象的结构分离,允许你在不改变对象结构的情况下添加新的操作。
核心思想是定义一个访问者(Visitor)接口,其中包含多个访问方法,每个方法对应一个具体元素的操作。具体元素(Concrete Element)实现一个元素接口,该接口包含一个接受访问者的方法。具体访问者(Concrete Visitor)实现了访问者接口,可以访问和操作各个具体元素。

访问者模式的优点就是增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。

访问者模式的目的是要把处理从数据结构分离出来。

GoF四人中的一个作者就说过:’大多时候你并不需要访问者模式,但当一旦你需要访问者模式时,那就是真的需要它了。

主要组成部分:

  • 访问者接口(Visitor):定义了多个访问方法,每个方法对应一个具体元素的操作。这些方法通常采用多态的方式来实现多种不同的操作。

  • 具体访问者(Concrete Visitor):实现了访问者接口中定义的访问方法,执行实际的操作。

  • 元素接口(Element):定义了一个接受访问者的方法,通常是accept方法,以及具体元素所共有的接口。

  • 具体元素(Concrete Element):实现元素接口,提供了具体的实现和数据。

  • 对象结构(Object Structure):包含了具体元素的集合,通常提供方法来允许访问者访问元素。

  • 客户端:创建具体访问者和具体元素,并将具体元素添加到对象结构中。客户端发起对元素的访问请求,由访问者来执行具体操作。

工作流程

  • 首先,客户端代码创建一个具体元素序列和一个具体访问者对象。

  • 然后,客户端通过调用元素的 accept 方法,将访问者传递给每个元素。

  • 当元素收到访问者后,它会调用访问者的相应操作,这些操作会根据元素的实际类型进行动态绑定。

  • 因此,不同的具体访问者可以为同样的具体元素执行不同的操作。


例子

下面的例子来自于《大话设计模式》, 例子不好找,看了慕课的这节视频,举的例子是付费课和免费课,韩曙亮博客用的付费游戏和免费游戏,不太恰当,最后还是采用了书中的例子

结构

代码

访问者接口(Visitor)

/**
 * @author: lpy
 * @Date: 2023/11/07
 * @desc: Visitor
 */
public abstract class Action {
    abstract void getManConclusion(Man m);

    abstract void getWoManConclusion(Women m);
}

具体访问者(Concrete Visitor)

/**
 * @author: lpy
 * @Date: 2023/11/07
 */
public class Success extends Action{
    @Override
    void getManConclusion(Man m) {
        System.out.println(m.getClass().getSimpleName()+" "+this.getClass().getSimpleName()+"时,背后多半有一个伟大的女人");
    }

    @Override
    void getWoManConclusion(Women m) {
        System.out.println(m.getClass().getSimpleName()+" "+this.getClass().getSimpleName()+"时,背后多半有一个失败的男人");
    }
}
/**
 * @author: lpy
 * @Date: 2023/11/07
 */
public class Failure extends Action{
    @Override
    void getManConclusion(Man m) {
        System.out.println(m.getClass().getSimpleName()+" "+this.getClass().getSimpleName()+"时,背后多半有一个不成功的女人");
    }

    @Override
    void getWoManConclusion(Women m) {
        System.out.println(m.getClass().getSimpleName()+" "+this.getClass().getSimpleName()+"时,背后多半有一个成功的男人");
    }
}

元素接口(Element)

/**
 * @author: lpy
 * @Date: 2023/11/07
 * @desc: Element
 */
public abstract class Person {
    abstract void accept(Action c);
}

具体元素(Concrete Element)

需要提一下当中用到一种双分派的技术,首先在客户程序中将具体
状态作为参数传递给’男人’类完成了一次分派,然后’男人’类调用作为参数
的’具体状态’中的方法’男人反应’,同时将自己(this)作为参数传递进
去。这便完成了第二次分派。双分派意味着得到执行的操作决定于请求的种
类和两个接收者的类型。’接受’方法就是一个双分派的操作,它得到执行的
操作不仅决定于’状态’类的具体状态,还决定于它访问的’人’的类别。

/**
 * @author: lpy
 * @Date: 2023/11/07
 */
public class Man extends Person{
    /**
     * 需要提一下当中用到一种双分派的技术,首先在客户程序中将具体
     * 状态作为参数传递给'男人'类完成了一次分派,然后'男人'类调用作为参数
     * 的'具体状态'中的方法'男人反应',同时将自己(this)作为参数传递进
     * 去。这便完成了第二次分派。双分派意味着得到执行的操作决定于请求的种
     * 类和两个接收者的类型。'接受'方法就是一个双分派的操作,它得到执行的
     * 操作不仅决定于'状态'类的具体状态,还决定于它访问的'人'的类别。
     * @param c
     */
    @Override
    void accept(Action c) {
        c.getManConclusion(this);
    }
}
/**
 * @author: lpy
 * @Date: 2023/11/07
 */
public class Women extends Person{
    @Override
    void accept(Action c) {
        c.getWoManConclusion(this);
    }
}

对象结构(Object Structure)

/**
 * @author: lpy
 * @Date: 2023/11/07
 */
public class ObjectStructure {
    private final static List<Person> ps = new ArrayList<>();

    public void add(Person p){
        ps.add(p);
    }
    public void delete(Person p){
        ps.remove(p);
    }
    public void visitor(Action c){
        for (Person person : ps) {
            person.accept(c);
        }
    }
}

客户端

/**
 * @author: lpy
 * @Date: 2023/11/07
 */
public class VisitorModeTest {
    public static void main(String[] args) {
        ObjectStructure obs = new ObjectStructure();
        obs.add(new Man());
        obs.add(new Women());

        obs.visitor(new Success());
        obs.visitor(new Failure());
    }
}

如果人类的性别不止是男和女,而是可有多种性别,那就意味’状态’类中的抽象方法就不可能稳定了,每加一种类别,就需要在状态类和它的所有下属类中都增加一个方法,这就不符合开放-封闭原则。


应用

访问者模式优缺点

  • 优点:
    • 可扩展性:通过添加新的访问者,可以轻松地为现有对象结构添加新的操作,而无需修改元素的类。

    • 分离关注点:将操作与元素的结构分离,使得每个类专注于自己的职责。

    • 开闭原则:符合开闭原则,可以添加新的具体元素和具体访问者,而不会修改现有代码。

优点就是增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。
最好是用于这种数据结构是确定的场景,比如 男女两种性别(除了演艺圈某些不男不女的特例,没有任何针对的意思,只是看不起),没有第三种数据结构

  • 缺点:
    • 增加新的具体元素可能需要修改所有的具体访问者,这可能导致维护复杂性的增加。

    • 增加新的具体元素和具体访问者需要保持一致,以确保每个元素都有对应的操作。

7 大设计原则

不能全部遵守 , 也不能不遵守 , 注意平衡 功能 和 系统复杂度 , 找到最合适的一个点 ;

- 单一职责原则(Single Responsibility Principle - SRP):
这个原则规定一个类应该只有一个改变的理由,也就是说,一个类应该只有一个单一的职责。这有助于保持类的简单性和可维护性,因为每个类只需关注一个特定的功能。

- 开放-封闭原则(Open-Closed Principle - OCP):
开放-封闭原则要求系统中的软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改原有代码。

- 里氏替换原则(Liskov Substitution Principle - LSP):
里氏替换原则规定,子类应该能够替换其父类,而不会影响程序的正确性。也就是说,子类应该继承父类的行为,但可以扩展或修改该行为,但不应该改变父类的行为。

- 接口隔离原则(Interface Segregation Principle - ISP):
接口隔离原则要求一个类不应该强迫其客户端依赖于它们不需要的接口。应该根据客户端的需求定义小而精确的接口,而不是大而笨重的接口。

- 依赖倒置原则(Dependency Inversion Principle - DIP):
依赖倒置原则要求高级模块不应该依赖于低级模块,它们都应该依赖于抽象。具体来说,这意味着应该通过抽象接口或抽象类来定义模块之间的依赖关系,而不是直接依赖于具体实现。

- 迪米特法则(Law of Demeter - LoD):
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

- 合成复用原则(Composite Reuse Principle - CRP):
合成复用原则鼓励通过组合(合成)已有的类来实现新功能,而不是通过继承现有的类。这样可以降低系统的耦合度,并且更加灵活。

源码位置

全部源码github.com/xs-alpha/designPattern

参考文献

如有内容侵权,麻烦联系博主删除


1 条评论

xiaosheng · 2023-11-07 09:43

24种设计模式,总体串了一遍。有一个大概的印象吧

发表评论

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注

ICP备案号: 辽ICP备20003309号