C#设计模式(17)——观察者模式
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)——观察者模式的更多相关文章
- C#设计模式(17)——观察者模式(Observer Pattern)
一.引言 在现实生活中,处处可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友,这些都属于观察者模式的应用.在这一章将分享我对观察者模式的理解,废话不多说了,直接进入今天的主题. 二. ...
- 乐在其中设计模式(C#) - 观察者模式(Observer Pattern)
原文:乐在其中设计模式(C#) - 观察者模式(Observer Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 观察者模式(Observer Pattern) 作者:weba ...
- 设计模式之观察者模式(Observable与Observer)
设计模式之观察者模式(Observable与Observer) 好久没有写博客啦,之前看完了<设计模式之禅>也没有总结一下,现在回忆一下设计模式之观察者模式. 1.什么是观察者模式 简单情 ...
- 8.5 GOF设计模式四: 观察者模式Observer
GOF设计模式四: 观察者模式Observer 现实中遇到的问题 当有许多不同的客户都对同一数据源感兴趣,对相同的数据有不同的处理方式,该如 何解决?5.1 定义: 观察者模式 观察者模式 ...
- php 设计模式之观察者模式(订阅者模式)
php 设计模式之观察者模式 实例 没用设计模式的代码,这样的代码要是把最上面那部分也要符合要求加进来,就要修改代码,不符合宁增不改的原则 介绍 观察者模式定义对象的一对多依赖,这样一来,当一个对象改 ...
- [JS设计模式]:观察者模式(即发布-订阅者模式)(4)
简介 观察者模式又叫发布---订阅模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知. 举一个现实生活中的例子,例如小 ...
- 实践GoF的23种设计模式:观察者模式
摘要:当你需要监听某个状态的变更,且在状态变更时通知到监听者,用观察者模式吧. 本文分享自华为云社区<[Go实现]实践GoF的23种设计模式:观察者模式>,作者: 元闰子 . 简介 现在有 ...
- 17.java设计模式之观察者模式
基本需求: 气象站可以将每天测量到的温度,湿度,气压等等,以公告的形式发布出去(比如发布到自己的网站或第三方) 需要设计开放型API,便于其他第三方也能接入气象站获取数据 提供温度.气压和湿度的接口 ...
- java设计模式之观察者模式
观察者模式 观察者模式(有时又被称为发布(publish )-订阅(Subscribe)模式.模型-视图(View)模式.源-收听者(Listener)模式或从属者模式)是软件设计模式的一种.在此种模 ...
随机推荐
- c/c++浮点数在内存中存储方式
转自:https://www.cnblogs.com/dolphin0520/archive/2011/10/02/2198280.html 任何数据在内存中都是以二进制的形式存储的,例如一个shor ...
- jQuery如何制作动画
下面为一组图片(四张)展示 1 2 3 4 页面代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN& ...
- Ubuntu系统分配存储空间的建议以及给Ubuntu系统根目录扩容方法(从20GB追加100GB)
当初准备装双系统时,也思考了很久分配多少空间给Ubuntu16.04系统,查了许多资料,大多意思是‘/’目录总共给20GB,其他的给/home.网上资料推荐的大多跟这篇文章一样:https://blo ...
- Configuring Apache Kafka for Performance and Resource Management
Apache Kafka is optimized for small messages. According to benchmarks, the best performance occurs w ...
- jenkins之Job建立-运行 git 脚本
新建一个自由风格的项目,运行git脚本 1.点击菜单栏中的“新任务” 2.进入该页面后输入一个项目名称,然后选择“构建一个自由风格的软件项目”,滑动到最底端,点击ok(在左下角) 3.进入下图页面后 ...
- MySQL之记录相关操作
一 介绍 MySQL数据操作: DML ======================================================== 在MySQL管理软件中,可以通过SQL语句中的 ...
- Linux内存管理 (19)总结内存管理数据结构和API
专题:Linux内存管理专题 关键词:mm.vaddr.VMA.page.pfn.pte.paddr.pg_data.zone.mem_map[]. 1. 内存管理数据结构的关系图 在大部分Linux ...
- 偶现bug如何处理?
请先允许我对此类bug进行吐槽,相信做测试的同学都碰见过这种bug! 我们在测试过程中经常会碰见一类很头疼的bug,就是偶现性的bug,所谓偶现性,是相对于必现而言,这类bug有些可以有重现路径,但是 ...
- C# 递归构造树状数据结构(泛型),如何构造?如何查询?
十年河东,十年河西,莫欺少年穷. 学无止境,精益求精 难得有清闲的一上午,索性写篇博客. 首先,我们需要准备一张表,如下范例: create table TreeTable ( TreeId ) no ...
- EQueue
EQueue 2.3.2版本发布(支持高可用) - dotNET跨平台 - CSDN博客https://blog.csdn.net/sD7O95O/article/details/78097193 E ...