万字解析设计模式之观察者模式、中介者模式、访问者模式

一、观察者模式

1.1概述

观察者模式是一种行为型设计模式,它允许一个对象(称为主题或可观察者)在其状态发生改变时,通知它的所有依赖对象(称为观察者)并自动更新它们。这种模式提供了一种松耦合的方式,使得主题和观察者可以独立地改变和扩展。

在观察者模式中,主题维护了一个观察者列表,并提供了增加、删除和通知观察者的方法。当主题的状态发生改变时,它会调用通知方法,通知所有注册的观察者。观察者接收到通知后,会自动更新自己的状态以反映主题的新状态。因此,观察者模式可以用于实现事件、回调和发布-订阅等功能。

定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。

1.2结构

在观察者模式中有如下角色:

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

1.3实现

抽象观察者

java 复制代码
package com.yanyu.Subscribe;

public interface Observer {
    void update(String message);
}

具体观察者

java 复制代码
package com.yanyu.Subscribe;

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

public class SubscriptionSubject implements Subject {
    //储存订阅公众号的微信用户
    private List<Observer> weixinUserlist = new ArrayList<Observer>();

    @Override
    public void attach(Observer observer) {
        weixinUserlist.add(observer);

    }

    @Override
    public void detach(Observer observer) {
        weixinUserlist.remove(observer);

    }

    @Override
    public void notify(String message) {
        for (Observer observer : weixinUserlist) {
            observer.update(message);
        }
    }
}

抽象主题类

java 复制代码
package com.yanyu.Subscribe;


public interface Subject {
    //增加订阅者
    public void attach(Observer observer);
    //删除订阅者
    public void detach(Observer observer);

    //通知订阅者更新消息
    public void notify(String message);
}

具体主题

java 复制代码
package com.yanyu.Subscribe;

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

public class SubscriptionSubject implements Subject {
    //储存订阅公众号的微信用户
    private List<Observer> weixinUserlist = new ArrayList<Observer>();

    @Override
    public void attach(Observer observer) {
        weixinUserlist.add(observer);

    }

    @Override
    public void detach(Observer observer) {
        weixinUserlist.remove(observer);

    }

    @Override
    public void notify(String message) {
        for (Observer observer : weixinUserlist) {
            observer.update(message);
        }
    }
}

客户端类

java 复制代码
package com.yanyu.Subscribe;

public class Client {
    public static void main(String[] args) {
        SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
        //创建微信用户
        WeixinUser user1=new WeixinUser("孙悟空");
        WeixinUser user2=new WeixinUser("猪悟能");
        WeixinUser user3=new WeixinUser("沙悟净");
        //订阅公众号
        mSubscriptionSubject.attach(user1);
        mSubscriptionSubject.attach(user2);
        mSubscriptionSubject.attach(user3);
        //公众号更新发出消息给订阅的微信用户
        mSubscriptionSubject.notify("\n专栏更新了");
    }
}

1.4优缺点

1,优点:

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

2,缺点:

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

1.5应用场景

  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。

比如用户界面、网络编程、应用程序监控等领域。它可以提高程序的可维护性、可扩展性和可重用性,同时也符合面向对象设计的开闭原则和单一职责原则。

1.6JDK中提供的实现

在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

1,Observable类

Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

  • void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
  • void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
  • void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。

2,Observer 接口

Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。

【例】警察抓小偷

警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下:

小偷是一个被观察者,所以需要继承Observable类

java 复制代码
package com.yanyu.Subscribe1;


import java.util.Observable;

public class Thief extends Observable {

    private String name;

    public Thief(String name) {
        this.name = name;
    }

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

    public String getName() {
        return name;
    }

    public void steal() {
        System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
        super.setChanged(); //changed  = true
        super.notifyObservers();
    }
}

警察是一个观察者,所以需要让其实现Observer接口

java 复制代码
package com.yanyu.Subscribe1;


import java.util.Observable;
import java.util.Observer;

public class Policemen implements Observer {

    private String name;

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

    public String getName() {
        return name;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
    }
}

客户端代码

java 复制代码
package com.yanyu.Subscribe1;

public class Client {
    public static void main(String[] args) {
        //创建小偷对象
        Thief t = new Thief("隔壁老王");
        //创建警察对象
        Policemen p = new Policemen("小李");
        //让警察盯着小偷
        t.addObserver(p);
        //小偷偷东西
        t.steal();
    }
}

二、中介者模式

2.1概述

用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。

如果引入中介者模式,那么同事类之间的关系将变为星型结构,从下右图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。

2.2结构

中介者模式包含以下主要角色:

  • 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  • 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  • 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

2.3实现

