[设计模式] 设计模式课程(四)-- 观察者模式

场景

  • 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 };
View Code

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 };
View Code
  • 文件分割器程序,对于大文件,需要进度条显示分割进度
  • 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 };
View Code

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 };
View Code
  • 添加抽象基类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)
View Code

示例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 }
View Code

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)解析例子

https://blog.51cto.com/tianli/40455

原文地址:https://www.cnblogs.com/cxc1357/p/12288002.html