设计模式学习笔记-观察者模式

1. 概述

  有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

2. 解决的问题

  将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。

3. 模式中的角色

  3.1 抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

  3.2 具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。

  3.3 抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。

  3.4 具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。

4. 模式解读

  4.1 观察者模式的类图  

  

  4.2 观察者模式的代码

复制代码
    /// <summary>/// 抽象主题类/// </summary>public abstract class Subject{private IList<Observer> observers = new List<Observer>();/// <summary>/// 增加观察者/// </summary>/// <param name="observer"></param>public void Attach(Observer observer){observers.Add(observer);}/// <summary>/// 移除观察者/// </summary>/// <param name="observer"></param>public void Detach(Observer observer){observers.Remove(observer);}/// <summary>/// 向观察者(们)发出通知/// </summary>public void Notify(){foreach (Observer o in observers){o.Update();}}}/// <summary>/// 抽象观察者类,为所有具体观察者定义一个接口,在得到通知时更新自己/// </summary>public abstract class Observer{public abstract void Update();}/// <summary>/// 具体观察者或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。/// </summary>public class ConcreteSubject : Subject{private string subjectState;/// <summary>/// 具体观察者的状态/// </summary>public string SubjectState{get { return subjectState; }set { subjectState = value; }}}/// <summary>/// 具体观察者,实现抽象观察者角色所要求的更新接口,已是本身状态与主题状态相协调/// </summary>public class ConcreteObserver : Observer{private string observerState;private string name;private ConcreteSubject subject;/// <summary>/// 具体观察者用一个具体主题来实现/// </summary>public ConcreteSubject Subject{get { return subject; }set { subject = value; }}public ConcreteObserver(ConcreteSubject subject, string name){this.subject = subject;this.name = name;}/// <summary>/// 实现抽象观察者中的更新操作/// </summary>public override void Update(){observerState = subject.SubjectState;Console.WriteLine("The observer's state of {0} is {1}", name, observerState);}}
复制代码

  4.3 客户端代码

