1. 模式的定义与特点
- 观察者(Observer Pattern)模式的定义:观察者模式又叫做
发布-订阅(Publish/Subscribe)模式
、模型-视图(Model/View)模式
、源-监听器(Source/Listener)模式
或从属者(Dependents)模式
。属于行为型
设计模式的一种,是一个在项目中经常使用的模式。指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
2. 模式的结构
-
观察者模式包含如下角色:
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
-
类图:
-
时序图:
3. 使用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当你的业务符合订阅-发布这种场景时就考虑这个模式。
- 如某电子商务网站可以在执行发送操作后给用户多个发送商品打折信息。
- 某团队战斗游戏中某队友牺牲将给所有成员提示等等。
4. 优缺点
- 优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
- 缺点:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
5. 具体案例
- 需求:
推特(抽象主题)
是美国一个常见的社交软件,可以在推特上关注一个人,并成为他的粉丝(抽象观察者)
。NBA球星詹姆斯的推特(具体主题)
也有许多关注者,其中包括真爱粉(具体观察者)
和黑粉(具体观察者)
。当打完比赛后,詹姆斯会在推特上更新他今天的得分(具体主题状态改变)
,真爱粉和黑粉都会得到通知并作出反应(自动更新)
。
-
推特(抽象主题):
public abstract class Twitter { protected List<Fan> fanList = new ArrayList<Fan>(); // 添加抽象观察者 public void add(Fan fan) { fanList.add(fan); } // 移除抽象观察者 public void remove(Fan fan) { fanList.remove(fan); } // 通知方法 public abstract void notifyState(int score); }
-
詹姆斯的推特(具体主题):
public class LBJTwitter extends Twitter { @Override public void notifyState(int score) { for (Fan fan : fanList) { fan.response(score); } } }
-
推特上的粉丝(抽象观察者):
public interface Fan { void response(int score); }
-
真爱粉和黑粉(具体观察者):
public class RealFan implements Fan{ @Override public void response(int score) { if (score > 30){ System.out.println("真爱粉:这是39岁的老头?"); } else { System.out.println("真爱粉:他都已经39了,你还要他怎么样!"); } } } public class BlackFan implements Fan { @Override public void response(int score) { if (score > 30) { System.out.println("黑粉:这么会刷?"); } else { System.out.println("他都已经93了,你还要他怎么样!"); } } }
-
测试:
public class Test { public static void main(String[] args) { LBJTwitter lbjTwitter = new LBJTwitter(); lbjTwitter.add(new RealFan()); lbjTwitter.add(new BlackFan()); System.out.println("当詹姆斯砍下35分---"); lbjTwitter.notifyState(35); System.out.println("当詹姆斯砍下20分---"); lbjTwitter.notifyState(20); } } /*当詹姆斯砍下35分--- 真爱粉:这是39岁的老头? 黑粉:这么会刷? 当詹姆斯砍下20分--- 真爱粉:他都已经39了,你还要他怎么样! 黑粉:他都已经93了,你还要他怎么样!*/