【例】租房

现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。

类图如下:

抽象中介者(Mediator)角色

java 复制代码
package com.yanyu.mediator;


//抽象中介者
public abstract class Mediator {
    //申明一个联络方法
    public abstract void constact(String message,Person person);
}

具体中介者(ConcreteMediator)角色

java 复制代码
package com.yanyu.mediator;

// 中介机构
public class MediatorStructure extends Mediator {
    // 首先中介结构必须知道所有房主和租房者的信息
    private HouseOwner houseOwner; // 房主
    private Tenant tenant; // 租房者

    // 获取房主信息
    public HouseOwner getHouseOwner() {
        return houseOwner;
    }

    // 设置房主信息
    public void setHouseOwner(HouseOwner houseOwner) {
        this.houseOwner = houseOwner;
    }

    // 获取租房者信息
    public Tenant getTenant() {
        return tenant;
    }

    // 设置租房者信息
    public void setTenant(Tenant tenant) {
        this.tenant = tenant;
    }

    // 联系方法,发送信息给对应的人
    public void constact(String message, Person person) {
        if (person == houseOwner) { // 如果是房主,则租房者获得信息
            tenant.getMessage(message);
        } else { // 反之则是房主获得信息
            houseOwner.getMessage(message);
        }
    }
}

抽象同事类(Colleague)角色

java 复制代码
package com.yanyu.mediator;

//抽象同事类
public abstract class Person {
    protected String name;
    protected Mediator mediator;

    public Person(String name,Mediator mediator){
        this.name = name;
        this.mediator = mediator;
    }
}

具体同事类(Concrete Colleague)角色

java 复制代码
package com.yanyu.mediator;

//具体同事类 房屋拥有者
public class HouseOwner extends Person {

    public HouseOwner(String name, Mediator mediator) {
        super(name, mediator);
    }

    //与中介者联系
    public void constact(String message){
        mediator.constact(message, this);
    }

    //获取信息
    public void getMessage(String message){
        System.out.println("房主" + name +"获取到的信息:" + message);
    }
}
java 复制代码
package com.yanyu.mediator;

//具体同事类 承租人
public class Tenant extends Person {
    public Tenant(String name, Mediator mediator) {
        super(name, mediator);
    }

    //与中介者联系
    public void constact(String message){
        mediator.constact(message, this);
    }

    //获取信息
    public void getMessage(String message){
        System.out.println("租房者" + name +"获取到的信息:" + message);
    }
}

客户端角色

java 复制代码
package com.yanyu.mediator;

//测试类
public class Client {
    public static void main(String[] args) {
        //一个房主、一个租房者、一个中介机构
        MediatorStructure mediator = new MediatorStructure();

        //房主和租房者只需要知道中介机构即可
        HouseOwner houseOwner = new HouseOwner("张三", mediator);
        Tenant tenant = new Tenant("李四", mediator);

        //中介结构要知道房主和租房者
        mediator.setHouseOwner(houseOwner);
        mediator.setTenant(tenant);

        tenant.constact("需要租三室的房子");
        houseOwner.constact("我这有三室的房子,你需要租吗?");
    }
}

2.4优缺点

1,优点:

  • 松散耦合

    中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样"牵一处而动全身"了。

  • 集中控制交互

    多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改。

  • 一对多关联转变为一对一的关联

    没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。

2,缺点:

当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

2.5应用场景

适应的场景:

  1. 当一些对象和其他对象紧密耦合以致难以对其进行修改时, 可使用中介者模式。该模式让你将对象间的所有关系抽取成为一个单独的类, 以使对于特定组件的修改工作独立于其他组件。

  2. 当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式。应用中介者模式后, 每个组件不再知晓其他组件的情况。 尽管这些组件无法直接交流, 但它们仍可通过中介者对象进行间接交流。 如果你希望在不同应用中复用一个组件, 则需要为其提供一个新的中介者类。

  3. 如果为了能在不同情景下复用一些基本行为, 导致你需要被迫创建大量组件子类时, 可使用中介者模式。由于所有组件间关系都被包含在中介者中, 因此你无需修改组件就能方便地新建中介者类以定义新的组件合作方式。

三·、访问者模式

3.1概述

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

3.2结构

访问者模式包含以下主要角色:

  • 抽象访问者(Visitor)角色:定义了对元素对象的访问方法,即对不同类型的元素对象进行不同的处理方式,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
  • 具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。
  • 抽象元素(Element)角色:定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
  • 具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。

3.3实现

【例】给宠物喂食

现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。

  • 访问者角色:给宠物喂食的人
  • 具体访问者角色:主人、其他人
  • 抽象元素角色:动物抽象类
  • 具体元素角色:宠物狗、宠物猫
  • 结构对象角色:主人家

