Head First设计模式——观察者模式
前言: 这篇文章我们以Head First设计模式中讲解的气象站为例,通过它的案列进行学分析和编码(C#)测试,并归纳总结出观察者模式。
1、气象监测案列,错误示范实现
一个气象站,分别有三个装置:温度感应装置,湿度感应装置,气压感应装置。WeathData对象跟踪气象站数据,WeathData有MeasurmentsChanged()方法,当感应装置数据变化后就会调用MeasurmentsChanged对使用改数据的用户进行数据更新。目前需求是要三个布告板,分别是目前气象数据状况布告板(CurrentConditionDisply)、气象数据统计布告板(StaisticsDisply)、天气预报布告板(ForcastDisply)。三块布告板都是需要接收气象站数据,然后按需展示到布告板上。针对这个需求我们可以如下方式实现:
public class WeatherData(){
private float Temperature{get;set;}
private float Humidity{get;set;}
private float Pressure{get;set;}
public void MeasurmentsChanged(){
CurrentConditionDisply.Update(Temperature,Humidity,Pressure);
StaisticsDisply.Update(Temperature,Humidity,Pressure);
ForcastDisply.Update(Temperature,Humidity,Pressure);
}
}
public class CurrentConditionDisply{
public void Update(float temperature,float humidity,float Pressure){
//更新公布数据
}
}
public class StaisticsDisply{
public void Update(float temperature,float humidity,float Pressure){
//更新统计数据
}
}
public class ForcastDisply{
public void Update(float temperature,float humidity,float Pressure){
//更新天气预报
}
}
WeatherData是数据跟踪对象,当气象站数据变化时用MeasurmentsChanged方法来依次调用三块布告板的Update方法更新气象数据。按照这种设计能实现目前需求,但是如果新加入一种布告板或者删除一个布告板,那么我们需要去修改MeasurmentsChanged方法新增或者删除代码,这就会造成后期的维护扩展问题。这个例子暴露的问题:
1、我们是针对实现编程,而非针对接口。
2、对于每个新的布告板,我们都得修改代码。
3、无法在运行时动态地增加或者删除布告板。
4、未封装改变的部分,违反了对修改关闭,对扩展开放原则。
2、使用观察者模式解耦
由1的实现和带来的问题以及它的场景我们可以使用设计模式中的观察者模式很好的满足这一需求,且后面的维护扩展都很方便。首先我们先了解观察者模式
观察者模式:定义了对象之间的一对多依赖,当一个对象改变时,他的所有依赖都会收到通知并自动更新。
订阅报纸就是典型的观察者模式,出版社即为主题(subject),订阅者即是观察者(observer),当有新报纸时,报社就会派人送新报纸到订阅了该报纸的读者手上。我们通过观察者模式类图进行理解我记忆,然后我们再对之前的气象站进行观察者模式封装修改。
3、利用观察者模式改进气象站
按照观察者模式我们需要定义一个主题接口Subject,WeatherData作为具体的主题类继承接口Subject,实现注册移除通知观察者接口。定义Observer接口,其他三块布告板继承Observer实现自己的更新数据方法Update。
/// <summary>
/// 主题
/// </summary>
public interface Subject
{
public void RegisterObserver(Observer o);
public void RemoveObserver(Observer o);
public void NotifyObserver();
}
/// <summary>
/// 具体主题(气象站)
/// </summary>
public class WeatherData : Subject
{
private List<Observer> observers;
private float Temperature { get; set; }
private float Humidity { get; set; }
private float Pressure { get; set; }
public WeatherData()
{
observers = new List<Observer>();
}
public void RegisterObserver(Observer o)
{
observers.Add(o);
}
public void RemoveObserver(Observer o)
{
observers.Remove(o);
}
//通知观察者
public void NotifyObserver()
{
foreach (var o in observers)
{
o.Update(Temperature, Humidity, Pressure);
}
}
public void MeasurmentsChanged()
{
NotifyObserver();
}
//数据变化
public void SetMeasurments(float temperature, float humidity, float pressure)
{
Temperature = temperature;
Humidity = humidity;
Pressure = pressure;
MeasurmentsChanged();
}
}
/// <summary>
/// 订阅者
/// </summary>
public interface Observer
{
void Update(float temperature, float humidity, float pressure);
}
public class CurrentConditionDisply : Observer
{
private Subject weatherData;
public CurrentConditionDisply(Subject weatherData)
{
this.weatherData = weatherData;
weatherData.RegisterObserver(this);
}
public void Update(float temperature, float humidity, float pressure)
{
Console.WriteLine($"当前情况布告板:{temperature},{humidity},{pressure}");
}
}
public class StaisticsDisply : Observer
{
private Subject weatherData;
public StaisticsDisply(Subject weatherData)
{
this.weatherData = weatherData;
weatherData.RegisterObserver(this);
}
public void Update(float temperature, float humidity, float pressure)
{
Console.WriteLine($"统计数据布告板:{temperature},{humidity},{pressure}");
}
}
public class ForcastDisply : Observer
{
private Subject weatherData;
public ForcastDisply(Subject weatherData)
{
this.weatherData = weatherData;
weatherData.RegisterObserver(this);
}
public void Update(float temperature, float humidity, float pressure)
{
Console.WriteLine($"天气预报布告板:{temperature},{humidity},{pressure}");
}
}
对使用了观察者模式的气象站进行测试,当数据变化的时候就会自动通知观察者并更新数据,也可以灵活的添加移除观察者而不用去具体的实现里面修改代码。
static void Main(string[] args)
{
WeatherData weatherData = new WeatherData();
CurrentConditionDisply currentConditionDisply = new CurrentConditionDisply(weatherData);
StaisticsDisply staisticsDisply = new StaisticsDisply(weatherData);
ForcastDisply forcastDisply = new ForcastDisply(weatherData);
weatherData.SetMeasurments(30, 65, 30.5F);
Console.WriteLine("---------------移除订阅者-----------");
weatherData.RemoveObserver(currentConditionDisply);
weatherData.SetMeasurments(31,55,20);
Console.WriteLine("---------------添加订阅者-----------");
weatherData.RegisterObserver(currentConditionDisply);
weatherData.SetMeasurments(30, 55, 30.5F);
Console.ReadKey();
}
- Npm vs Yarn 之备忘详单
- 竞猜活动区块链方案探索
- 大前端神器安利之 Puppeteer
- 传统数据库也能实现区块链存储
- golang(Go语言) byte/[]byte 与 二进制形式字符串 互转
- Sublime Text 最新注册码分享
- Lua table之弱引用
- 看吧,这就是现代化 PHP 该有的样子
- 从web图片裁剪出发:了解H5中的Blob
- Android子线程更新UI主线程方法之Handler
- Drawable.Bitmap.Canvas.Paint.Matrix
- 关于JSON.stringify和Unicode编码,需要注意的几点
- 用 PHP 的方式实现的各类算法合集
- Nginx 反向代理解决前后端联调跨域问题
- 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 数组属性和方法
- 1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知
- CNN不用乘法? AdderNet和DeepShift论文理解
- 动手构建地铁关系网,实现最短路径查询
- Java并发编程(08):Executor线程池框架
- 用Scipy求解单个正态总体的置信区间
- 架构设计 | 基于电商交易流程,图解TCC事务分段提交
- 用Gaussian做CASSCF计算
- 用Gaussian 16计算振动分辨的紫外-可见吸收光谱
- graylog日志分析系统上手教程
- 使用Seq搭建免费的日志服务
- 拜托!这才是分布式系统CAP的正确打开方式!
- 接口管理这下总会了吧?
- 交子杯 - 2020 - AI赛道 - TOP1
- Valine 一款快速、简洁且高效的无后端评论系统
- 两段有趣的C代码