观察者模式: 定义了一种 1对多 的依赖关系, 让多个观察者对象同时监听1个主题对象.
这个主题对象在状态发生变化时, 会通知所有的观察者对象, 使它们能够同时更新自己.
稍微解释一下 这个1 对多 的依赖关系.
1对多 这个关键词我们常常在DB 表设计里提到, 但是这里的意思是有点区别的.
首先, 1 是1个对象, 而不是1个类, 而多也是指多个对象, 而不是多个类.
其次, 这里的多个对象可以是多个不同的类的对象. 甚至是毫无关系的多个类.
再次, 这个依赖关系, 到底是1个对象依赖多个对象, 还是多个对象依赖1个对象呢.
在这里的定义来讲答案是后者.
但是, 实际上, 1个观察者也可以观察多个被观察者的. (但这就不属于观察者模式了)
所以, 观察者模式(Observer) 也叫做 发布-订阅模式(publish/Subscribe).
相当与, 多个读者同时收听1个电台.
首先睇下Observer 模式的标准UML图.
我们大概得出上图有4个角色.
Observer是1个接口, 我们可以理解它是1个抽象观察者类.
它只有1个update()方法, 也就是说它可以通过这个方法来执行某些动作.
Subject是1个接口, 我们可以理解为1个抽象被观察者类.
我们可以见到它有5个方法.
attach() 和 detach()方法用来增删观察者的数量, 也就是指当前被观察者对象到底有多少个观察者在观察着它.
setState(), 和 getState() 用于获取和设置通知者对象本身的状态, 这个状态通常是传送给观察者们的信息或参数.
也就是说观察者模式到底在观察什么. 无非就是观察被观察者的这个状态.
Notify(), 被观察者通知所有观察者, 让观察者根据自己的当前状态(getState())执行自己的update()方法
这个(些)就是具体的被观察者类, 只要实现了Subject接口, 就可以添加1些对象作为自己的观察者(或者叫粉丝啦)
写到这里, 大家都会了解到, 这个类里面肯定有1个容器, 用于存放观察者的对象.
这个容器可以根据需要由具体的被观察者选择, 通常是无序不能重复的Set容器(例如 HashSet)
而Notify()方法无非就是遍历自己的观察者容器, 逐个执行观察者的update()方法.
这些类可以是毫无关联的类, 但是它们都必须实现Observer接口
一旦这些对象被通知者, 加入自己的容器, 就相当于观察者正在观察某个被观察者.
注意, 观察者可以被多个被观察者加入自己的容器, 也就是相当于观察了多个被观察者了.(但这就break了观察者模式)
下面我们用1个具体例子来简单实现这个模式.
我们假定1个事件有3个角色.
1. 指挥者.(Commander)
指挥炮手打炮, 他可以让让若干个炮手和炮灰纳入自己的命令范围.
2. 炮手, (CannonShooter)
一旦指挥者通知目标, 若干个炮手就往哪个目标轰击.
3. 炮灰 (CannonFodder)
一旦指挥者通知, 炮灰就趴下..
也就是说, 炮手必须知道指挥这的状态(目标信号), 到底打谁.
而炮灰是无序关心到底打哪里的, 一旦接到通知, 趴下就是了.
public interface Subject { public void attach(Observer obs); public void detach(Observer obs); public void sNotify(); //notify is a finel method of Object class public int getState(); public void setState(int state); }
5个方法的意义上面已经解释过了.
通知方法之所以不写成notify(), 是因为notify()本身是Object类的1个finel方法
public interface Observer { public void update(); }
只有1个抽象方法update()
import java.util.HashSet; import java.util.Iterator; public class Commander implements Subject{ private int targetPlaceID; private HashSet<Observer> gunnerSet = new HashSet<Observer>(); @Override public void attach(Observer obs){ this.gunnerSet.add(obs); } @Override public void detach(Observer obs) { this.gunnerSet.remove(obs); } @Override public void sNotify() { if (this.gunnerSet.isEmpty()){ return; } Iterator itr = this.gunnerSet.iterator(); while (itr.hasNext()){ Observer obs = (Observer)itr.next(); obs.update(); } } @Override public int getState() { // TODO Auto-generated method stub return this.targetPlaceID; } @Override public void setState(int state) { // TODO Auto-generated method stub this.targetPlaceID = state; } }
它重写了接口所有方法.
所谓的notify()方法, 无非就是遍历自己容器的所有观察者, 该干嘛的干嘛(遍历调用它们的update())方法
public class CannonShooter implements Observer{ private Subject cmder; public CannonShooter(Subject cmder){ this.cmder = cmder; } public Subject getCmder() { return cmder; } public void setCmder(Subject cmder) { this.cmder = cmder; } public void fireCannon(int targetPlace){ System.out.println(this.getClass().getSimpleName() + ": fired on target(id:" + targetPlace + ") by Cannon"); } @Override public void update() { // TODO Auto-generated method stub fireCannon(cmder.getState()); } }
可以见到, 炮手必须知道指挥者的状态信息, 所以它里面必须有个当前指挥者的对象成员.
public class CannonFodder implements Observer{ private int id; public CannonFodder(int id){ this.id = id; } public void getDown(){ System.out.println(this.getClass().getSimpleName() +" id:" + this.id + " getDowned"); } @Override public void update() { // TODO Auto-generated method stub this.getDown(); } }
炮灰无需关心指挥者的状态, 里面只需要重写自己的update()方法就ok.
public class ClientObserver { public static void f(){ Commander cmder = new Commander(); CannonShooter cster = new CannonShooter(cmder); CannonFodder cfder1 = new CannonFodder(1); CannonFodder cfder2 = new CannonFodder(2); CannonFodder cfder3 = new CannonFodder(3); cmder.setState(107); cmder.attach(cster); cmder.attach(cfder1); cmder.attach(cfder2); cmder.attach(cfder3); cmder.sNotify(); cmder.setState(108); cmder.detach(cfder3); cmder.sNotify(); } }
上面的代码不难看懂.
无非就是实例化1个指挥者, 1个炮手, 3个炮灰
首先, 指挥者通知向107目标打炮, 炮手射了, 3个炮灰趴下了.
后来指挥者想向打击108目标, 但是觉得第3号炮灰不在攻击范围, 所以从自己的观察者容器里移除3号炮灰.
这时, 炮手向108号目标打击, 只有1号2号炮灰听指挥爬下.
相当灵活.
上面的例子中, 观察者和被观察者的耦合性不大.
1个subject可以有任意数目的观察者Observer, 程序猿令subject发出通知时根本无需知道观察者是谁, 有多少观察者存在.
而单个观察者本身也无需知道到底有几个其他观察者同时存在.
很明显嘛, 就是当1个对象发生改变的同时需要同时改变其他多个对象时.
而且, 观察者模式令到耦合的双方, 依赖与接口(抽象), 而不是依赖于具体(非抽象类), 符合封闭-开放模式.
另1个例子:
package com.home.javacommon.designpattern.observer; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; interface Observer{ void sNotify(); } interface Publisher<T extends Observer>{ void setObserver(T t); void update(); } class Target{ private String target; public String getTarget(){ return this.target; } public Target(String target){ this.target = target; } } @Slf4j class Commander implements Observer{ private Target target; public void setTarget(Target target){ log.info("Commander set target is {}: ", target.getTarget()); this.target = target; } public Target getTarget(){ return this.target; } private List<Publisher> publishersList = new ArrayList<>(); public void addPublisher(Publisher publisher){ this.publishersList.add(publisher); } @Override public void sNotify() { log.info("Commander ordered to fire the target: {}", this.target.getTarget()); for (Publisher publisher : this.publishersList) { publisher.setObserver(this); publisher.update(); } } } @Slf4j class CannonShooter implements Publisher<Commander>{ private String name; public CannonShooter(String name){ this.name = name; } private Commander commander; @Override public void setObserver(Commander commander) { this.commander = commander; } @Override public void update() { log.info("Canoon Shooter: {} fired the target {}", this.name, this.commander.getTarget().getTarget()); } } public class ObserverClient { public static void main(String[] args){ Commander mike = new Commander(); mike.setTarget(new Target("The Tree")); CannonShooter jack = new CannonShooter("jack"); CannonShooter bill = new CannonShooter("bill"); mike.addPublisher(jack); mike.addPublisher(bill); mike.sNotify(); } }