抽象访问者(Visitor)角色

java 复制代码
package com.yanyu.visitor;


public interface Person {
    void feed(Cat cat);

    void feed(Dog dog);
}

具体访问者角色

java 复制代码
package com.yanyu.visitor;


public class Owner implements Person {

    @Override
    public void feed(Cat cat) {
        System.out.println("主人喂食猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("主人喂食狗");
    }
}

抽象元素(Element)角色

java 复制代码
package com.yanyu.visitor;

public interface Animal {
    void accept(Person person);
}

具体元素角色

java 复制代码
package com.yanyu.visitor;


public class Dog implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("好好吃,汪汪汪!!!");
    }
}
java 复制代码
package com.yanyu.visitor;

public class Cat implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("好好吃,喵喵喵!!!");
    }
}

对象结构(Object Structure)角色

java 复制代码
package com.yanyu.visitor;


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

public class Home {
    private List<Animal> nodeList = new ArrayList<Animal>();

    public void action(Person person) {
        for (Animal node : nodeList) {
            node.accept(person);
        }
    }

    //添加操作
    public void add(Animal animal) {
        nodeList.add(animal);
    }
}

客户端角色

java 复制代码
package com.yanyu.visitor;

public class Client {
    public static void main(String[] args) {
        Home home = new Home();
        home.add(new Dog());
       // home.add(new Cat());
        Owner owner = new Owner();
        home.action(owner);
        Someone someone = new Someone();
        home.action(someone);
    }
}

3.4 优缺点

1,优点:

  • 扩展性好

    在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

  • 复用性好

    通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

  • 分离无关行为

    通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

2,缺点:

  • 对象结构变化很困难

    在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了"开闭原则"。

  • 违反了依赖倒置原则

    访问者模式依赖了具体类,而没有依赖抽象类。

3.5使用场景

  • 对象结构相对稳定,但其操作算法经常变化的程序。

  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。

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

    • 访问者模式通过在访问者对象中为多个目标类提供相同操作的变体, 让你能在属于不同类的一组对象上执行同一操作。
  • 可使用访问者模式来清理辅助行为的业务逻辑。

    • 该模式会将所有非主要的行为抽取到一组访问者类中, 使得程序的主要类能更专注于主要的工作。
  • 当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。

    • 你可将该行为抽取到单独的访问者类中, 只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。

3.6扩展

访问者模式用到了一种双分派的技术。

1,分派:

变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap() ,map变量的静态类型是 Map ,实际类型是 HashMap 。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。

静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。

动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。

2,动态分派:

通过方法的重写支持动态分派。

java 复制代码
public class Animal {
    public void execute() {
        System.out.println("Animal");
    }
}
​
public class Dog extends Animal {
    @Override
    public void execute() {
        System.out.println("dog");
    }
}
​
public class Cat extends Animal {
     @Override
    public void execute() {
        System.out.println("cat");
    }
}
​
public class Client {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.execute();
        
        Animal a1 = new Cat();
        a1.execute();
    }
}

上面代码的结果大家应该直接可以说出来,这不就是多态吗!运行执行的是子类中的方法。

Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。

3,静态分派:

通过方法重载支持静态分派。

java 复制代码
public class Animal {
}
​
public class Dog extends Animal {
}
​
public class Cat extends Animal {
}
​
public class Execute {
    public void execute(Animal a) {
        System.out.println("Animal");
    }
​
    public void execute(Dog d) {
        System.out.println("dog");
    }
​
    public void execute(Cat c) {
        System.out.println("cat");
    }
}
​
public class Client {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal a1 = new Dog();
        Animal a2 = new Cat();
​
        Execute exe = new Execute();
        exe.execute(a);
        exe.execute(a1);
        exe.execute(a2);
    }
}

四、观察者模式实验

任务描述

气象站将为天气预报系统提供温度、湿度和压力的数据,系统中有 4 个主题(CurrentConditionsDisplayStatisticsDisplayForecastDisplayHeatIndexDisplay)对数据进行了订阅,它们将为用户提供 4 大服务,分别是显示当前温度和湿度、显示气温的最大最小及平均值、气温预测和热力值计算。

本关任务:用观察者模式(如图)实现统一数据接口,并能实时为订阅者提供数据更新服务。