复制代码
    class Program{static void Main(string[] args){// 具体主题角色通常用具体自来来实现ConcreteSubject subject = new ConcreteSubject();subject.Attach(new ConcreteObserver(subject, "Observer A"));subject.Attach(new ConcreteObserver(subject, "Observer B"));subject.Attach(new ConcreteObserver(subject, "Observer C"));subject.SubjectState = "Ready";subject.Notify();Console.Read();}}
复制代码

  运行结果

  

5. 模式总结

  5.1 优点

    5.1.1 观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。

  5.2 缺点

    5.2.1 依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。

  5.3 适用场景

    5.3.1 当一个对象的改变需要给变其它对象时,而且它不知道具体有多少个对象有待改变时。

    5.3.2 一个抽象某型有两个方面,当其中一个方面依赖于另一个方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。

 

6. 模式引申,应用C#中的事件委托来彻底解除通知者和观察者之间的耦合。

   6.1 关于委托的定义:委托是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法有相同的行为。委托方法可以像其它任何方法一样,具有参数和返回值。委托可以看作是对函数(方法)的的抽象,是函数的“类”,委托的实例代表一个(或多个)具体的函数,它可以是多播的。

   6.2 关于事件:事件基于委托,为委托提供了一种发布/订阅机制。事件的订阅与取消与我们刚才讲的观察者模式中的订阅与取消类似,只是表现形式有所不同。在观察者模式中,订阅使用方法Attach()来进行;在事件的订阅中使用“+=”。类似地,取消订阅在观察者模式中用Dettach(),而事件的取消用“-=”。

 

7. 下面例子分别用观察者模式,事件机制来实现

  7.1 实例描述:客户支付了订单款项,这时财务需要开具发票,出纳需要记账,配送员需要配货。

  7.2 观察者模式的实现

    7.2.1 类图

    

    7.2.2 代码实现

复制代码
    /// <summary>/// 抽象观察者/// </summary>public interface ISubject{void Notify();}/// <summary>/// 工作岗位,作为这里的观察者的抽象/// </summary>public abstract class JobStation{public abstract void Update();}/// <summary>/// 具体主题,这里是客户/// </summary>public class Customer : ISubject{private string customerState;private List<JobStation> observers = new List<JobStation>();/// <summary>/// 增加观察者/// </summary>/// <param name="observer"></param>public void Attach(JobStation observer){this.observers.Add(observer);}/// <summary>/// 移除观察者/// </summary>/// <param name="observer"></param>public void Detach(JobStation observer){this.observers.Remove(observer);}/// <summary>/// 客户状态/// </summary>public string CustomerState{get { return customerState; }set { customerState = value; }}public void Notify(){foreach (JobStation o in observers){o.Update();}}}/// <summary>/// 会计/// </summary>public class Accountant : JobStation{private string accountantState;private Customer customer;public Accountant(Customer customer){this.customer = customer;}/// <summary>/// 更新状态/// </summary>public override void Update(){if (customer.CustomerState == "已付款"){Console.WriteLine("我是会计,我来开具发票。");accountantState = "已开发票";}}}/// <summary>/// 出纳/// </summary>public class Cashier : JobStation{private string cashierState;private Customer customer;public Cashier(Customer customer){this.customer = customer;}public override void Update(){if (customer.CustomerState == "已付款"){Console.WriteLine("我是出纳员,我给登记入账。");cashierState = "已入账";}}}/// <summary>/// 配送员/// </summary>public class Dilliveryman : JobStation{private string dillivierymanState;private Customer customer;public Dilliveryman(Customer customer){this.customer = customer;}public override void Update(){if (customer.CustomerState == "已付款"){Console.WriteLine("我是配送员,我来发货。");dillivierymanState = "已发货";}}}
复制代码

    7.2.3 客户端代码

复制代码
    class Program{static void Main(string[] args){Customer subject = new Customer();subject.Attach(new Accountant(subject));subject.Attach(new Cashier(subject));subject.Attach(new Dilliveryman(subject));subject.CustomerState = "已付款";subject.Notify();Console.Read();}}
复制代码

    运行结果:

    我是会计,我来开具发票。
    我是出纳员,我给登记入账。
    我是配送员,我来发货。

 

  7.3 事件实现

    7.3.1 类图

    

    通过类图来看,观察者和主题之间已经不存在任何依赖关系了。

    7.3.2 代码实现

    

复制代码
    /// <summary>/// 抽象主题/// </summary>public interface ISubject{void Notify();}/// <summary>/// 声明委托/// </summary>public delegate void CustomerEventHandler();/// <summary>/// 具体主题/// </summary>public class Customer : ISubject{private string customerState;// 声明一个委托事件,类型为 CustomerEventHandlerpublic event CustomerEventHandler Update;public void Notify(){if (Update != null){// 使用事件来通知给订阅者Update();}}public string CustomerState{get { return customerState; }set { customerState = value; }}}/// <summary>/// 财务,已经不需要实现抽象的观察者类,并且不用引用具体的主题/// </summary>public class Accountant{private string accountantState;public Accountant(){ }/// <summary>/// 开发票/// </summary>public void GiveInvoice(){Console.WriteLine("我是会计,我来开具发票。");accountantState = "已开发票";}}/// <summary>/// 出纳,已经不需要实现抽象的观察者类,并且不用引用具体的主题/// </summary>public class Cashier{private string cashierState;public void Recoded(){Console.WriteLine("我是出纳员,我给登记入账。");cashierState = "已入账";}}/// <summary>/// 配送员,已经不需要实现抽象的观察者类,并且不用引用具体的主题/// </summary>public class Dilliveryman{private string dillivierymanState;public void Dilliver(){Console.WriteLine("我是配送员,我来发货。");dillivierymanState = "已发货";}}
复制代码

    7.3.3 客户端代码

复制代码
    class Program{static void Main(string[] args){Customer subject = new Customer();Accountant accountant = new Accountant();Cashier cashier = new Cashier();Dilliveryman dilliveryman = new Dilliveryman();// 注册事件subject.Update += accountant.GiveInvoice;subject.Update += cashier.Recoded;subject.Update += dilliveryman.Dilliver;/** 以上写法也可以用下面代码来替换subject.Update += new CustomerEventHandler(accountant.GiveInvoice);subject.Update += new CustomerEventHandler(cashier.Recoded);subject.Update += new CustomerEventHandler(dilliveryman.Dilliver);*/subject.CustomerState = "已付款";subject.Notify();Console.Read();}}
复制代码

    运行结果

    我是会计,我来开具发票。
    我是出纳员,我给登记入账。
    我是配送员,我来发货。

转载于:https://www.cnblogs.com/Free-Thinker/p/4730227.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/273053.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Handler与多线程

1、Handler介绍 在Android开发中&#xff0c;我们常会使用单独的线程来完成某些操作&#xff0c;比如用一个线程来完成从网络上下的图片&#xff0c;然后显示在一个ImageView上&#xff0c;在多线程操作时&#xff0c;Android中必须保证以下两点&#xff1a; &#xff08;1&…

oracle语法官方文档,Oracle官方文档必备语法知识

很多Oracle DBA虽然接触Oracle时间很长&#xff0c;但是一旦想不起语法或找不出相应参数时&#xff0c;习惯百度或谷歌。虽然已经下载了官方文档&#xff0c;但是Oracle官方文档必备语法知识[日期&#xff1a;2015-04-21]来源&#xff1a;Linux社区作者&#xff1a;kuqlan[字体…

新中大oracle实列名,新中大财务软件操作流程(完整版)

新中大财务软件最基本的三个模块&#xff1a;核算单位、财务处理系统、报表处理系统。简单地说&#xff0c;核算单位模块是用于建账&#xff0c;财务处理系统用于登账&#xff0c;报表处理系统用于出报表的。一、总账处理系统1、建账套双击财务软件图标 → 在登录界面选择用户编…

linux切换任务命令,Linux top详解之交互命令、命令行选项

top交互命令我们之前说过top是一个交互命令。上一节我们已经遇到了一些命令。这里我们会探索更多的命令。2.1 ‘h’: 帮助首先&#xff0c;我们可以用’h’或者’?’显示交互命令的帮助菜单。2.2 “或者”: 刷新显示top命令默认在一个特定间隔(3秒)后刷新显示。要手动刷新&am…

linux系统硬盘设置密码,LUKS:Linux下磁盘加密

Linux下磁盘加密LUKS(Linux Unified Key Setup)为Linux硬盘加密提供了一种标准&#xff0c;它不仅能通用于不同的Linux发行版本&#xff0c;还支持多用户/口令。因为它的加密密钥独立于口令&#xff0c;所以如果口令失密&#xff0c;我们可以迅速改变口令而无需重新加密真个硬盘…

Hibernate查询

9.1 Hibernate数据查询 数据查询与检索是Hibernate的一个亮点。Hibernate的数据查询方式主要有3种&#xff0c;它们是&#xff1a; l Hibernate Query Language&#xff08;HQL&#xff09; l Criteria Query l Native SQL 下面对这3种查询方式分别进…

单例模式 创建对象

两种选择 1 使用pthread_once&#xff0c; once是类的成员变量 只执行一次Create create的作用是创建一个对象 2 使用 static lock 如下所示&#xff0c;注意lock必须是static的&#xff0c;否则是局部变量&#xff0c;每个线程都有自己的lock&#xff0c;无法保证只执行一次。…

opencv配置

OpenCV的简单安装和一次性配置在这里就不赘述了&#xff0c;网上教程很多&#xff0c;可以参考一下这个链接里面的教程http://wenku.baidu.com/view/3b40de25453610661ed9f46b.html。 但是很多情况下面&#xff0c;我们新建一个项目就要重新配置一次OpenCV&#xff0c;那就相当…

linux 动态执行cp,Linux常用命令之cp、mv、rm、cat、more、head、tail、ln命令讲解

上一章节中&#xff0c;我们了解到了Linux系统的最基础的几个文件处理命令&#xff0c;核心的是ls命令&#xff0c;在今天这章中&#xff0c;我们来继续学习Linux对于文件操作相关的一些命令&#xff0c;比如复制、移动、删除、查看等命令。1、cp 命令解释命令名称&#xff1a;…

Linux鼠标回报率修改,鼠标回报率怎么调? 设置鼠标回报率的三种方法

鼠标回报率如何设置呢&#xff1f;鼠标回报率又称刷新率&#xff0c;是指鼠标MCU与电脑传输数据频率。鼠标回报率对于游戏玩家而言至关重要&#xff0c;但同时鼠标回报率与电脑性能息息相关。只有电脑硬件性能良好&#xff0c;才能适当提升鼠标回报率&#xff0c;以实现更高的鼠…

[转载]孙婧妍:高考语文148分是这样炼成的(附:孙婧妍

原文地址&#xff1a;孙婧妍&#xff1a;高考语文148分是这样炼成的(附&#xff1a;孙婧妍2013高考作文《手机论》)作者&#xff1a; 语文新高考高考语文148分是这样炼成的 (附&#xff1a;孙婧妍2013高考作文《手机论》) 来源&#xff1a;网络 作者&#xff1a;孙婧妍…

c语言字符串逆置,字符串逆置

满意答案9n7j5j3m4o2013.12.03采纳率&#xff1a;49% 等级&#xff1a;11已帮助&#xff1a;15198人47911 zxl0714 1358 Accepted 164K 15MS G 0.46K 2007-04-08 10:32:38#include using namespace std;void reverse(char* ch){int i, len;char tmp;len strlen( ch );for (…

【Android基础】Fragment 详解之Fragment介绍

Fragment在Android 3.0&#xff08; API 11&#xff09;引入&#xff0c;是为了支持在大屏上显示更加动态、灵活的UI&#xff0c;比如在平板和电视上。Fragment可以看作是嵌套的Activity&#xff0c;类似ActivityGroup&#xff0c;但是开销肯定没有ActivityGroup那么大&#xf…

linux postfix 搭建,linux 下搭建postfix服务器

linux 下postfix邮箱的安装linux一、首先关闭sendmail服务service sendmail stop二、chkconfig sendmail off(关闭开机自启动)三、修改DNS正解文件&#xff0c;使DNS可以解析邮箱服务添加下面两行mail.zhubf.com. IN A 172.17.17.2zhubf.com. IN MX 10 …

android 飞框动画,AndroidTV中实现飞框选中效果

相信很多从事AndroidTV开发的朋友都对如何展示item的选中效果感到苦恼&#xff0c;电视端开发与移动端最大的不同是用户只能通过一个遥控器进行控制(当然如果你的电视是触屏的话除外……)&#xff0c;在这个时候&#xff0c;我们需要让用户知道当前选中的到底是哪一个项目&…

鸿蒙系统支持980,鸿蒙手机上线时间 鸿蒙系统支持哪些手机2021最新汇总

鸿蒙手机来了&#xff0c;从2019年公布到现在的正式发布&#xff0c;没想到华为这么迅速&#xff0c;而且华为EMUI微博更名HarmonyOS&#xff0c;在Android与iOS这两座大山面前&#xff0c;大家觉得鸿蒙系统值得更新体验吗&#xff1f;目前来说鸿蒙系统支持第三方手机有哪些呢&…

confluence正常安装网页报错_NAS折腾手记1:在OMV5上安装ZFS On Linux的正确步骤

起因是直接安装OVMExtra里自带的zfs插件会报错&#xff0c;所以需要使用命令行来做一些前置准备。源配置有两种方法。1是安装OMVExtra并在内直接启用所有测试源下载地址在此​omv-extras.org2是手动添加&#xff0c;执行以下命令vi /etc/apt/sources.list.d/buster-backports.l…

android sdk eclipse没导入,Android—新的eclipse导入SDK出错解决办法

原先系统崩溃&#xff0c;重装系统&#xff0c;加入一块内存条&#xff0c;从32位变成62位&#xff0c;原先的eclipse用不了&#xff1b;去官网下载64位的eclipse&#xff0c;安装&#xff0c;用一样的方法导入SDK。这时候肯定会提示错误&#xff0c;如下&#xff1a;1.This An…

两个分数化简比怎么化_我学《分数的意义》心得

停课不停学已经有将近两个月了&#xff0c;我们迈入了“分数”这一部分。听妈妈说&#xff0c;这一块内容很重要&#xff0c;可我觉得到目前为止(明天就学真分数、假分数和带分数了)&#xff0c;分数好像并不比四年级难。看了看书&#xff0c;再做点练习&#xff0c;把这点新的…

html在线拖拽环绕,jQuery实现html元素拖拽

代码很简单&#xff0c;效果非常棒&#xff0c;直接给大家上源码&#xff1a;html定投金额 :元10050010002000300040005000600070008000900010000单位:元css.money-input{margin:36px auto 0;width:330px;font-size:14px;color:#818181}.input-rela{width:250px;height:42px;di…