咱们或许需求在某个目标的状况产生改变时,主动告诉其他相关的目标更新自己的状况,但是咱们又不希望直接在目标中硬编码这种告诉机制,由于这样会导致代码的可保护性和可扩展性变差。

为了处理这种问题,咱们能够运用调查者形式。调查者形式界说了一种松耦合的目标通讯机制,使得多个目标能够在一起工作,而又不必互相了解。在调查者形式中,一个目标(称为主题)保护一组依靠于它的目标(称为调查者),并在状况产生改变时主动告诉一切的调查者。这种机制能够使得代码愈加灵敏、可扩展和易于保护,然后进步软件的质量和效率。

因而,调查者形式被广泛运用于许多范畴,包括桌面运用程序Web 运用程序移动运用程序等等。经过运用调查者形式,咱们能够在不改动目标原有行为的情况下,动态地增加新的行为,然后使得代码愈加灵敏和易于保护。

什么是调查者形式

官方界说:

调查者形式:界说了一种一对多的依靠联系,当一个目标的状况产生改变时,一切依靠它的目标都会得到告诉并主动更新。

The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

调查者形式的角色

  • Subject(主题):被调查的目标,保护一个调查者列表,并供给注册、删去和告诉调查者的办法。

  • Observer(调查者):调查主题的目标,包括一个更新自己状况的办法。

  • ConcreteSubject(详细主题):完成主题接口,保护自己的状况并在状况产生改变时告诉调查者。

  • ConcreteObserver(详细调查者):完成调查者接口,包括更新自己状况的详细完成。

调查者形式的完成

事例

想象一下,在一个城市里,有一个气候台多个手机App桌面App和网站,它们都需求显现当时的气候情况。在这种情况下,咱们能够运用调查者形式来完成这个功用。 气候台(Subject)保护了当时的气候状况,并供给了一个办法来更新气候数据。手机App、桌面App和网站(Observers)订阅了气候台的气候数据,当气候数据产生改变时,气候台会主动告诉一切的调查者,并更新它们自己的状况。

Subject(主题)

// 主题接口
interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

Subject(主题)类的办法比较固定,通常会包括以下三个办法:

  • registerObserver(Observer o):用于注册一个Observer目标,使得Subject能够向该Observer发送告诉。
  • removeObserver(Observer o):用于移除一个现已注册的Observer目标。
  • notifyObservers():用于向一切已注册的Observer发送告诉。

Observer(调查者)

// 调查者接口
interface Observer {
    void update(double temperature, double humidity, double pressure);
}
  • Observer(调查者)一般包括一个办法,即update()办法,该办法用于接收Subject发来的告诉,并更新自己的状况。

ConcreteSubject(详细主题)

// 气候台类
class WeatherStation implements Subject {
    private double temperature;
    private double humidity;
    private double pressure;
    private List<Observer> observers = new ArrayList<>();
    public void setWeather(double temperature, double humidity, double pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers();
    }
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }
    @Override
    public void notifyObservers() {
        for (Observer o : observers) {
            o.update(temperature, humidity, pressure);
        }
    }
}

ConcreteSubject(详细主题)通常会保护一个Observer列表,用于存储一切现已注册的Observer目标,以便在状况产生改变时能够告诉它们。在详细完成中,

ConcreteSubject需求完成注册Observer、移除Observer和告诉Observer等办法,并在状况产生改变时调用告诉办法告诉一切的Observer更新自己的状况。

WeatherStation便是一个详细的主题,它保护了一个List集合,用于存储一切现已注册的Observer目标,一起供给了注册Observer、移除Observer和告诉Observer等办法。这种完成方式使得咱们能够动态地增加或删去Observer目标,然后完成更好的灵敏性和扩展性。

ConcreteObserver(详细调查者)

// 手机App、桌面App和网站类
class WeatherApp implements Observer {
    private String name;
    public WeatherApp(String name) {
        this.name = name;
    }
    @Override
    public void update(double temperature, double humidity, double pressure) {
        System.out.printf("%s: 当时气候温度为 %.2f ℃,湿度为 %.2f%%,气压为 %.2f kPa\n", name, temperature, humidity, pressure);
    }
}

WeatherApp类完成了Observer接口,偏重写了其间的update()办法。当WeatherStation的气候数据产生改变时,它会主动告诉一切现已注册的Observer目标,并调用它们的update()办法。在WeatherAppupdate()办法中,它会获取WeatherStation的当时气候数据,并更新自己的界面显现。

