1.观察者模式介绍

  观察者模式又叫发布-订阅模式,它定义了对象间的一种一对多关系,当一个对象的状态发生改变时,所有依赖于它的对象都会收到通知并被自动更新。观察者模式就四个角色:抽象主题,具体主题,抽象观察者,具体观察者。抽象主题是一个抽象的接口或者抽象类,对主题的功能进行抽象,抽象观察者对具体的观察者进行抽象。观察者模式在软件开发中的应用十分广泛,如微信订阅号、微博订阅等都采用了观察者模式,我们关注了某个明星的微博,当这个明星更新微博状态时我们的微博都会收到通知,明星的微博就是主题角色,我们的微博属于观察者角色。

  下边通过大话设计模式中秘书做卧底的例子来理解观察者模式的用法。上班时间有的同事喜欢偷偷看股票行情,有的同事看NBA直播,但是老板会不定时来办公室,如果被老板逮到就不好了,于是大家想出来一个办法:让秘书小妹在外边把风,如果老板来了就通知下大家,这样就不会被逮到了。这个栗子中秘书小妹就是一个主题,同事们属于观察者,秘书小妹如果看到老板过来就会通知 送她零食的同事们。代码比较简单:

   //抽象主题类
public interface ISubject
{ //添加观察者 送零食的加进来,老板来了通知你
void Add(Observer observer);
//删除观察者 不送零食的秘书小妹就不通知了
void Remove(Observer observer);
//主题状态
string SubjectState { get; set; }
//通知方法
void Notify();
} //具体主题 ,秘书类
public class Mishu : ISubject
{
//秘书要知道通知哪些同事
private IList<Observer> observers = new List<Observer>(); public void Add(Observer observer)
{
observers.Add(observer);
}
public void Remove(Observer observer)
{
observers.Remove(observer);
}
public string SubjectState { get; set; }
public void Notify()
{
foreach (Observer o in observers)
{
o.Update();
}
}
}
//抽象观察者
public abstract class Observer
{
//名字
protected string name;
//观察者要知道自己订阅了那个主题
protected ISubject sub;
public Observer(string name, ISubject sub)
{
this.name = name;
this.sub = sub;
}
//接受到通知后的更新方法
public abstract void Update();
}
//看股票的同事
public class StockObserver : Observer
{
public StockObserver(string name, ISubject sub) : base(name, sub) { }
public override void Update()
{
Console.WriteLine($"通知内容:{sub.SubjectState},反应:{name}关闭股票行情,继续工作!");
}
}
//看NBA的同事
public class NBAObserver : Observer
{
public NBAObserver(string name, ISubject sub) : base(name, sub) { }
public override void Update()
{
Console.WriteLine($"通知内容:{sub.SubjectState},反应:{name}关闭NBA直播,继续工作!");
}
} /// <summary>
/// 客户端调用
/// </summary>
class Program
{
static void Main(string[] args)
{
Mishu mishu = new Mishu();
//新建同事 观察者角色
Observer tongshi1 = new StockObserver("巴菲特", mishu);
Observer tongshi2 = new NBAObserver("麦迪", mishu);
//秘书小妹要知道哪些同事要通知(主题要知道所有订阅了自己的观察者)
mishu.Add(tongshi1);
mishu.Add(tongshi2);
//主题状态更改了
mishu.SubjectState = "老板回来了!";
//调用主题的通知方法
mishu.Notify();
Console.ReadKey();
}
}

运行程序,执行结果如下:

  上边的例子中秘书小妹充当主题角色,上班偷懒的同事充当观察者角色,通过观察者模式实现了功能。但是这里还有一个小问题:上边例子能够成功的前提是所有观察者的角色都有一个Update()方法来执行更新。但是有时候各个观察者并不都是相同的类型,如观察者1收到通知执行Update1()方法,而观察者2收到通知执行的是Update2()方法,这时候采用上边的模式就不能满足需求了。怎么改进呢?①各个观察者不属于同一类,所以不需要抽象观察者类了 ②因为各个观察者的反应不是同一的Update(),所以我们不能foreach遍历观察者集合来统一调用Update()方法了,这时可以考虑通过事件委托在客户端确定所有观察者的反应。改进后的代码如下:

    //抽象主题角色