实现方式

  1. 仔细检查你的业务逻辑, 试着将其拆分为两个部分: 独立于其他代码的核心功能将作为发布者; 其他代码则将转化为一组订阅类;

  2. 声明订阅者接口。 该接口至少应声明一个 update 方法。

  3. 声明发布者接口并定义一些接口来在列表中添加和删除订阅对象。 记住发布者必须仅通过订阅者接口与它们进行交互;

  4. 确定存放实际订阅列表的位置并实现订阅方法。 通常所有类型的发布者代码看上去都一样, 因此将列表放置在直接扩展自发布者接口的抽象类中是显而易见的。 具体发布者会扩展该类从而继承所有的订阅行为。但是, 如果你需要在现有的类层次结构中应用该模式, 则可以考虑使用组合的方式: 将订阅逻辑放入一个独立的对象, 然后让所有实际订阅者使用该对象;

  5. 创建具体发布者类。 每次发布者发生了重要事件时都必须通知所有的订阅者;

  6. 在具体订阅者类中实现通知更新的方法。 绝大部分订阅者需要一些与事件相关的上下文数据。 这些数据可作为通知方法的参数来传递。但还有另一种选择。 订阅者接收到通知后直接从通知中获取所有数据。 在这种情况下, 发布者必须通过更新方法将自身传递出去。 另一种不太灵活的方式是通过构造函数将发布者与订阅者永久性地连接起来;

  7. 客户端必须生成所需的全部订阅者, 并在相应的发布者处完成注册工作。

编程要求

根据提示,在右侧编辑器 Begin-End 内 补充"WeatherData.java"和"Client.java"文件中的代码,其它文件的代码不用修改。

测试说明

输入第一行给出一个正整数 n(n⩽10)表示数据更新的批次。随后n行,每行给出 3 个实数(温度、湿度和气压),这 3 个数以空格分隔。

测试输入: 380.0 65.0 30.482.0 70.0 29.278.0 90.0 29.2; 预期输出: 现状: 80.0F度和65.0% 湿度 平均/最高/最低温度 = 80.0/80.0/80.0 预测: 气温正在上升 热指数 82.95535 现状: 82.0F度和70.0% 湿度 平均/最高/最低温度 = 81.0/82.0/80.0 预测: 气温正变得凉爽 热指数 86.90124 现状: 78.0F度和90.0% 湿度 平均/最高/最低温度 = 80.0/82.0/78.0 预测: 气温没有变化 热指数 83.64967

抽象观察者

java 复制代码
package step1;

public interface IDisplayElement {
    void display();
}

具体观察者

java 复制代码
package step1;

public class CurrentConditionsDisplay implements IObserver,IDisplayElement{
    private float temperature;
    private float humidity;
    @Override
    public void display() {
        System.out.println ("现状: " + temperature + "F度和" + humidity + "% 湿度");
    }

    @Override
    public void Update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
}
java 复制代码
package step1;

public class StatisticsDisplay implements IObserver,IDisplayElement{
    private float maxTemp = 0.0f;
    private float minTemp = 200;
    private float tempSum = 0.0f;
    private int numReadings;
    @Override
    public void display() {
        System.out.println ("平均/最高/最低温度 = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
    }

    @Override
    public void Update(float temperature, float humidity, float pressure) {
        tempSum += temperature;
        numReadings++;
        if (temperature > maxTemp)
        {
            maxTemp = temperature;
        }
        if (temperature < minTemp)
        {
            minTemp = temperature;
        }
        display();
    }
}
java 复制代码
package step1;

public class ForecastDisplay implements IObserver,IDisplayElement{
    private float currentPressure = 29.92f;
    private float lastPressure;
    @Override
    public void display() {
        String info="";
        if (currentPressure > lastPressure) {
            info="气温正在上升";
        }
        else if (currentPressure == lastPressure) {
            info="气温没有变化";
        }
        else{
            info="气温正变得凉爽";
        }
        System.out.println("预测: "+info);
    }

    @Override
    public void Update(float temperature, float humidity, float pressure) {
        lastPressure = currentPressure;
        currentPressure = pressure;
        display();
    }
}
java 复制代码
package step1;

public class HeatIndexDisplay implements IObserver,IDisplayElement{
    float heatIndex = 0.0f;
    @Override
    public void display() {
        System.out.println("热指数 " + heatIndex);
    }

    @Override
    public void Update(float temperature, float humidity, float pressure) {
        heatIndex = computeHeatIndex(temperature, humidity);
        display();
    }
    private float computeHeatIndex(float t, float rh){
        float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh)
                + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh))
                + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +
                (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 *
                (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) +
                (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +
                0.000000000843296 * (t * t * rh * rh * rh)) -
                (0.0000000000481975 * (t * t * t * rh * rh * rh)));
        return index;
    }
}

抽象主题

java 复制代码
package step1;

public interface IObserver {
    void Update(float temperature, float humidity, float pressure);
}
java 复制代码
package step1;