WeatherApp类扮演的角色是详细的Observer,它完成了Observer接口,并在其间界说了详细的更新逻辑,即依据WeatherStation的气候数据更新自己的界面显现。一起,WeatherApp类还能够动态地注册、移除和更新自己的状况,以便更好地习惯不同的运用场景和需求。

客户端

// 测验代码
public class ObserverPatternExample {
    public static void main(String[] args) {
        WeatherStation weatherStation = new WeatherStation();
        WeatherApp app1 = new WeatherApp("手机App1");
        WeatherApp app2 = new WeatherApp("桌面App1");
        WeatherApp app3 = new WeatherApp("网站1");
        weatherStation.registerObserver(app1);
        weatherStation.registerObserver(app2);
        weatherStation.registerObserver(app3);
        weatherStation.setWeather(20.5, 60.3, 101.3);
    }
}

调查者形式类图

维基百科

从气象站到软件设计:深入解析观察者模式

在spring源码中的运用

在Spring结构中,ApplicationContext中的事情、Bean生命周期事情和自界说事情都是调查者形式的详细运用。ApplicationContext作为主题(Subject)保护了一个监听器列表(Observer),用于存储一切现已注册的监听器。当事情产生时,ApplicationContext会主动告诉一切的监听器,并调用它们的事情处理办法(onApplicationEvent办法),以便履行详细的逻辑。这种形式能够协助咱们在Spring运用程序中完成目标之间的动态通讯,以便更好地满意事务需求和用户需求。

  1. ApplicationContext中的事情,例如ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent等;

  2. Bean生命周期事情,例如BeforeInitializationEvent、AfterInitializationEvent等;

  3. 自界说事情,例如订单创立事情、付出成功事情、邮件发送成功事情等。

这三个场景都是事情驱动模型,事情驱动模型是一种完成调查者形式的方式,它将调查者形式中的主题(Subject)和调查者(Observer)抽象为事情(Event)和监听器(Listener),并运用事情和监听器之间的联系来完成调查者形式。

代码举例

我来详细说一下自界说事情

举一个订单创立事情的比如。假定咱们正在开发一个电商渠道,咱们需求在订单创立成功时,发送一封承认邮件给用户。为了完成这个功用,咱们能够界说一个OrderCreatedEvent事情,并在订单创立时发布这个事情。详细代码如下:

首要,界说一个OrderCreatedEvent类,承继自ApplicationEvent类,用于表明订单创立事情:

public class OrderCreatedEvent extends ApplicationEvent {
    private Order order;
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
    public Order getOrder() {
        return order;
    }
}

在订单创立成功时,咱们能够经过ApplicationContext目标来发布一个OrderCreatedEvent事情,示例代码如下:

@Component
public class OrderService {
    @Autowired
    private ApplicationContext applicationContext;
    public void createOrder(Order order) {
        // 创立订单逻辑
        ...
        // 发布订单创立事情
        applicationContext.publishEvent(new OrderCreatedEvent(this, order));
    }
}

上述代码中,咱们首要经过@Autowired注解注入了ApplicationContext目标,然后在订单创立成功后,经过applicationContext.publishEvent()办法来发布OrderCreatedEvent事情,并将订单信息封装到事情目标中。

接下来,咱们需求界说一个监听器来监听OrderCreatedEvent事情,并在事情产生时发送承认邮件给用户。示例代码如下:

@Component
public class OrderCreatedEventListener implements ApplicationListener<OrderCreatedEvent> {
    @Override
    public void onApplicationEvent(OrderCreatedEvent event) {
        Order order = event.getOrder();
        // 发送承认邮件给用户
        ...
    }
}

在Spring结构中,自界说事情是一个十分重要的特性,它能够协助咱们完成各种杂乱事务场景,例如订单创立事情、付出成功事情、邮件发送成功事情等。

举一个订单创立事情的比如。假定咱们正在开发一个电商渠道,咱们需求在订单创立成功时,发送一封承认邮件给用户。为了完成这个功用,咱们能够界说一个OrderCreatedEvent事情,并在订单创立时发布这个事情。详细代码如下:

首要,界说一个OrderCreatedEvent类,承继自ApplicationEvent类,用于表明订单创立事情:

javaCopy code
public class OrderCreatedEvent extends ApplicationEvent {
    private Order order;
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
    public Order getOrder() {
        return order;
    }
}

在订单创立成功时,咱们能够经过ApplicationContext目标来发布一个OrderCreatedEvent事情,示例代码如下:

