设计模式学习(二): 观察者模式 (C#)
《深入浅出设计模式》学习笔记第二章
需求:
开发一套气象监测应用,如图:
气象站,目前有三种装置,温度、湿度和气压感应装置。
WeatherData对象追踪气象站的数据,并更新到布告板,布告板(目前是三个:目前状况、气象统计、天气预报)用来显示目前的天气状况给用户。
初步设计
目前的要求:
1.其中有三个方法分别获得气温、湿度和气压的数据。
2.一旦气象测量被更新,那么这个measurementsChanged()方法就会被调用。
3.一旦有新的数据,者三个布告板(暂时三个)就会马上更新。
4.可扩展,可以开发第三方的布告板。
错误的实现:
错误在哪:
1.变化的地方需要封装。
2.布告板应该统一实现某个带有update方法的接口。
3.不应该针对实现编程,应该针对接口编程。
什么是观察者模式 Observer Pattern
例子:
我们订阅公众号,公众号一旦有新文章就会发送给我们。
当我不再想看文章时,就取消订阅,这时就不会给我发送文章了。
只要公众号还在运营,就一直有人订阅或者取消订阅。
出版者(Publishers) + 订阅者(Subscribers) = 观察者模式(Observer Pattern)
不过我们用的名词不一样,出版者改为主题(Subject),订阅者改为观察者(Observer)
观察者模式定义:
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者(dependents)都会收到通知并自动更新。
观察这模式关系图:
松耦合
两个对象之间的松耦合就是,他们可以交互,但是不清楚对方太多细节。
观察者模式的Subject与Observers之间是松耦合,因为:
1.Subject对于Observer知道的唯一一件事情就是它实现了某个接口。
2.随时可以添加新的Observer。
3.新的Observer出现时,永远不需要更改Subject的代码。
4.我们可以独立的复用Subject或Observer。
5.改变任意的一方并不会影响另外一方。
设计原则
交互对象之间应该尽可能的去实现松耦合设计。
气象应用的具体设计:
C#实现:
接口们:
namespace C02ObserverPattern.Raw.Bases
{
public interface ISubject
{
// 订阅
void RegisterObserver(IObserver observer);
// 取消订阅
void RemoveObserver(IObserver observer);
// 状态变化时,通知所有观察者
void NotifyObservers();
}
public interface IObserver
{
// 气象之变化时,subject会把这些值更新给observers
void Update(float temp, float humidity, float pressure);
}
public interface IDisplayElement
{
// 布告板显示
void Display();
}
}
WeatherData(subject):
namespace C02ObserverPattern.Raw
{
public class WeatherData: ISubject
{
private readonly HashSet<IObserver> _observers;
private float _temp, _humidity, _pressure;
public WeatherData()
{
_observers = new HashSet<IObserver>();
}
public void RegisterObserver(IObserver observer)
{
_observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
public void NotifyObservers()
{
foreach (var observer in _observers)
{
observer.Update(_temp, _humidity, _pressure);
}
}
public void MeasurementsChanged()
{
NotifyObservers();
}
public void SetMeasurements(float temp, float humidity, float pressure)
{
_temp = temp;
_humidity = humidity;
_pressure = pressure;
MeasurementsChanged();
}
}
}
其中一个布告板:
namespace C02ObserverPattern.Raw
{
public class CurrentConditionDisplay: IObserver, IDisplayElement
{
private float _temp, _humidity;
public CurrentConditionDisplay(ISubject weatherData)
{
weatherData.RegisterObserver(this);
}
public void Update(float temp, float humidity, float pressure)
{
_temp = temp;
_humidity = humidity;
Display();
}
// 这个布告板只显示温度和湿度
public void Display()
{
Console.WriteLine($"Current conditions:{_temp}℃ and {_humidity}% humidity");
}
}
}
测试程序:
namespace C02ObserverPattern
{
class Program
{
static void Main(string[] args)
{
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
// XxxDisplay1 xxxDisplay1 = new XxxDisplay1(weatherData);
// XxxDisplay2 xxxDisplay2 = new XxxDisplay2(weatherData);
weatherData.SetMeasurements(10, 20, 30);
weatherData.SetMeasurements(14, 25, 36);
weatherData.SetMeasurements(40, 50, 60);
Console.ReadLine();
}
}
}
结果:
----------------------------------------------------------------------------------------------------------------------------------------------------------------
使用C#内置的Observer Pattern
IObservable<T> 作为Subject。
IObserver<T> 作为Observer。
先封装一下数据:
namespace C02ObserverPattern.BuiltIn
{
public struct MyData
{
public float Temperature { get; }
public float Humidity { get; }
public float Pressure { get; }
public MyData(float temperature, float humidity, float pressure)
{
Temperature = temperature;
Humidity = humidity;
Pressure = pressure;
}
}
}
WeatherData:
namespace C02ObserverPattern.BuiltIn
{
public class WeatherDataAlternative : IObservable<MyData>
{
private readonly HashSet<IObserver<MyData>> _observers;
public WeatherDataAlternative()
{
_observers = new HashSet<IObserver<MyData>>();
}
// 订阅用户,并返回可取消订阅的对象
public IDisposable Subscribe(IObserver<MyData> observer)
{
if (!_observers.Contains(observer))
{
_observers.Add(observer);
}
return new Unsubscriber(_observers, observer);
}
// 发布通知
public void NotifyMeasurementsChanged(MyData? data)
{
foreach (var observer in _observers)
{
if (!data.HasValue)
{
observer.OnError(new Exception("No value!!!"));
}
else
{
observer.OnNext(data.Value);
}
}
}
// 关闭这个Subject
public void WeatherStationClose()
{
foreach (var observer in _observers.ToArray())
{
observer.OnCompleted();
}
_observers.Clear();
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("The weather station has been closed.");
}
// 取消订阅的类
private class Unsubscriber : IDisposable
{
private readonly HashSet<IObserver<MyData>> _observers;
private readonly IObserver<MyData> _observer;
public Unsubscriber(HashSet<IObserver<MyData>> observers, IObserver<MyData> observer)
{
_observers = observers;
_observer = observer;
}
public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
_observers.Remove(_observer);
}
}
}
}
实现两个布告板:
namespace C02ObserverPattern.BuiltIn
{
public class CurrentConditionDisplayAlternative : IObserver<MyData>
{
private IDisposable _unsubscriber;
// 订阅
public virtual void Subscribe(IObservable<MyData> provider)
{
if (provider != null)
_unsubscriber = provider.Subscribe(this);
}
// 相当于Display()
public void OnNext(MyData value)
{
Console.WriteLine($"Current condition: {value.Temperature}C degrees and {value.Humidity}% humidity and {value.Pressure} pressure.");
}
// 发生错误时调用
public void OnError(Exception error)
{
var original = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Error occurred at Current Condition Display!");
Console.ForegroundColor = original;
}
// 完成时,一般就是取消订阅
public void OnCompleted()
{
Console.WriteLine("Current condition display Task Completed.");
Unsubscribe();
}
// 取消订阅
public virtual void Unsubscribe()
{
Console.WriteLine("Current condition display will unsubscribe from weather station.");
_unsubscriber.Dispose();
}
}
}
namespace C02ObserverPattern.BuiltIn
{
public class StatisticsDisplayAlternative : IObserver<MyData>
{
private IDisposable _unsubscriber;
public virtual void Subscribe(IObservable<MyData> provider)
{
if (provider != null)
_unsubscriber = provider.Subscribe(this);
}
public void OnNext(MyData value)
{
Console.WriteLine($"Statistics: {value.Temperature}, {value.Humidity}, {value.Pressure}.");
}
public void OnError(Exception error)
{
var original = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Error occurred at Statistics!");
Console.ForegroundColor = original;
}
public void OnCompleted()
{
Console.WriteLine("Statistics Task Completed.");
Unsubscribe();
}
public virtual void Unsubscribe()
{
Console.WriteLine("Statistics will unsubscribe from weather station.");
_unsubscriber.Dispose();
}
}
}
测试程序:
namespace C02ObserverPattern
{
class Program
{
static void Main(string[] args)
{
WeatherDataAlternative weatherDataAlternative = new WeatherDataAlternative();
CurrentConditionDisplayAlternative currentConditionDisplayAlternative = new CurrentConditionDisplayAlternative();
currentConditionDisplayAlternative.Subscribe(weatherDataAlternative);
StatisticsDisplayAlternative statisticsDisplayAlternative = new StatisticsDisplayAlternative();
statisticsDisplayAlternative.Subscribe(weatherDataAlternative);
weatherDataAlternative.NotifyMeasurementsChanged(new MyData(25, 75, 120));
Task.Delay(1000);
weatherDataAlternative.NotifyMeasurementsChanged(null);
Task.Delay(1000);
currentConditionDisplayAlternative.Unsubscribe();
weatherDataAlternative.NotifyMeasurementsChanged(new MyData(23, 45, 104));
weatherDataAlternative.WeatherStationClose();
Console.ReadLine();
}
}
}
- 实用的前端开发小技巧汇集
- python编码问题
- 微信支付推出人脸识别智慧时尚试衣间,无感购物即将来袭
- 如何判断你买的域名有没有被K过?
- 【设计模式】Factory模式
- Windows:将cmd命令行添加到右键中方法
- 回家的低价票难抢?注意!可能是被“爬虫”吃了
- DeepMind团队回顾2017年:想象、推理取得突破
- flask-mail发送QQ邮件代码示例(亲测可行)
- 数据结构与算法C#版笔记--排序(Sort)-下
- pip --upgrade批量更新过期的python库
- 数据结构与算法C#版笔记--排序(Sort)-上
- android 模拟器安装二三事
- 2017小程序发展大事件和未来3大趋势分析
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 拨云见日:揭开ORA-00600:[4193]的神秘面纱
- AUCell | 识别单细胞对“基因集”的响应
- 未来十年,机器学习工程师会消失吗?
- Get了!用Python制作数据预测集成工具 | 附代码
- 定时任务最简单的3种实现方法(超好用)
- Swift:UICollectionReusableView xib创建报错
- echo-高性能,可扩展,极简的Go Web框架
- 小程序文字显示换行
- css Backgroud-clip (文字颜色渐变)
- 微信小程序 buton清除默认样式
- 正则replace 回调函数里接收的参数是什么?
- 微信小程序使用pako.js的踩坑笔记
- Koa - 初体验(写个接口)
- Koa - 中间件(理解中间件、实现一个验证token中间件)
- Koa - 使用koa-multer上传文件(上传限制、错误处理)