观察者模式
观察者模式,又叫做发布–订阅模式(Publish/Subscribe)模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生改变时,会通知所有的观察者对象,使他们能够自动更新自己。
观察者模式(Observer)结构图
- Subject类:可翻译为主题或抽象通知者,一般用一个抽象类或一个接口实现。它把所有对观察者对象的引用保留在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
// 抽象通知者
public abstract class Subject {private List<Observer> list = new ArrayList<>();// 状态protected String subjectState;// 增加观察者public void attatch(Observer observer) {list.add(observer);}// 减少观察者public void detach(Observer observer) {list.remove(observer);}// 通知观察者public void notifyObserver() {for (Observer item : list) {item.update();}}public String getSubjectState() {return subjectState;}public void setSubjectState(String subjectState) {this.subjectState = subjectState;}
}
- Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或一个接口实现。更新接口通常包含一个update()方法,这个方法叫做更新方法。
// 抽象观察者
public abstract class Observer { public abstract void update();
}
- ConcreteSubject类:具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记的观察者发出通知。具体主题角色通常用一个子类实现。
// 具体主题或具体通知者
public class ConcreteSubject extends Subject {
}
- ConcreteObserver类:具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常使用一个子类实现。
// 具体观察者
public class ConcreteObserver extends Observer {private String name;private Subject sub;public ConcreteObserver(String name, Subject sub) {this.name = name;this.sub = sub;}@Overridepublic void update() {System.out.println("观察者"+this.name + "的新状态是"+this.sub.getSubjectState());}
}
客户端调用
Subject subject = new ConcreteSubject();
subject.attatch(new ConcreteObserver("nameX", subject));
subject.attatch(new ConcreteObserver("nameY", subject));
subject.attatch(new ConcreteObserver("nameZ", subject));subject.notifyObserver();
案例
那么现在举一个简单的例子:某公司中,某部门几个员工在上班期间趁着老板不在,炒股、看NBA等等开始摸鱼,并且通过买一些小零食贿赂前台小姐姐,当老板回来时通知他们一声。使用观察者模式如何实现呢?
那么首先分清楚角色划分,具体主题或具体通知者就是我们的前台小姐姐,那么具体观察者就是部门的几个员工。
我们的抽象通知者Subject类基本是不需要改变的,直接拿上面的的代码就行,然后设置个名称区分下就行。
- Subject抽象通知者
// 抽象通知者
public abstract class Subject {private List<Observer> list = new ArrayList<>();protected String subjectState;protected String name;public Subject(String name) {this.name = name;}// 增加观察者public void attatch(Observer observer) {list.add(observer);}// 减少观察者public void detach(Observer observer) {list.remove(observer);}// 通知观察者public void notifyObserver() {for (Observer item : list) {item.update();}}public String getSubjectState() {return subjectState;}public void setSubjectState(String subjectState) {this.subjectState = subjectState;}
}
- 具体通知者
// 前台小姐姐
public class Secretary extends Subject {public Secretary(String name) {super(name);}
}
- 抽象观察者
// 看股票的同事
public class StockObserver extends Observer {public StockObserver(String name, Subject sub) {super(name, sub);}@Overridepublic void update() {System.out.println(super.sub.name + ":"+super.sub.getSubjectState() + "!" + super.name + "快关闭股票行情,开始工作");}
}// 看NBA的同事
public class NBAObserver extends Observer {public NBAObserver(String name, Subject sub) {super(name, sub);}@Overridepublic void update() {System.out.println(super.sub.name + ":"+super.sub.getSubjectState() + "!" + super.name + "快关闭NBA直播,开始工作");}
}
- 客户端调用
Subject subject = new Secretary("前台小姐姐");
subject.attatch(new StockObserver("张三", subject));
subject.attatch(new StockObserver("李四", subject));
subject.attatch(new NBAObserver("王五", subject));
subject.setSubjectState("老板回来了");subject.notifyObserver();
结果:
前台小姐姐:老板回来了!张三快关闭股票行情,开始工作
前台小姐姐:老板回来了!李四快关闭股票行情,开始工作
前台小姐姐:老板回来了!王五快关闭NBA直播,开始工作
Java内置接口实现
实际上,java已经为观察者模式准备好了相关的接口和抽象类了。观察者接口java.util.Observer
和通知者java.util.Observable
。有了这些Java内置的代码,我们只需要扩展或继承Observable,并且告诉它什么时候应该通知观察者就OK了。
那么后边考虑到如果有多个通知者的情况下,为了避免需要在update()方法中对通知者进行强转,中间又加了一层Subject类。
代码结构图
那么看看具体实现过程:
- 抽象通知者类
// 抽象通知者
public abstract class Subject extends Observable {private List<Observer> list = new ArrayList<>();protected String subjectState;protected String name;public Subject(String name) {this.name = name;}public String getSubjectState() {return subjectState;}public void setSubjectState(String subjectState) {this.subjectState = subjectState;super.setChanged(); // 改变通知者的状态super.notifyObservers(); // 调用父类的方法,通知所有的观察者}
}
- 具体通知者
// 前台小姐姐
public class Secretary extends Subject {public Secretary(String name) {super(name);}
}
- 具体观察者类
// 看股票的同事
public class StockObserver implements Observer {protected String name;public StockObserver(String name) {this.name = name;}@Overridepublic void update(Observable o, Object arg) {Subject sub = (Subject) o;System.out.println(sub.name + ":"+sub.getSubjectState() + "!" + this.name + "快关闭股票行情,开始工作");}}// 看NBA的同事
public class NBAObserver implements Observer { protected String name;public NBAObserver(String name) {this.name = name;}@Overridepublic void update(Observable o, Object arg) {Subject sub = (Subject) o;System.out.println(sub.name + ":"+sub.getSubjectState() + "!" + this.name + "快关闭NBA直播,开始工作");}
}
- 客户端调用
Subject subject = new Secretary("前台小姐姐");
subject.addObserver(new StockObserver("张三"));
subject.addObserver(new StockObserver("李四"));
subject.addObserver(new NBAObserver("王五"));
subject.setSubjectState("老板回来了");subject.notifyObservers();
特点
使用观察者模式的动机是什么呢?
-
将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。
-
当一个对象的改变需要同时改变其他对象时,而它不知道具体有多少个对象有待改变时,应该考虑使用观察者模式。
-
抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。
-
观察者模式所作的工作其实就是在解耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。