public interface ISubject {
    void RegisterObserver(IObserver o);//注册观察者
    void RemoveObserver(IObserver o);//注销观察者
    void notifyObservers();///数据发生变化时,通知观察者
}

具体主题

java 复制代码
package step1;
import java.util.ArrayList;

public class WeatherData implements ISubject {
    private float temperature;//温度
    private float humidity;//湿度
    private float pressure;//压力
    private ArrayList<IObserver> observers= new ArrayList<>();

    @Override
    public void RegisterObserver(IObserver o) {
        /********** Begin ****注册观察者*****/
        observers.add(o);
       
        /********** End *********/
    }

    @Override
    public void RemoveObserver(IObserver o) {
        /********** Begin ****注销观察者*****/
       observers.remove(o);

        /********** End *********/
    }

    @Override
    public void notifyObservers() {
        /********** Begin ****通知观察者*****/
        for (IObserver observer : observers) {
            observer.Update(temperature, humidity, pressure); // Notify each observer
        }

        /********** End *********/
    }

    public void setMeasurements(float temperature, float humidity, float pressure)
    {
        /********** Begin ****数据接口*****/
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        
         
        /********** End *********/
    }
    private void measurementsChanged()
    {
        notifyObservers();
    }

}

客户端类

java 复制代码
package step1;

import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        /********** Begin ****请搭建天气Subject及观察者的结构*****/
        WeatherData weatherData = new WeatherData(); // 创建天气数据主题

        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay();      // 创建当前天气显示观察者
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay();   // 创建统计显示观察者
        ForecastDisplay forecastDisplay = new ForecastDisplay(); 
        // 创 建气温预测显示观察者
        HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(); // 创建热力值显示观察者

        weatherData.RegisterObserver(currentConditionsDisplay);
        weatherData.RegisterObserver(statisticsDisplay);
        weatherData.RegisterObserver(forecastDisplay);
        weatherData.RegisterObserver(heatIndexDisplay);
        

        /********** End *********/
        以下将模拟天气数据发生n次变化/
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        float temp,humidity,pressure;
        for (int i = 0; i <n ; i++) {
            temp= scanner.nextFloat();
            humidity= scanner.nextFloat();
            pressure= scanner.nextFloat();
            /********** Begin *********/
             通知所有的观察者
             weatherData.setMeasurements(temp, humidity, pressure); // 更新天气数据
             weatherData.notifyObservers();
            /********** End *********/
        }
    }
}

五、中介者模式实验

任务描述

假设我们正在开发一个农业病虫害系统,它有三个子系统

  1. 农田管理系统:负责管理所有农田的信息,包括农田的位置、大小、作物种植情况等。
  2. 病虫害检测系统:负责实时监测各个农田的病虫害情况,并将检测结果发送给中介者。
  3. 专家建议系统:负责根据病虫害检测结果,向农民提供有关预防和治疗的建议。

当病虫害检测系统检测到病虫害时,它将检测结果发送给中介者。中介者根据检测结果调用专家建议系统,向相应的农民提供预防和治疗建议。

本关任务:请重构农田管理子系统,它除了管理土地外还负责扮演中介者角色,负责协调各个子系统之间的通信。

实现方式

  1. 找到一组当前紧密耦合, 且提供其独立性能带来更大好处的类 (例如更易于维护或更方便复用);

  2. 声明中介者接口并描述中介者和各种组件之间所需的交流接口。 在绝大多数情况下, 一个接收组件通知的方法就足够了;

  3. 如果你希望在不同情景下复用组件类, 那么该接口将非常重要。 只要组件使用通用接口与其中介者合作, 你就能将该组件与不同实现中的中介者进行连接。

  4. 实现具体中介者类。 该类可从自行保存其下所有组件的引用中受益;

  5. 你可以更进一步, 让中介者负责组件对象的创建和销毁。 此后, 中介者可能会与工厂或外观类似;

  6. 组件必须保存对于中介者对象的引用。 该连接通常在组件的构造函数中建立, 该函数会将中介者对象作为参数传递;

  7. 修改组件代码, 使其可调用中介者的通知方法, 而非其他组件的方法。 然后将调用其他组件的代码抽取到中介者类中, 并在中介者接收到该组件通知时执行这些代码。

编程要求

根据提示,在右侧编辑器 Begin-End 内 修改**"FarmlandManagementSystem.java"的代码,让 FarmlandManagementSystem 具备中介者身份,** 补充**"Client.java"中的代码。其它文件不需要修改(有提示作用)。

测试说明