public interface ISubject
{
//添加观察者
void Add(Observer observer);
//删除观察者
void Remove(Observer observer);
//主题状态
string SubjectState { get; set; }
//通知方法
void Notify();
}
//***********************1.定义一个委托
public delegate void EventHandler();
//具体主题角色 秘书类
public class Mishu : ISubject
{
public event EventHandler Update;
//存储要通知的同事
public IList<Observer> observers = new List<Observer>();
public string SubjectState { get; set; } public void Add(Observer observer)
{
observers.Add(observer);
}
public void Remove(Observer observer)
{
observers.Remove(observer);
}
//**********2.通知方法中不能通过遍历来统一调用每个观察者的Update()方法了,改成执行一个委托
public void Notify()
{
Update();
}
}
//抽象观察者角色
public abstract class Observer {
protected ISubject sub;
protected string name;
protected Observer(string name,ISubject sub)
{
this.name = name;
this.sub = sub;
} }
//具体观察者角色 看股票的同事
public class StockObserver
{
public string name;
public ISubject sub;
public StockObserver(string name,ISubject sub)
{
this.name = name;
this.sub = sub;
}
public void CloseStockMarket()
{
Console.WriteLine($"通知内容:{sub.SubjectState},反应:{name}关闭股票行情,继续工作!");
}
}
//具体观察者角色 看NBA的同事
public class NBAObserver
{
public string name;
public ISubject sub;
public NBAObserver(string name, ISubject sub)
{
this.name = name;
this.sub = sub;
}
public void CloseNBA()
{
Console.WriteLine($"通知内容:{sub.SubjectState},反应:{name}关闭NBA直播,继续工作!");
}
}
class Program
{
static void Main(string[] args)
{
Mishu mishu = new Mishu();
//观察者订阅了主题mishu
StockObserver tongshi1 = new StockObserver("巴菲特", mishu);
NBAObserver tongshi2 = new NBAObserver("麦迪", mishu);
//*******************3.将遍历观察者并调用观察者的Update(),改成了事件委托形式
mishu.Update += tongshi1.CloseStockMarket;
mishu.Update += tongshi2.CloseNBA;
//主题状态更改,并通知
mishu.SubjectState = "老板回来了!";
mishu.Notify();
Console.ReadKey();
}
}

运行结果:

  我们看到通过事件委托我们可以实现让不同的观察者调用不同的方法,当我们遇到类似这样的情况:点击一个按钮,有的页面元素显示Show(),有的隐藏Hide(),有的关闭Close()就可以采用这种模式。但是这种模式没有对具体的观察者进行抽象,如果观察者太多也会造成事件委托过于复杂。两种观察者模式的实现各有利弊,我们可以根据实际的情况来选择。

2.小结

上边栗子的类图:

观察者模式的使用场景:当一个对象的状态发生变化时,需要让其它对象知道并作出反应可以考虑观察者模式。

观察者模式的优点:想一下如果不使用观察者模式,订阅者怎么获取订阅号的更新?最直接的方法应该就是轮询了,这种方式十分浪费资源,而且获取更新也不及时。观察者模式的主要功能就是解决了这一问题。这种模式符合依赖倒置原则,同时降低了观察者和主题间的耦合,建立了一套触发机制。

