zoukankan      html  css  js  c++  java
  • [设计模式] 设计模式课程(四)-- 观察者模式

    场景

    • 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

  • 相关阅读:
    Spring Boot简明教程之实现Web开发及常用参数获取方式分析
    SpringBoot 简明教程之项目属性配置(三):配置文件优先级及多配置切换
    史上最简单MySQL教程详解(进阶篇)之存储过程(二)
    史上最简单MySQL教程详解(进阶篇)之存储过程(一)
    文字从中间向两边延展
    字符串转化成驼峰命名
    统计字符串字符个数
    while求和(1到100)
    for循环实现乘阶
    递归遍历所有ul下的所有子节点
  • 原文地址:https://www.cnblogs.com/cxc1357/p/12288002.html
Copyright © 2011-2022 走看看