农田管理系统固定农田数量,请输入害虫警报数量,并输入农田名("农田1"和"农田2")和害虫名(不限定,任意名)。

测试输入: 2农田1 红蜘蛛农田2 绿潜蝇

预期输出: 农民农田1收到了关于红蜘蛛的建议:请及时采取相应的防治措施 农民农田2收到了关于绿潜蝇的建议:请及时采取相应的防治措施

测试输入: 3农田1 红线虫农田2 青蚂蚱农田2 大果蝇

预期输出: 农民农田1收到了关于红线虫的建议:请及时采取相应的防治措施 农民农田2收到了关于青蚂蚱的建议:请及时采取相应的防治措施 农民农田2收到了关于大果蝇的建议:请及时采取相应的防治措施

抽象中介者(Mediator)角色

java 复制代码
package step1;

///抽象中介者
public interface Mediator {
    // 通知方法,用于传递事件信息给中介者
    /*
      该方法接收四个参数:发送者 (sender)、事件名 (event)、受影响的土地 (land) 和影响因素 (pest)。
    */
    void notify(Object sender, String event, String land, String pest);
}

具体中介者(ConcreteMediator)角色

java 复制代码
package step1;
import java.util.Hashtable;

/**
 * FarmlandManagementSystem充当中介者,串联起各子系统。
 */
public class FarmlandManagementSystem implements Mediator {
    private Hashtable farmlands = new Hashtable();
    private PestDetectionSystem pestDetectionSystem;
    private ExpertAdviceSystem expertAdviceSystem;

    /**
     * 添加农田到系统中。
     * @param farmland 要添加的农田。
     */
    public void addFarmland(Farmland farmland) {
        if (!farmlands.contains(farmland)) {
            farmlands.put(farmland.getName(), farmland);
        }
    }

    /**
     * 从系统中移除农田。
     * @param farmland 要移除的农田。
     */
    public void removeFarmland(Farmland farmland) {
        farmlands.remove(farmland);
    }

    /**
     * 设置用于中介者的虫害检测系统。
     * @param pestDetectionSystem 要设置的虫害检测系统。
     */
    public void setPestDetectionSystem(PestDetectionSystem pestDetectionSystem) {
        this.pestDetectionSystem = pestDetectionSystem;
    }

    /**
     * 设置用于中介者的农民建议系统。
     * @param expertAdviceSystem 要设置的农民建议系统。
     */
    public void setExpertAdviceSystem(ExpertAdviceSystem expertAdviceSystem) {
        this.expertAdviceSystem = expertAdviceSystem;
    }

    /**
     * 通知中介者有关事件,并采取适当的行动。
     * @param sender 发送通知的对象。
     * @param event 被通知的事件。
     * @param land 与事件相关的土地。
     * @param pest 与事件相关的虫害。
     */
    @Override
    public void notify(Object sender, String event, String land, String pest) {
        if (event.equals("pestDetected")) {
            Farmland farmland = (Farmland) farmlands.get(land);
            expertAdviceSystem.provideAdvice(farmland, pest);
        }
    }
}

抽象同事类(Colleague)角色

java 复制代码
package step1;

import java.util.List;

public class Farmland {
    private String name;
    private double size;
    private List<String> crops;///土地上种植的作物

    public Farmland(String location, double size, List<String> crops) {
        this.name = location;
        this.size = size;
        this.crops = crops;
    }

    public String getName() {
        return name;
    }

    public double getSize() {
        return size;
    }

    public List<String> getCrops() {
        return crops;
    }
}

具体同事类(Concrete Colleague)角色

java 复制代码
package step1;

public class ExpertAdviceSystem {
    private Mediator mediator;

    public ExpertAdviceSystem(Mediator mediator) {
        this.mediator = mediator;
    }

    public void provideAdvice(Farmland farmland, String pest) {
        // 模拟向农民提供预防和治疗建议的过程
        String advice = "请及时采取相应的防治措施";
        System.out.println("农民" + farmland.getName() + "收到了关于" + pest + "的建议:" + advice);
    }
}
java 复制代码
package step1;

public class PestDetectionSystem {
    private Mediator mediator;

    public PestDetectionSystem(Mediator mediator) {
        this.mediator = mediator;
    }
    public void detectPest(String farmland, String pest) {
        // 模拟程序中病虫害检测功能忽略,直接发出警报
        mediator.notify(this, "pestDetected", farmland, pest);
    }
}

客户端类

java 复制代码
package step1;
import java.util.Hashtable;

/**
 * FarmlandManagementSystem acts as a mediator to connect various subsystems.
 * FarmlandManagementSystem充当中介者,串联起各子系统。
 */
