第六章 6.1 观察者模式

分类:01_设计模式

标签:

第六章 行为型模式(11种)

行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。

行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

行为型模式分为:

以上 11 种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。

6.1 观察者模式

6.1.1 观察者模式介绍

观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者 一些产品的设计思路,都有这种模式的影子.

现在我们常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。当我们观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。

生活中也有许多观察者模式的应用,比如 汽车与红绿灯的关系,'红灯停,绿灯行',在这个过程中交通信号灯是汽车的观察目标,而汽车是观察者.

105.jpg

观察者模式(observer pattern)的原始定义是:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。

解释一下上面的定义: 观察者模式它是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应的作出反应.

在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以应对多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展.

观察者模式的别名有发布-订阅(Publish/Subscribe)模式,模型-视图(Model-View)模式、源-监听(Source-Listener) 模式等

6.1.2 观察者模式原理

观察者模式结构中通常包括: 观察目标和观察者两个继承层次结构.

106.jpg

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

6.1.3 观察者模式实现

/**
* 抽象观察者
* @author spikeCong
* @date 2022/10/11
**/
public interface Observer {

   //update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
   public void update();
}

/**
* 具体观察者
* @author spikeCong
* @date 2022/10/11
**/
public class ConcreteObserverOne implements Observer {

   @Override
   public void update() {
       //获取消息通知,执行业务代码
       System.out.println("ConcreteObserverOne 得到通知!");
   }
}

/**
* 具体观察者
* @author spikeCong
* @date 2022/10/11
**/
public class ConcreteObserverTwo implements Observer {

   @Override
   public void update() {
       //获取消息通知,执行业务代码
       System.out.println("ConcreteObserverTwo 得到通知!");
   }
}

/**
* 抽象目标类
* @author spikeCong
* @date 2022/10/11
**/
public interface Subject {

    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}

/**
* 具体目标类
* @author spikeCong
* @date 2022/10/11
**/
public class ConcreteSubject implements Subject {

   //定义集合,存储所有观察者对象
   private ArrayList<Observer> observers = new ArrayList<>();


   //注册方法,向观察者集合中增加一个观察者
   @Override
   public void attach(Observer observer) {
       observers.add(observer);
   }

   //注销方法,用于从观察者集合中删除一个观察者
   @Override
   public void detach(Observer observer) {
       observers.remove(observer);
   }

   //通知方法
   @Override
   public void notifyObservers() {
       //遍历观察者集合,调用每一个观察者的响应方法
       for (Observer obs : observers) {
           obs.update();
       }
   }
}

public class Client {

   public static void main(String[] args) {
       //创建目标类(被观察者)
       ConcreteSubject subject = new ConcreteSubject();

       //注册观察者类,可以注册多个
       subject.attach(new ConcreteObserverOne());
       subject.attach(new ConcreteObserverTwo());

       //具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
       subject.notifyObservers();
   }
}

6.1.4 观察者模式应用实例

接下来我们使用观察模式,来实现一个买房摇号的程序.摇号结束,需要通过短信告知用户摇号结果,还需要想MQ中保存用户本次摇号的信息.

1 ) 未使用设计模式


/**
* 模拟买房摇号服务
* @author spikeCong
* @date 2022/10/11
**/
public class DrawHouseService {

   //摇号抽签
   public String lots(String uId){
       if(uId.hashCode() % 2 == 0){
           return "恭喜ID为: " + uId + " 的用户,在本次摇号中中签! !";
       }else{
           return "很遗憾,ID为: " + uId + "的用户,您本次未中签! !";
       }
   }
}

public class LotteryResult {

   private String uId; // 用户id
   private String msg; // 摇号信息
   private Date dataTime; // 业务时间
   
   
//get&set.....
}

/**
* 开奖服务接口
* @author spikeCong
* @date 2022/10/11
**/
public interface LotteryService {

   //摇号相关业务
   public LotteryResult lottery(String uId);
}


/**
* 开奖服务
* @author spikeCong
* @date 2022/10/11
**/
public class LotteryServiceImpl implements LotteryService {

   //注入摇号服务
   private DrawHouseService houseService = new DrawHouseService();

   @Override
   public LotteryResult lottery(String uId) {
       //摇号
       String result = houseService.lots(uId);

       //发短信
       System.out.println("发送短信通知用户ID为: " + uId + ",您的摇号结果如下: " + result);

       //发送MQ消息
       System.out.println("记录用户摇号结果(MQ), 用户ID:" +  uId + ",摇号结果:" + result);

       return new LotteryResult(uId,result,new Date());
   }
}