javaCopy code
@Component
public class OrderService {
    @Autowired
    private ApplicationContext applicationContext;
    public void createOrder(Order order) {
        // 创立订单逻辑
        ...
        // 发布订单创立事情
        applicationContext.publishEvent(new OrderCreatedEvent(this, order));
    }
}

上述代码中,咱们首要经过@Autowired注解注入了ApplicationContext目标,然后在订单创立成功后,经过applicationContext.publishEvent()办法来发布OrderCreatedEvent事情,并将订单信息封装到事情目标中。

接下来,咱们需求界说一个监听器来监听OrderCreatedEvent事情,并在事情产生时发送承认邮件给用户。示例代码如下:

javaCopy code
@Component
public class OrderCreatedEventListener implements ApplicationListener<OrderCreatedEvent> {
    @Override
    public void onApplicationEvent(OrderCreatedEvent event) {
        Order order = event.getOrder();
        // 发送承认邮件给用户
        ...
    }
}

在上述代码中,咱们界说了一个OrderCreatedEventListener监听器,完成了ApplicationListener接口,偏重写了其间的onApplicationEvent()办法,用于接收OrderCreatedEvent事情的告诉,并履行相应的逻辑,例如发送承认邮件给用户等。

经过这种方式,咱们能够十分方便地完成订单创立事情的处理逻辑,并将处理逻辑与事务逻辑进行别离,然后使得代码愈加模块化和易于保护。一起,咱们还能够经过界说不同的自界说事情和监听器,来完成各种杂乱的事务场景,例如付出成功事情、邮件发送成功事情等。

什么情况下运用调查者形式

  1. 当一个目标的改动需求一起改动其他多个目标的时分,运用调查者形式能够防止目标之间的严密耦合。例如,在一个气候运用中,如果要在多个当地显现当时气候状况,咱们能够运用调查者形式,将气候运用作为主题(Subject),将手机App、桌面App和网站作为调查者(Observer),以便在气候数据产生改变时,主动更新一切的调查者。

  2. 当一个目标的改动会触发连锁反应,引起其他多个目标的状况改变时,运用调查者形式能够协助咱们更好地办理这种连锁反应。例如,在一个电商运用中,如果用户下单成功后,需求触发一系列的事情,例如减少库存、生成订单、发送短信等,咱们能够运用调查者形式,将订单作为主题(Subject),将库存办理器、订单办理器、短信发送器等作为调查者(Observer),以便在订单创立成功后,主动触发这些事情。

  3. 当一个目标的改动需求动态地增加或删去其他多个目标时,运用调查者形式能够协助咱们更好地办理这些动态改变。例如,在一个交际运用中,如果用户在发布一条动态时,能够选择将这条动态发布到某个圈子或许某些老友,咱们能够运用调查者形式,将用户作为主题(Subject),将圈子和老友作为调查者(Observer),以便在用户选择发布圈子或老友时,动态地增加或删去对应的调查者。

总结:

调查者形式的长处:

  1. 解耦性好:调查者形式能够将主题(Subject)和调查者(Observer)解耦,然后使得它们之间的依靠联系变得松懈。这样一来,主题和调查者就能够相互独立地改变,而不会对互相形成影响。

  2. 可扩展性好:调查者形式能够很容易地增加新的调查者,然后完成愈加灵敏的扩展。这样一来,当新的调查者加入时,主题和已有的调查者不需求做任何修正,只需求增加新的调查者即可。

  3. 便于保护:调查者形式能够将逻辑别离,然后使得代码愈加明晰、简练和易于保护。这样一来,当需求改变时,咱们只需求修正调查者的完成,而不需求修正主题的完成,然后防止了代码的耦合性。

  4. 易于完成:调查者形式的完成十分简单,只需求界说好主题和调查者的接口,并在主题中保护一个调查者列表,即可完成调查者形式的功用。

调查者形式的缺陷:

  1. 过多的调查者会导致性能问题:当主题有过多的调查者时,会导致告诉调查者的时刻延伸,然后影响性能。因而,在运用调查者形式时,需求留意调查者的数量,防止过多的调查者。

  2. 调查者和主题之间的联系或许难以理解:当调查者和主题之间的联系过于杂乱时,或许会导致代码难以理解和保护。因而,在运用调查者形式时,需求留意代码的明晰度和简练度。

总之,调查者形式是一种十分常用的规划形式,它能够协助咱们完成目标之间的松耦合,并使得代码愈加灵敏、可扩展和易于保护。尽管调查者形式存在一些缺陷,但只需合理运用和规划,就能够最大化地发挥其长处,然后满意各种事务需求。