C#设计模式(17)——观察者模式的更多相关文章

  1. C#设计模式(17)——观察者模式(Observer Pattern)

    一.引言 在现实生活中,处处可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友,这些都属于观察者模式的应用.在这一章将分享我对观察者模式的理解,废话不多说了,直接进入今天的主题. 二. ...

  2. 乐在其中设计模式(C#) - 观察者模式(Observer Pattern)

    原文:乐在其中设计模式(C#) - 观察者模式(Observer Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 观察者模式(Observer Pattern) 作者:weba ...

  3. 设计模式之观察者模式(Observable与Observer)

    设计模式之观察者模式(Observable与Observer) 好久没有写博客啦,之前看完了<设计模式之禅>也没有总结一下,现在回忆一下设计模式之观察者模式. 1.什么是观察者模式 简单情 ...

  4. 8.5 GOF设计模式四: 观察者模式Observer

    GOF设计模式四: 观察者模式Observer  现实中遇到的问题  当有许多不同的客户都对同一数据源感兴趣,对相同的数据有不同的处理方式,该如 何解决?5.1 定义: 观察者模式  观察者模式 ...

  5. php 设计模式之观察者模式(订阅者模式)

    php 设计模式之观察者模式 实例 没用设计模式的代码,这样的代码要是把最上面那部分也要符合要求加进来,就要修改代码,不符合宁增不改的原则 介绍 观察者模式定义对象的一对多依赖,这样一来,当一个对象改 ...

  6. [JS设计模式]:观察者模式(即发布-订阅者模式)(4)

    简介 观察者模式又叫发布---订阅模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知. 举一个现实生活中的例子,例如小 ...

  7. 实践GoF的23种设计模式:观察者模式

    摘要:当你需要监听某个状态的变更,且在状态变更时通知到监听者,用观察者模式吧. 本文分享自华为云社区<[Go实现]实践GoF的23种设计模式:观察者模式>,作者: 元闰子 . 简介 现在有 ...

  8. 17.java设计模式之观察者模式

    基本需求: 气象站可以将每天测量到的温度,湿度,气压等等,以公告的形式发布出去(比如发布到自己的网站或第三方) 需要设计开放型API,便于其他第三方也能接入气象站获取数据 提供温度.气压和湿度的接口 ...

  9. java设计模式之观察者模式

    观察者模式 观察者模式(有时又被称为发布(publish )-订阅(Subscribe)模式.模型-视图(View)模式.源-收听者(Listener)模式或从属者模式)是软件设计模式的一种.在此种模 ...

随机推荐

  1. 在搭建tesseract-OCR环境中遇到问题和反省

    Tesseract,一款由HP实验室开发由Google维护的开源OCR(Optical Character Recognition , 光学字符识别)引擎,特点是开源,免费,支持多语言,多平台. 在搭 ...

  2. Ubuntu 16.04 启用 点击Launcher图标,窗口实现最小化 功能

    安装了Ubuntu之后,要是每次都点击最小化按钮来实现窗口的最小化,操作起来很不方便,那么怎么样才能方便操作呢, Ubuntu 16.04 本身支持 点击应用程序Launcher图标实现最小化 功能, ...

  3. Docker:测试环境的准备-centos7上安装docker

    Dockers官方部署文档:https://docs.docker.com/install/linux/docker-ce/centos/ 1.建议先关闭 selinux (selinux是 linu ...

  4. ios兼容 iphoneX ios10 ios11

    假设你有一个固定位置的标题栏,你的iOS10的CSS可能是这样写的: header { position: fixed; top:; left:; right:; height: 44px; padd ...

  5. 在Windows下使用Git+TortoiseGit+码云管理项目代码

    1.      安装Git 下载地址:点击打开链接 安装指南:默认选项即可 2.      安装TortoiseGit 下载地址:点击打开链接 安装指南:点击打开链接 3.      在码云创建账号, ...

  6. web框架开发-Django用户认证组件

    可以用认证组件做什么 针对session的缺陷, 跟新数据时,不跟新key键, 用户认证组件是删除后再重建 用户认证组件很多功能可以直接使用 利用用户认证表(auth_user,通过Django自己创 ...

  7. LVS负载均衡集群

    回顾-Nginx反向代理型负载 负载均衡(load balance)集群,提供了一种廉价.有效.透明的方法,来扩展网络设备和服务器的负载.带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用 ...

  8. [LeetCode] 4. 寻找两个有序数组的中位数

    题目链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/ 题目描述: 给定两个大小为 m 和 n 的有序数组 nums1 和 ...

  9. appium设置会话时间,可以超长时。Open Application

  10. Redis原理

    RESP协议 支持tcp协议.基本数据类型,比如数组,字符串等,也可支持其他的通信场景. 模拟redis接收传输过来的set数据 //ServerSocket监听6379端口模拟redis publi ...