设计模式之观察者模式
应用场景
在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心;还有,当我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。这样的例子还有很多,例如,股票价格与股民、微信公众号与微信用户、气象局的天气预报与听众、小偷与警察等。
在软件世界也是这样,例如,Excel 中的数据与折线图、饼状图、柱状图之间的关系;MVC 模式中的模型与视图的关系;事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便。
模式的定义
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
模式的结构与实现
模式结构
观察者模式的主要角色如下。
抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
如下图:
模式实现
观察者模式具体代码如下:
1 // 抽象主题者 被观察者 2 public interface ISubject<E> { 3 4 boolean attach(IObserver<E> observer); 5 6 boolean detach(IObserver<E> observer); 7 8 void notify(E event); 9 10 }
1 //抽象观察者 2 public interface IObserver<E> { 3 void update(E event); 4 }
1 //具体主题者 2 public class ConcreteSubject<E> implements ISubject<E>{ 3 4 private List<IObserver> observers=new ArrayList<>(); 5 6 7 @Override 8 public boolean attach(IObserver<E> observer) { 9 return !this.observers.contains(observer)&&this.observers.add(observer); 10 } 11 12 @Override 13 public boolean detach(IObserver<E> observer) { 14 return this.observers.remove(observer); 15 } 16 17 @Override 18 public void notify(E event) { 19 for(IObserver observer:this.observers){ 20 observer.update(event); 21 } 22 } 23 }
1 // 具体的观察者 2 public class ConcreteObserver<E> implements IObserver<E> { 3 @Override 4 public void update(E event) { 5 System.out.println("观察者接收事件(信息):"+event); 6 } 7 }
1 public class Test { 2 3 public static void main(String[] args) { 4 //被观察者 5 ISubject<String> observable=new ConcreteSubject<>(); 6 //观察者 7 IObserver<String> observer=new ConcreteObserver<>(); 8 //注册 9 observable.attach(observer); 10 //通知 11 observable.notify("被观察者这发出通知信息..."); 12 } 13 }
运行结果:
案例分析
利用观察者模式设计一个学校铃声的事件处理程序。
分析如下:
在本实例中,学校的“铃”是事件源和目标,“老师”和“学生”是事件监听器和具体观察者,“铃声”是事件类。学生和老师来到学校的教学区,都会注意学校的铃,这叫事件绑定;当上课时间或下课时间到,会触发铃发声,这时会生成“铃声”事件;学生和老师听到铃声会开始上课或下课,这叫事件处理。这个实例非常适合用观察者模式实现。铃声事件模型参考下图:
程序设计如下:
代码结构如下:
本项目中亦用到了策略模式
关键代码
1 /** 2 * 铃声事件类:用于封装事件源及一些与事件相关的参数 3 */ 4 public class RingEvent extends AbsEvent { 5 6 private static final long serialVersionUID = 1L; 7 8 private RingStrategy ringStrategy; //铃声策略 预备、上课、下课 9 10 public RingEvent(RingStrategy ringStrategy) { 11 super(); 12 this.ringStrategy=ringStrategy; 13 } 14 15 public RingEvent(Bell source, RingStrategy ringStrategy) { 16 super(source); 17 this.ringStrategy=ringStrategy; 18 } 19 20 public RingStrategy getRingStrategy() { 21 return ringStrategy; 22 } 23 24 public void setRingStrategy(RingStrategy ringStrategy) { 25 this.ringStrategy = ringStrategy; 26 } 27 }
1 /** 2 * 目标类:事件源,铃 观察者 3 */ 4 public class Bell extends AbsBell { 5 6 //注册监听事件 7 List<AbsEventListener> listeners;//监听器容器 8 9 10 public Bell() { 11 listeners = new ArrayList<>(); 12 } 13 14 //给事件源绑定监听器 15 public void addListener(AbsEventListener listener){ 16 listeners.add(listener); 17 } 18 19 //事件触发器:敲钟,当铃声sound的值发生变化时,触发事件。 20 public void ring(Class<? extends RingStrategy> clazz){ 21 //用策略模式 获取响铃的策略 22 RingStrategy strategy=RingContext.getRingStrategy(clazz); 23 strategy.ring();//打铃声 24 RingEvent e = new RingEvent(strategy); 25 //通知注册在该事件源上的所有监听器 26 notice(e); 27 } 28 29 //当事件发生时,通知绑定在该事件源上的所有监听器做出反应(调用事件处理方法) 30 private void notice(RingEvent e){ 31 for(AbsEventListener listener:listeners){ 32 listener.heardBell(e); 33 } 34 } 35 }
1 /** 2 * 抽象事件监听器 3 */ 4 public abstract class AbsEventListener implements Listener{ 5 //监听器(监听者做出的响应) 6 public abstract void heardBell(RingEvent e); 7 8 }
1 public class TeachEventListener extends AbsEventListener{ 2 @Override 3 public void heardBell(RingEvent e) { 4 if(e.getRingStrategy() instanceof PrepareRingStrategy) { 5 System.out.println("老师准备上课..."); 6 } else if(e.getRingStrategy() instanceof BeginClassRingStrategy) { 7 System.out.println("老师上课了..."); 8 }else { 9 System.out.println("老师下课了..."); 10 } 11 } 12 }
1 /** 2 * 响铃策略上下文 3 */ 4 public class RingContext { 5 6 public static RingStrategy getRingStrategy(Class<? extends RingStrategy> clazz){ 7 try{ 8 if(null!=clazz) 9 return clazz.newInstance(); 10 }catch (Exception e){ 11 e.printStackTrace(); 12 } 13 return null; 14 } 15 }
1 /** 2 * 响铃策略-预备 3 */ 4 public class PrepareRingStrategy extends RingStrategy{ 5 @Override 6 public void ring() { 7 System.out.println("预备铃声响!"); 8 } 9 }
1 public class ObserverPatternTest { 2 3 public static void main(String[] args) { 4 Bell bell = new Bell(); //铃(事件源) 5 bell.addListener(new TeachEventListener()); //注册监听器(老师) 6 bell.addListener(new StuEventListener()); //注册监听器(学生) 7 bell.ring(PrepareRingStrategy.class); //打预备铃声 8 System.out.println("------------"); 9 bell.ring(BeginClassRingStrategy.class); //打上课铃声 10 System.out.println("------------"); 11 bell.ring(OverClassRingStrategy.class); //打下课铃声 12 } 13 }sdfsf
运行效果:
在开源框架中的应用(待补充)
模式的优缺点
观察者模式是一种对象行为型模式,其主要优点如下:
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
目标与观察者之间建立了一套触发机制
它的主要缺点如下:
目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
参考
1、https://www.jianshu.com/p/d55ee6e83d66
2、http://c.biancheng.net/view/1390.html
源代码
PS:代码已经上传,需要查看的朋友点击拿取
爱生活,爱学习,爱感悟,爱挨踢