public class FarmlandManagementSystem implements Mediator {
    private Hashtable farmlands = new Hashtable(); // Hashtable用于存储农田信息
    private PestDetectionSystem pestDetectionSystem; // 用于虫害检测的子系统
    private ExpertAdviceSystem expertAdviceSystem; // 用于农民建议的子系统

    /**
     * Add a farmland to the system.
     * 将农田添加到系统中。
     * @param farmland The farmland to be added. 要添加的农田
     */
    public void addFarmland(Farmland farmland) {
        if (!farmlands.contains(farmland)) {
            farmlands.put(farmland.getName(), farmland); // 将农田信息存入Hashtable
        }
    }

    /**
     * Remove a farmland from the system.
     * 从系统中移除农田。
     * @param farmland The farmland to be removed. 要移除的农田
     */
    public void removeFarmland(Farmland farmland) {
        farmlands.remove(farmland); // 从Hashtable中移除农田信息
    }

    /**
     * Set the pest detection system for the mediator.
     * 为中介者设置虫害检测系统。
     * @param pestDetectionSystem The pest detection system to be set. 要设置的虫害检测系统
     */
    public void setPestDetectionSystem(PestDetectionSystem pestDetectionSystem) {
        this.pestDetectionSystem = pestDetectionSystem; // 设置虫害检测子系统
    }

    /**
     * Set the farmer advice system for the mediator.
     * 为中介者设置农民建议系统。
     * @param expertAdviceSystem The expert advice system to be set. 要设置的农民建议系统
     */
    public void setFarmerAdviceSystem(ExpertAdviceSystem expertAdviceSystem) {
        this.expertAdviceSystem = expertAdviceSystem; // 设置农民建议子系统
    }

    /**
     * Notify the mediator about an event and take appropriate action.
     * 通知中介者有关事件,并采取适当的行动。
     * @param sender The object that sends the notification. 发送通知的对象
     * @param event The event being notified. 被通知的事件
     * @param land The land related to the event. 与事件相关的土地
     * @param pest The pest related to the event. 与事件相关的虫害
     */
    @Override
    public void notify(Object sender, String event, String land, String pest) {
        if (event.equals("pestDetected")) { // 如果事件是虫害检测
            Farmland farmland = (Farmland) farmlands.get(land); // 获取与事件相关的农田信息
            expertAdviceSystem.provideAdvice(farmland, pest); // 调用农民建议子系统提供建议
        }
    }
}

六、访问者模式实验

任务描述

在智慧农业系统中,假设有番茄、黄瓜和花菜三块实验田,系统需要对每块作物的生长状态进行监控,未来还需要对病虫害进行监控。

本关任务:系统要求在设计软件架构时,将作物对象与监控数据分离,当需要增加新的监控分析时,无需修改作物的类结构。系统已模拟实现生长状态的监控,请参照后,添加病虫害监控功能(PestsVisitors)。

实现方式

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

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

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

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

  5. 为每个无法在元素层次结构中实现的行为创建一个具体访问者类并实现所有的访问者方法。你可能会遇到访问者需要访问元素类的部分私有成员变量的情况。 在这种情况下, 你要么将这些变量或方法设为公有, 这将破坏元素的封装; 要么将访问者类嵌入到元素类中。 后一种方式只有在支持嵌套类的编程语言中才可能实现;

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

编程要求

根据提示,在右侧编辑器中补充PestsVisitors.javaClient.java文件中 Begin-End 内的代码,完成实验。

测试说明

平台会对你编写的代码进行测试:

预期输出: 黄瓜实验田1生长监控完成 花菜实验田2生长监控完成 番茄实验田3生长监控完成 黄瓜实验田1病虫害监控完成 花菜实验田2病虫害监控完成 番茄实验田3病虫害监控完成

抽象元素(Element)角色

java 复制代码
package step1;

public interface Crop {
    void accept(CropVisitor visitor);
}

具体元素(ConcreteElement)角色

java 复制代码
package step1;

public class CucumberCrop implements Crop{
    private String name;
    // 省略其它属性和方法
    public String getName() {
        return name;
    }
    public CucumberCrop(String cropname)
    {
        name = cropname;
    }
    @Override
    public void accept(CropVisitor visitor) {
        visitor.visit(this);
    }
}
java 复制代码
package step1;

public class TomatoCrop implements Crop{
    private String name;
    // 省略其它属性和方法
    public String getName() {
        return name;
    }
    public TomatoCrop(String cropname)
    {
        name = cropname;
    }
    public void accept(CropVisitor visitor) {
        visitor.visit(this);
    }


}

抽象访问者(Visitor)角色

java 复制代码
package step1;

