访问者模式

评测系统的需求

 当电影播放完后,通常人们都会对其进行评价。评价有三种状态:成功、失败、待定。而观众分为男人、女人。

 

传统方案

 Person

  ->Woman

  ->Man

 

问题:

 若要增加新功能时,对代码改动较大。

 扩展性不好,比如增加新的人员类型,新的管理方法。

 

访问者模式

 封装一些作用于某种数据类型的各元素的操作,它可以在不改变的前提下定义作用于这些元素的新操作。

 主要将数据结构与数据操作分离,有利于解决数据结构和操作耦合性的问题

 基本工作原理:在被访问类里加一个对外提供接待访问者的接口。

 应用场景:需要对一个对象结构中的对象进行很多不同的操作【操作间没有关联】,同时避免这些操作 修改了这些对象的类。

 

访问者模式结构

在这里插入图片描述

 1.访问者 (Visitor) 接口声明了一系列以对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。

 2.具体访问者(Concrete Visitor) 会为不同的具体元素类实现相同行为的几个不同版本。

 3.元素(Element) 接口声明了一个方法来 “接收” 访问者。 该方法必须有一个参数被声明为访问者接口类型。

 4.具体元素(Concrete Element) 必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意, 即使元素基类实现了该方法, 所有子类都必须对其进行重写并调用访问者对象中的合适方法。

 5.客户端(Client) 通常会作为集合或其他复杂对象 (例如一个组合树) 的代表。 客户端通常不知晓所有的具体元素类, 因为它们会通过抽象接口与集合中的对象进行交互。

 

访问者模式适合应用场景

  1.如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。

 2.可使用访问者模式来清理辅助行为的业务逻辑。

 3.当某个行为仅在类层次结构中的一些类中有意义,而在其他类中没有意义时,可使用该模式。

 

实现方式

 1.在访问者接口中声明一组 “访问” 方法, 分别对应程序中的每个具体元素类。

 2.声明元素接口。 如果程序中已有元素类层次接口, 可在层次结构基类中添加抽象的 “接收” 方法。 该方法必须接受访问者对象作为参数。

 3.在所有具体元素类中实现接收方法。 这些方法必须将调用重定向到当前元素对应的访问者对象中的访问者方法上。

 4.元素类只能通过访问者接口与访问者进行交互。 不过访问者必须知晓所有的具体元素类, 因为这些类在访问者方法中都被作为参数类型引用。

 5.为每个无法在元素层次结构中实现的行为创建一个具体访问者类并实现所有的访问者方法。

 你可能会遇到访问者需要访问元素类的部分私有成员变量的情况。 在这种情况下, 你要么将这些变量或方法设为公有, 这将破坏元素的封装; 要么将访问者类嵌入到元素类中。 后一种方式只有在支持嵌套类的编程语言中才可能实现。

 6.客户端必须创建访问者对象并通过 “接收” 方法将其传递给元素。

 

访问者模式优缺点

 优点:

  ✔️ 开闭原则。 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。

   ✔️ 单一职责原则。 可将同一行为的不同版本移到同一个类中。

  ✔️ 访问者对象可以在与各种对象交互时收集一些有用的信息。 当你想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。

 缺点:

  ❌ 每次在元素层次结构中添加或移除一个类时, 你都要更新所有的访问者。

  ❌ 在访问者同某个元素进行交互时, 它们可能没有访问元素私有成员变量和方法的必要权限。

 

解决评测系统需求

在这里插入图片描述

public abstract class Action  {

    public abstract void getManResult(Man man);

    public abstract void getWomanResult(Woman woman);
}

public class Good extends Action {
    @Override
    public void getManResult(Man man) {
        System.out.println("男人对这部电影评价很好");
    }

    @Override
    public void getWomanResult(Woman woman) {
        System.out.println("女人对这部电影评价很好");
    }
}

public class Bad extends Action {
    @Override
    public void getManResult(Man man) {
        System.out.println("男人对这部电影评价不好");
    }

    @Override
    public void getWomanResult(Woman woman) {
        System.out.println("女人对这部电影评价不好");
    }
}

public class Wait extends Action {
    @Override
    public void getManResult(Man man) {
        System.out.println("男人暂时不评价这部电影");
    }

    @Override
    public void getWomanResult(Woman woman) {
        System.out.println("女人暂时不评价这部电影");
    }
}

public abstract class Person {
    //提供一个方法,让访问者可以访问
    public abstract void accept(Action action);
}

public class Woman extends Person {
    /*
        这里使用了 双分派
            客户端,将具体状态作为参数传递Woman
            Woman类调用作为参数的具体方法中getWomanResult,同时将自己作为参数传入
     */
    @Override
    public void accept(Action action) {
        action.getWomanResult(this);
    }
}

public class Man extends Person {
    @Override
    public void accept(Action action) {
        action.getManResult(this);
    }
}
//数据结构,管理Person
public class ObjectStucture {

    private List<Person> plist = new LinkedList<>();

    public void attach(Person person){
        plist.add(person);
    }

    public void detach(Person person){
        plist.remove(person);
    }

    public void display(Action action){
        for (Person person : plist) {
            person.accept(action);
        }
    }
}
public class Client {
    public static void main(String[] args) {
        ObjectStucture stucture = new ObjectStucture();

        stucture.attach(new Man());
        stucture.attach(new Woman());

        stucture.display(new Good());

        stucture.display(new Bad());

        stucture.display(new Wait());
    }
}

 
应用案例的小结

​  上面提到了双分派,所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。
​  双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型

​  以上述实例为例,假设我们要添加一个Wait的状态类考察 Man 类和 Woman 类的反应,由于使用了双分派, 只需增加一个 Action子类即可在客户端调用即可不需要改动任何其他类的代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值