场景
- Observer / Event,属于“组件协作”模式,解决了框架和应用的协作问题
- 在软件构建过程中,需要为某些对象建立一种“通知--依赖关系”
- 对象间存在一对多的依赖关系,当一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知并自动更新
- 使用面向对象技术,可将这种依赖关系弱化,形成一种稳定的依赖关系,降低软件体系的耦合
结构
- 发布者类(Publisher):向其他对象发送值得关注的事件,事件在发布者自身状态改变或执行特定行为后发生
- 订阅者接口(Subscriber):声明了通知接口
- 具体订阅者:可执行一些操作来回应发布者的通知
- 客户端:创建发布者和订阅者对象
实现
- 将业务逻辑拆分为两部分,独立于其他代码的核心功能作为发布者,其他代码作为订阅类
- 声明订阅者接口,接口至少声明一个update方法
- 声明发布者接口并定义一些接口在列表中添加和删除订阅对象,发布者仅通过订阅者接口与他们进行交互
- 确定存放实际订阅表的位置,实现订阅方法
- 创建具体发布者类
- 在具体订阅者类中实现通知更新的方法,通过通知方法的参数传递订阅者需要的上下文数据
- 在客户端生成所需的全部订阅者,并在发布者处完成注册工作
场景
- 定义对象间的一对多关系,一个对象发生改变时,所有依赖它的对象都得到通知并被自动更新
示例1
MainForm1.cpp
1 class MainForm : public Form 2 { 3 TextBox* txtFilePath; 4 TextBox* txtFileNumber; 5 ProgressBar* progressBar; 6 7 public: 8 void Button1_Click(){ 9 10 string filePath = txtFilePath->getText(); 11 int number = atoi(txtFileNumber->getText().c_str()); 12 13 FileSplitter splitter(filePath, number, progressBar); 14 15 splitter.split(); 16 17 } 18 };
FileSplitter1.cpp
1 class FileSplitter 2 { 3 string m_filePath; 4 int m_fileNumber; 5 ProgressBar* m_progressBar; 6 7 public: 8 FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) : 9 m_filePath(filePath), 10 m_fileNumber(fileNumber), 11 m_progressBar(progressBar){ 12 } 13 14 void split(){ 15 16 //1.读取大文件 17 18 //2.分批次向小文件中写入 19 for (int i = 0; i < m_fileNumber; i++){ 20 //... 21 float progressValue = m_fileNumber; 22 progressValue = (i + 1) / progressValue; 23 m_progressBar->setValue(progressValue); 24 } 25 26 } 27 };
- 文件分割器程序,对于大文件,需要进度条显示分割进度
- MainForm1中收集用户输入的两个参数,传递给FIleSplitter1
- 问题:违反了依赖倒置原则。实现(FileSplitter)依赖细节(ProgressBar),而细节是容易变化的(比如后期改用一个Label展示进度,或在Linux平台没有图形界面,用...表示进度)
- 依赖:指编译层面的依赖(A依赖B,A编译的时候需要B才能编译通过)
- 不要依赖细节,而是依赖它的抽象
- ProgressBar是一个具体通知控件,是否可用抽象方式(接口 IProgress)表示
MainForm2.cpp
1 class MainForm : public Form, public IProgress 2 { 3 TextBox* txtFilePath; 4 TextBox* txtFileNumber; 5 6 ProgressBar* progressBar; 7 8 public: 9 void Button1_Click(){ 10 11 string filePath = txtFilePath->getText(); 12 int number = atoi(txtFileNumber->getText().c_str()); 13 14 ConsoleNotifier cn; 15 16 FileSplitter splitter(filePath, number); 17 18 splitter.addIProgress(this); //订阅通知 19 splitter.addIProgress(&cn); //订阅通知 20 21 splitter.split(); 22 23 splitter.removeIProgress(this); 24 25 } 26 27 virtual void DoProgress(float value){ 28 progressBar->setValue(value); 29 } 30 }; 31 32 class ConsoleNotifier : public IProgress { 33 public: 34 virtual void DoProgress(float value){ 35 cout << "."; 36 } 37 };
FileSplitter2.cpp
1 class IProgress{ 2 public: 3 virtual void DoProgress(float value)=0; 4 virtual ~IProgress(){} 5 }; 6 7 class FileSplitter 8 { 9 string m_filePath; 10 int m_fileNumber; 11 12 List<IProgress*> m_iprogressList; // 抽象通知机制,支持多个观察者 13 14 public: 15 FileSplitter(const string& filePath, int fileNumber) : 16 m_filePath(filePath), 17 m_fileNumber(fileNumber){ 18 19 } 20 21 void split(){ 22 23 //1.读取大文件 24 25 //2.分批次向小文件中写入 26 for (int i = 0; i < m_fileNumber; i++){ 27 //... 28 29 float progressValue = m_fileNumber; 30 progressValue = (i + 1) / progressValue; 31 onProgress(progressValue);//发送通知 32 } 33 34 } 35 36 37 void addIProgress(IProgress* iprogress){ 38 m_iprogressList.push_back(iprogress); 39 } 40 41 void removeIProgress(IProgress* iprogress){ 42 m_iprogressList.remove(iprogress); 43 } 44 45 protected: 46 virtual void onProgress(float value){ 47 48 List<IProgress*>::iterator itor=m_iprogressList.begin(); 49 50 while (itor != m_iprogressList.end() ) 51 (*itor)->DoProgress(value); //更新进度条 52 itor++; 53 } 54 } 55 };
- 添加抽象基类IProgress,由原先具体的通知控件变为抽象的通知机制
- c++支持多继承,但会导致耦合性问题,一般不推荐使用
- 只有推荐在一种情况下用,即父类中一个是主继承类(Form),其他都是接口或抽象基类(IProgress)
- 重构后,FileSplitter类不再耦合界面类(ProgressBar),实现了独立编译,符合了依赖倒置原则
- 将来可以放在Windows界面,或Linux界面运行,进度通知功能依赖抽象的IProgress完成
- 形式上,DoProgress()从FileSplitter1.cpp的23行,挪到了MainForm2.cpp的28行
- 利用容器,支持多个观察者(命令行,GUI等),FileSplitter 类构造函数的写法发生变化,并增加相应addIProgress(),removeIProgress(),改写onProgress()支持容器,在MainForm中增加订阅通知操作
- 观察者自己决定是否订阅通知(MainForm2 18-19),目标对象对此一无所知
- Subject和ConcreteSubject相当于FileSplitter(目标对象),Attach()相当于addIProgress(),Notify()相当于onProgress()
- Observer相当于IProgress(抽象观察者),Update相当于DoProgress()
- ConcreteObserver相当于MainForm和ConsoleNotifier(具体观察者)
- Subject和Observer是稳定的,ConcreteSubject和ConcreteObserver是变动的
- Observer模式使我们可以独立地改变目标与观察者,从而使二者的依赖关系为松耦合
- 目标发送通知时,无需指定观察者,通知会自动传播(不知道谁是观察者)
- Observer 模式是基于事件UI框架中非常常用的模式,是MVC模式的重要组成部分
- 重构过程
- ProgressBar* m_progressBar; // 具体通知控件
- IProgress* m_iprogress; // 抽象通知机制
- List<IProgress*> m_iprogressList; // 支持多个观察者
示例2
1 // 发布者基类包含订阅管理代码和通知方法。 2 class EventManager is 3 private field listeners: hash map of event types and listeners 4 5 method subscribe(eventType, listener) is 6 listeners.add(eventType, listener) 7 8 method unsubscribe(eventType, listener) is 9 listeners.remove(eventType, listener) 10 11 method notify(eventType, data) is 12 foreach (listener in listeners.of(eventType)) do 13 listener.update(data) 14 15 // 具体发布者包含一些订阅者感兴趣的实际业务逻辑。我们可以从发布者基类中扩 16 // 展出该类,但在实际情况下并不总能做到,因为具体发布者可能已经是子类了。 17 // 在这种情况下,你可用组合来修补订阅逻辑,就像我们在这里做的一样。 18 class Editor is 19 public field events: EventManager 20 private field file: File 21 22 constructor Editor() is 23 events = new EventManager() 24 25 // 业务逻辑的方法可将变化通知给订阅者。 26 method openFile(path) is 27 this.file = new File(path) 28 events.notify("open", file.name) 29 30 method saveFile() is 31 file.write() 32 events.notify("save", file.name) 33 34 // ... 35 36 37 // 这里是订阅者接口。如果你的编程语言支持函数类型,则可用一组函数来代替整 38 // 个订阅者的层次结构。 39 interface EventListener is 40 method update(filename) 41 42 // 具体订阅者会对其注册的发布者所发出的更新消息做出响应。 43 class LoggingListener implements EventListener is 44 private field log: File 45 private field message 46 47 constructor LoggingListener(log_filename, message) is 48 this.log = new File(log_filename) 49 this.message = message 50 51 method update(filename) is 52 log.write(replace('%s',filename,message)) 53 54 class EmailAlertsListener implements EventListener is 55 private field email: string 56 57 constructor EmailAlertsListener(email, message) is 58 this.email = email 59 this.message = message 60 61 method update(filename) is 62 system.email(email, replace('%s',filename,message)) 63 64 65 // 应用程序可在运行时配置发布者和订阅者。 66 class Application is 67 method config() is 68 editor = new TextEditor() 69 70 logger = new LoggingListener( 71 "/path/to/log.txt", 72 "有人打开了文件:%s"); 73 editor.events.subscribe("open", logger) 74 75 emailAlerts = new EmailAlertsListener( 76 "admin@example.com", 77 "有人更改了文件:%s") 78 editor.events.subscribe("save", emailAlerts)
示例3
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class Subject { 5 private List<Observer> observers = 6 new ArrayList<Observer>(); 7 private int state; 8 9 public int getState() { 10 return state; 11 } 12 13 public void setState(int state) { 14 this.state = state; 15 notifyAllObservers(); 16 } 17 18 public void attach(Observer observer) { 19 observers.add(observer); 20 } 21 22 public void notifyAllObservers() { 23 for(Observer observer:observers) { 24 observer.update(); 25 } 26 } 27 } 28 29 public abstract class Observer { 30 protected Subject subject; 31 public abstract void update(); 32 } 33 34 public class BinaryObserver extends Observer{ 35 36 public BinaryObserver(Subject subject) { 37 this.subject = subject; 38 this.subject.attach(this); 39 } 40 41 @Override 42 public void update() { 43 System.out.println("Binary String: " + 44 Integer.toBinaryString(subject.getState())); 45 } 46 } 47 48 public class OctalObserver extends Observer{ 49 50 public OctalObserver(Subject subject){ 51 this.subject = subject; 52 this.subject.attach(this); 53 } 54 55 @Override 56 public void update() { 57 System.out.println( "Octal String: " 58 + Integer.toOctalString( subject.getState() ) ); 59 } 60 } 61 62 public class HexaObserver extends Observer{ 63 64 public HexaObserver(Subject subject){ 65 this.subject = subject; 66 this.subject.attach(this); 67 } 68 69 @Override 70 public void update() { 71 System.out.println( "Hex String: " 72 + Integer.toHexString( subject.getState() ).toUpperCase() ); 73 } 74 } 75 76 public class ObserverPatternDemo { 77 public static void main(String[] args) { 78 Subject subject = new Subject(); 79 80 new HexaObserver(subject); 81 new OctalObserver(subject); 82 new BinaryObserver(subject); 83 84 System.out.println("First state change: 15"); 85 subject.setState(15); 86 System.out.println("Second state change: 10"); 87 subject.setState(10); 88 } 89 }
First state change: 15
Hex String: F
Octal String: 17
Binary String: 1111
Second state change: 10
Hex String: A
Octal String: 12
Binary String: 1010
参考
观察者模式(Observer)解析例子