public interface CropVisitor {
    void visit(TomatoCrop tomatoCrop);
    void visit(CucumberCrop cucumberCrop);
    void visit(CauliflowerCrop cauliflowerCrop);
}

具体访问者角色:

java 复制代码
package step1;

public class GrowthVisitors implements CropVisitor{
    @Override
    public void visit(TomatoCrop tomatoCrop) {
        System.out.println(tomatoCrop.getName() + "生长监控完成");
    }

    @Override
    public void visit(CucumberCrop cucumberCrop) {
        System.out.println(cucumberCrop.getName() + "生长监控完成");
    }

    @Override
    public void visit(CauliflowerCrop cauliflowerCrop) {
        System.out.println(cauliflowerCrop.getName() + "生长监控完成");
    }
}
java 复制代码
package step1;
/********** Begin *********/


public class PestsVisitors implements CropVisitor{
    @Override
    public void visit(TomatoCrop tomatoCrop) {
        System.out.println(tomatoCrop.getName() + "病虫害监控完成");
    }

    @Override
    public void visit(CucumberCrop cucumberCrop) {
        System.out.println(cucumberCrop.getName() + "病虫害监控完成");
    }

    @Override
    public void visit(CauliflowerCrop cauliflowerCrop) {
        System.out.println(cauliflowerCrop.getName() + "病虫害监控完成");
    }
}



/********** End *********/

对象结构(Object Structure)角色

java 复制代码
package step1;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * CropGroup represents a group of crops and provides methods to add, remove, and accept visitors.
 * CropGroup表示一组农作物,提供添加、移除和接受访问者的方法。
 */
public class CropGroup {
    private ArrayList list = new ArrayList(); // 用ArrayList存储农作物

    /**
     * Accept a visitor to visit all crops in the group.
     * 接受访问者访问组中的所有农作物。
     * @param visitor The visitor to be accepted. 要接受的访问者
     */
    public void accept(CropVisitor visitor) {
        Iterator i = list.iterator(); // 使用迭代器遍历所有农作物

        while (i.hasNext()) {
            ((Crop) i.next()).accept(visitor); // 调用每个农作物的accept方法,让访问者访问该农作物
        }
    }

    /**
     * Add a crop to the group.
     * 将一个农作物添加到组中。
     * @param crop The crop to be added. 要添加的农作物
     */
    public void addCrop(Crop crop) {
        list.add(crop); // 将农作物添加到ArrayList中
    }

    /**
     * Remove a crop from the group.
     * 从组中移除一个农作物。
     * @param crop The crop to be removed. 要移除的农作物
     */
    public void removeProduct(Crop crop) {
        list.remove(crop); // 从ArrayList中移除农作物
    }
}

客户端类

java 复制代码
package step1;

public class Client {
    public static void main(String[] args) {
        CucumberCrop cucumberCrop=new CucumberCrop("黄瓜实验田1");
        CauliflowerCrop cauliflowerCrop = new CauliflowerCrop("花菜实验田2");
        TomatoCrop tomatoCrop = new TomatoCrop("番茄实验田3");
        CropGroup cropgroup = new CropGroup();
        cropgroup.addCrop(cucumberCrop);
        cropgroup.addCrop(cauliflowerCrop);
        cropgroup.addCrop(tomatoCrop);
        CropVisitor visitor =  new GrowthVisitors();
        cropgroup.accept(visitor);
        /********** Begin *********/
        CropVisitor visitor2 =  new PestsVisitors();
        cropgroup.accept(visitor2);

        /********** End *********/
    }
}
相关推荐
bandaoyu2 小时前
【设计模式】装饰器模式(Decorator Pattern)
设计模式·装饰器模式
lijiachang0307183 小时前
设计模式(一):单例模式
c++·笔记·学习·程序人生·单例模式·设计模式·大学生
抓哇FullStack-Junior4 小时前
设计模式——适配器模式
java·设计模式·适配器模式
silver6876 小时前
桥接模式详解
设计模式
思忖小下6 小时前
梳理你的思路(从OOP到架构设计)_设计模式Observer模式
观察者模式·设计模式·eit
java1234_小锋6 小时前
观察者模式和发布-订阅模式有什么异同?它们在哪些情况下会被使用?
观察者模式
ke_wu6 小时前
模板方法、观察者模式、策略模式
观察者模式·简单工厂模式·策略模式·模板方法模式
ThetaarSofVenice9 小时前
能省一点是一点 - 享元模式(Flyweight Pattern)
java·设计模式·享元模式
记得多喝水o9 小时前
图解设计模式
设计模式
InSighT__9 小时前
设计模式与游戏完美开发(2)
java·游戏·设计模式