观察者模式
Observer Pattern:观察者模式,又叫做发布—订阅模式,在GoF23种设计模式之中属于行为型模式的一种,是对于对象之间存在一对多的对于关系的时候使用的一个模式。
这种一对多的关系体现的方面是,当一个对象发生了变化,其他的所有依赖于这个对象的对象本身也会实时的得到通知并且进行更新自己的操作,达到同步更新的效果。
结构
通过简单的类图分析一下,观察者模式一共有四个角色:
-
抽象主题:(抽象是为了解耦合,易扩展)
-
具体主题:实现抽象主题的具体实现类,也就是观察者模式中的被观察者,发布—订阅模式中的Topic主题对象。
这个类中维护一个观察者的数组,里面有零个,一个或者多个观察者,它自己本身是不需要知道的。
其次,这个类里面还包含三个统一的方法,一个是增加观察者的方法,一个是移除观察者的方法,还有一个就是通过循环通知每个观察者更新的方法。
-
抽象观察者:(抽象是为了解耦合,易扩展)
-
具体观察者:实现抽象观察者的具体实现类,也就是观察者模式中的观察者对象,发布—订阅模式中的订阅对象。
该类中需要提供的就是一个更新自己的方法,当主题变更的时候,这个方法就进行更新自己的操作(该类中可以保存一个指向主题的引用)。
举例说明
-
观察者模式的使用场景有很多,更加耳熟的应该是发布订阅的模式,不论是RabbitMQ里面的Topic还是Kafka里面使用的都是这个模式。
比如说kafka,kafka是一种高吞吐量的分布式发布订阅消息系统,从名字中就可以看出来使用的就是发布订阅模式,在这个里面,每个消息都有一个主题被称为Topic,生产者生产消息,所有的消息都存在一个指定的Topic队列之中,而消费者消费消息,消费者首先订阅这个主题Topic,当队列中有消息的时候就可以消费。至于生产者是什么,在哪里,怎么做的消费者不需要知道,也不必要知道,它只需要订阅这个Topic,然后消费消息就行了。
-
有点没说清楚,再来一个例子。来个打棒球的例子,棒球比赛分为两队,每一场分为两方,一方是进攻方(只有一个人上场,叫做击球手),另一方是防守方(一个投球手,一个捕手也就是接球的,还有七个闲散人员,嘿嘿,就是在场地里面捡球的。)
棒球比赛的得分的规则就是击球手击飞球,在防守方所有人没有捡到球的情况下,从本垒跑向一垒,二垒,三垒,再回到本垒得到一分。
OK,回到正题,这里呢,击球手就是被观察者也就是模式中的主题,防守方的所有队员就是观察者,全部观察着这个击球手。
“砰”一声响亮的击球声响起,主题(击球手)发出通知“嘿,我击中球了!”,与此同时,所有的观察者(防守方)全都收到了消息并且实时更新自己的状态(要去捡球了啊,不然对方就要得分了啊)
这就是观察者模式的实际体现!!!
注意
- 显而易见的好处就是解耦合了,易于扩展,将观察者和被观察者完全隔离,你不需要知道我,我也不需要知道你,观察者做的只是一个订阅主题即可。
- 既然解耦合了,那么观察者就只能知道被观察者变化了,但是怎么变化的就不会知道了。
- 观察者模式还有一个用处,就是创建一套链式的触发机制。也就是说可以适用于对象1的变化影响对象2的变化,对象2的变化又影响对象3的变化等等的一套触发链的场景。
一个小DEMO
-
场景
一场崭新崭新的棒球比赛开始了,首先上来了A队的击球手,同时B队的所有人也上场了,他们准备好了,气氛很紧张,比赛一触即发。
-
抽象观者者先准备一下
/** * 观察者模式——抽象观察者 Observer对象 * 符合场景,这里就是以选手作为抽象观察者对象啦 * @author wq * */ public interface Player { // 有一个更新的方法 void update(); }
-
再来个抽象主题准备一下
/** * 观察者模式——抽象主题 Subject对象 * @author wq * */ import java.util.ArrayList; import java.util.List; public abstract class Topic { // 保存着所有的观察者对象 protected List<Player> players = new ArrayList<Player>(); // 订阅 public abstract void subscribe(Player player); // 取消订阅 public abstract void cancel(Player player); // 通知所有观察者 public abstract void notifyAllPlayer(); }
-
击球手上线(被观察者,主题,Topic,Subject,怎么叫随自己喜欢)
/** * 观察者模式——具体主题对象 * 击球手 * @author wq * */ public class Batter extends Topic{ @Override public void subscribe(Player player) { players.add(player); } @Override public void cancel(Player player) { players.remove(player); } @Override public void notifyAllPlayer() { for(Player player : players) { player.update(); } } }
-
防守方队员上线
/** * 观察者模式——具体观察者对象 * 投球手 * @author wq * */ public class Bowler implements Player{ @Override public void update() { System.out.println("Bowler:!!! 击中球了!击中了!"); } } //-----------------------------------------------------------------------------// /** * 观察者模式——具体观察者对象 * 捕手 * @author wq * */ public class Catcher implements Player{ @Override public void update() { System.out.println("Catcher:我没接到球?? 被他击中了??!!"); } } //-----------------------------------------------------------------------------// /** * 观察者模式——具体观察者对象 * 外野手 * @author wq * */ public class Fielder implements Player{ @Override public void update() { System.out.println("Fielder:哦!!他击中了,快去找球!!!"); } }
-
比赛正式开始
/** * 观察者模式 * 测试类 * @author wq * */ public class Main { public static void main(String[] args) { System.out.println("!!!!!!!!比赛正式开始!!!!!!!!!!!"); // 击球手到位 Batter batter = new Batter(); // 防守方上场 Bowler bowler = new Bowler(); // 投球手 Catcher catcher = new Catcher(); // 捕手 Fielder fielder = new Fielder(); //外野手 // 统一向击球手行注目礼,死盯着他 batter.subscribe(bowler); batter.subscribe(catcher); batter.subscribe(fielder); // 投球手 发射-----!- System.out.println("!!!!!!砰!!!!!!!"); // 球被击中了 batter.notifyAllPlayer(); } }
-
结果走一泼
!!!!!!!!比赛正式开始!!!!!!!!!!! !!!!!!砰!!!!!!! Bowler:!!! 击中球了!击中了! Catcher:我没接到球?? 被他击中了??!! Fielder:哦!!他击中了,快去找球!!!
完成
PS: Java中是已经实现了Observer类的,用的时候继承这个接口就行,但是这个例子还是自己搭建一个,两种方式都简单。