设计模式之观察者模式
介绍
观察者(Observer)模式定义对象之间一对多的关系,当一个对象被修改,其他关联对象会并得到通知并自动更新。
观察者模式是面向对象中最常用的设计模式之一,又称为发布-订阅(Publish/Subscribe)模式、模型-视图(Model-View)模式。
它是行为设计模式的一种。
经典样例:Java的事件监听机制
使用观察者模式之前的样例代码
在使用观察者模式之前,当遇到需要建立对象一对多关系,并且被关联对象改变时,我们会怎么做?
举个栗子:订阅杂志,有Magazine对象,People对象,下面代码为未使用观察者模式的代码:
class Magine { private String name; private double cost; private String content; private boolean isChange = false; public Magine(String name, double cost) { this.name = name; this.cost = cost; } public String getNews() { if (isChange) { return content; } return null; } public void setContent(String content) { if (this.content == null || !this.content.equals("content")) { isChange = true; } else { isChange = false; } this.content = content; } //------------getter与setter省略---------- class People { private String name; private Magine magine; public People(String name) { this.name = name; } public void subscribeMagine(Magine magine) { this.magine = magine; // ... } public String getNews() { return magine.getNews(); } }
测试类:
public class Test { public static void main(String[] args) { Magazine fashonMagine = new Magazine("fashon", 20); People zhangsan = new People("zhangsan"); zhangsan.subscribeMagine(fashonMagine); fashonMagine.setContent("The news"); System.out.println(zhangsan.getNews()); } } // 运行结果 The news
上面的类图如下所示:
上面是我学习观察者模式之前写的代码,相信很多人都写过这种代码,上面的代码咋看起来没什么问题,其实有很多缺点:
-
类与类之间耦合太严重,由类图可以看出People类关联Magine,People必须要有一个Magazine对象;
-
面向类编程,而没有面向抽象编程。
观察者模式
下面我们使用观察者模式对上面的代码进行重写,观察使用观察者模式的优点:
- Observer接口:
/** * 观察者接口 */ public interface Observer { public void update(String news); }
- subject接口:
/** * 主题接口 */ public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }
- 观察者具体类
/** * 观察者 */ public class ObserverPeople implements Observer { private String name; public ObserverPeople(String name) { this.name = name; } @Override public void update(String news) { readMagine(news); } public void readMagine(String content) { System.out.println("观察者观察到的内容: " + content); } }
- 主题具体类
/** * 主题杂志 */ public class SubjectMagazine implements Subject { List<Observer> observerList = new ArrayList<>(); private String name; private double cost; private String content; private boolean isChanged = false; public SubjectMagazine(String name, double cost) { this.name = name; this.cost = cost; } @Override public void registerObserver(Observer o) { if (!observerList.contains(o)) { observerList.add(o); } } @Override public void removeObserver(Observer o) { if (observerList.contains(o)) { observerList.remove(o); } } @Override public void notifyObservers() { if (isChanged) { for (Observer o : observerList) { o.update(content); } } isChanged = false; } public void setContent(String content) { if (this.content == null || !this.content.equals("content")) { isChanged = true; } this.content = content; notifyObservers(); }
//---------getter和setter省略-------------
}
- 测试类
public class Client { public static void main(String[] args) { SubjectMagazine fashonMagine = new SubjectMagazine("fashon", 20); ObserverPeople zhangsan = new ObserverPeople("zhangsan"); ObserverPeople lisi = new ObserverPeople("zhangsan"); fashonMagine.registerObserver(zhangsan); fashonMagine.registerObserver(lisi); fashonMagine.setContent("The observer news"); } } // 运行结果 观察者观察到的内容: The observer news 观察者观察到的内容: The observer news
如下图所示为使用观察者的类图
从上面类图中我们可以看出观察者模式改变了传统代码的结构,将观察者与被观察者(主题)解耦,普通类只需简单实现一个接口或抽象类就可以转换成观察者类,而且不需要观察者不断向被观察者抓取数据,被观察者会主动将自己的改变通知给多个观察者。
PUSH模型与PULL模型
在观察者中分为两种push模型与pull模式。
- push模型
push模型是指被观察者发生改变时,将状态改变的信息全部或部分发送给观察者。上面实现的观察者模式就是采用push模型。
- pull模型
pull模型是指被观察者发生改变时,将被观察者对象发送观察者,观察者可以自己获取感兴趣的内容。
pull模式的具体实现
- Observer接口:
/** * 观察者接口 */ public interface Observer { public void update(Subject Subject); }
- subject接口与上面的一致,具体实现类:
/** * 主题杂志 */ public class SubjectMagazine implements Subject { List<Observer> observerList = new ArrayList<>(); private String name; private double cost; private String content; private boolean isChanged = false; public SubjectMagazine(String name, double cost) { this.name = name; this.cost = cost; } @Override public void registerObserver(Observer o) { if (!observerList.contains(o)) { observerList.add(o); } } @Override public void removeObserver(Observer o) { if (observerList.contains(o)) { observerList.remove(o); } } @Override public void notifyObservers() { if (isChanged) { for (Observer o : observerList) { o.update(this); } } isChanged = false; } public void setContent(String content) { if (this.content == null || !this.content.equals("content")) { isChanged = true; } this.content = content; notifyObservers(); } public List<Observer> getObserverList() { return observerList; } public void setObserverList(List<Observer> observerList) { this.observerList = observerList; } //------------getter与setter省略---------- }
- 具体观察者类:
/** * 观察者 */ public class ObserverPeople implements Observer { private String name; public ObserverPeople(String name) { this.name = name; } @Override public void update(Subject subject) { readMagine(subject); } public void readMagine(Subject subject) { SubjectMagazine magazine = (SubjectMagazine) subject; System.out.println("观察者观察到的内容:" + magazine.getContent()); } }
- 测试类:
public class Client { public static void main(String[] args) { SubjectMagazine fashonMagine = new SubjectMagazine("fashon", 20); ObserverPeople zhangsan = new ObserverPeople("zhangsan"); ObserverPeople lisi = new ObserverPeople("zhangsan"); fashonMagine.registerObserver(zhangsan); fashonMagine.registerObserver(lisi); fashonMagine.setContent("The observer news"); } } // 运行结果 观察者观察到的内容: The observer news 观察者观察到的内容: The observer news
两种模式的比较:
push模式是假定subject知道Observer知道哪些内容,而pull模式Observer可以根据自己的兴趣获取信息,因为push模型下Subject在update方法中传递的是具体的参数类型,而pull模型下subject传递在update方法中传递一般为自身对象,是最大的参数集合。
JDK内置的观察者模式
Observer接口:
public interface Observer { void update(Observable o, Object arg); }
从上面的代码中,可以知道,JDK内置的观察者模式是PUSH模式与PULL模型结合。
Observable类:
public class Observable { private boolean changed = false; private Vector<Observer> obs; public Observable() { obs = new Vector<>(); } public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } }
public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } public synchronized void deleteObservers() { obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() { return obs.size(); } }
有JDK源码,可以知道相比上面实现的代码而言,添加了对同步的控制和一些辅助方法,如countObservers,统计观察者数目,hasChanged判断状态是否改变。但我们使用JDK内置的观察者模式时,必须继承Observable类,这会限制观察者类的继承潜力。
总结
观察者模式给程序员提供一个建立对象之间一对多关联关系的良好方法,并能够将信息生成层与响应层分离,给以后的修改留下很好的结构。