@Test
public void test1(){
   LotteryService ls = new LotteryServiceImpl();
   String result  = ls.lottery("1234567887654322");
   System.out.println(result);
}

1 ) 使用观察者模式进行优化

上面的摇号业务中,摇号、发短信、发MQ消息是一个顺序调用的过程,但是除了摇号这个核心功能以外, 发短信与记录信息到MQ的操作都不是主链路的功能,需要单独抽取出来,这样才能保证在后面的开发过程中保证代码的可扩展性和可维护性.

107.jpg


/**
* 事件监听接口
* @author spikeCong
* @date 2022/10/11
**/
public interface EventListener {

   void doEvent(LotteryResult result);
}

/**
* 短信发送事件
* @author spikeCong
* @date 2022/10/11
**/
public class MessageEventListener implements EventListener {

   @Override
   public void doEvent(LotteryResult result) {
       System.out.println("发送短信通知用户ID为: " + result.getuId() +
               ",您的摇号结果如下: " + result.getMsg());
   }
}

/**
* MQ消息发送事件
* @author spikeCong
* @date 2022/10/11
**/
public class MQEventListener implements EventListener {

   @Override
   public void doEvent(LotteryResult result) {
       System.out.println("记录用户摇号结果(MQ), 用户ID:" +  result.getuId() +
               ",摇号结果:" + result.getMsg());
   }
}

事件处理


/**
* 事件处理类
* @author spikeCong
* @date 2022/10/11
**/
public class EventManager {

   public enum EventType{
       MQ,Message
   }

   //监听器集合
   Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();

   public EventManager(Enum<EventType>... operations) {
       for (Enum<EventType> operation : operations) {
           this.listeners.put(operation,new ArrayList<>());
       }
   }

   /**
    * 订阅
    * @param eventType 事件类型
    * @param listener  监听
    */
   public void subscribe(Enum<EventType> eventType, EventListener listener){
       List<EventListener> users = listeners.get(eventType);
       users.add(listener);
   }

   /**
    * 取消订阅
    * @param eventType 事件类型
    * @param listener  监听
    */
   public void unsubscribe(Enum<EventType> eventType,EventListener listener){
       List<EventListener> users = listeners.get(eventType);
       users.remove(listener);
   }

   /**
    * 通知
    * @param eventType 事件类型
    * @param result    结果
    */
   public void notify(Enum<EventType> eventType, LotteryResult result){
       List<EventListener> users = listeners.get(eventType);
       for (EventListener listener : users) {
           listener.doEvent(result);
       }
   }
}

摇号业务处理


/**
* 开奖服务接口
* @author spikeCong
* @date 2022/10/11
**/
public abstract class LotteryService{

   private EventManager eventManager;

   public LotteryService(){
       //设置事件类型
       eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);
       //订阅
       eventManager.subscribe(EventManager.EventType.Message,new MessageEventListener());
       eventManager.subscribe(EventManager.EventType.MQ,new MQEventListener());
   }

   public LotteryResult lotteryAndMsg(String uId){
       LotteryResult result = lottery(uId);
       //发送通知
       eventManager.notify(EventManager.EventType.Message,result);
       eventManager.notify(EventManager.EventType.MQ,result);

       return result;
   }

   public abstract LotteryResult lottery(String uId);
}

/**
* 开奖服务
* @author spikeCong
* @date 2022/10/11
**/
public class LotteryServiceImpl extends LotteryService {

   //注入摇号服务
   private DrawHouseService houseService = new DrawHouseService();

   @Override
   public LotteryResult lottery(String uId) {
       //摇号
       String result = houseService.lots(uId);

       return new LotteryResult(uId,result,new Date());
   }
}

测试


@Test
public void test2(){
   LotteryService ls = new LotteryServiceImpl();
   LotteryResult result  = ls.lotteryAndMsg("1234567887654322");
   System.out.println(result);
}


6.1.5 观察者模式总结

1) 观察者模式的优点

2) 观察者模式的缺点

3 ) 观察者模式常见的使用场景

4 ) JDK 中对观察者模式的支持

JDK中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持.

用户可以直接使用Observer接口和Observable类作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类,使用JDK中提供的这两个类可以更加方便的实现观察者模式.


修改内容