设计模式---订阅发布模式(Subscribe/Publish)
设计模式---订阅发布模式(Subscribe/Publish)
订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。
将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相应对象间的一致性,这样会给维护、扩展和重用都带来不便。当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象需要改变时,就可以使用订阅发布模式了。
一个抽象模型有两个方面,其中一方面依赖于另一方面,这时订阅发布模式可以将这两者封装在独立的对象中,使它们各自独立地改变和复用。订阅发布模式所做的工作其实就是在解耦合。让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一边的变化。
在我们日常写程序时,经常遇到下面这种情况:
public void 有告警信息产生()
{
刷新界面();
更新数据库();
给管理员发Mail();
………………………………
}
当有告警信息产生时,依次要去执行:刷新界面()、更新数据库()、给管理员发Mail()等操作。表面上看代码写得很工整,其实这里面问题多多:
- 首先,这完全是面向过程开发,根本不适合大型项目。
- 第二,代码维护量太大。设想一下,如果产生告警后要执行10多个操作,那这将是个多么大,多少复杂的类呀,时间一长,可能连开发者自己都不知道如何去维护了。
- 第三,扩展性差。如果产生告警后,要增加一个声音提示()功能,怎么办呢?没错,只能加在有告警信息产生()这个函数中,这样一来,就违反了“开放-关闭原则”。而且修改了原有的函数,那么在测试时,除了要测新增功能外,还要做原功能的回归测试;在一个大型项目中,做一次回归测试可能要花费大约两周左右的时间,而且前提是新增功能没有影响原来功能及产生新的bug。
那么如何把有告警信息产生()函数同其他函数进行解耦合呢?别着急,下面就介绍今天的主角----订阅发布模式。见下图:
上面的流程就是对有告警信息产生()这个函数的描述。我们要做的,就是把产生告警和它需要通知的事件进行解耦,让它们之间没有相互依赖的关系,解耦合图如下:
事件触发者被抽象出来,称为消息发布者,即图中的P。事件接受都被抽象出来,称为消息订阅者,即图中的S。P与S之间通过S.P(即订阅器)连接。这样就实现了P与S的解耦。首先,P就把消息发送到指定的订阅器上,从始至终,它并不知道也不关心要把消息发向哪个S。S如果想接收消息,就要向订阅器进行订阅,订阅成功后,S就可以接收来自S.P的消息了,从始至终,S并不知道也不关心消息来源于哪个具体的P。同理,S还可以向S.P进行退订操作,成功退订后,S就无法接收到来自指定S.P的消息了。这样就完美的解决了P与S之间的解耦。
等等,好像还有一个问题。从图上看,虽然P与S之间完成了解耦,但是P与S.P,S与S.P之间不就又耦合上了吗?其实这个问题好解决,想想我们上篇的装饰模式是怎么解耦的?对,就接口。这里我们同样使用接口来解决P与S.P和S与S.P之间的解耦,同时,使用delegate来解决多订阅多发布的机制。
下面给出实现代码。由于订阅发布模式涉及P, S.P和S三部份内容,所以代码比较多,也比较长。请大家耐心阅读。
首先,为了实现P与S.P, S与S.P之间的解耦,我们需要定义两个接口文件
ISubscribe.cs
namespace TJVictor.DesignPattern.SubscribePublish
{
//定义订阅事件
public delegate void SubscribeHandle(string str);
//定义订阅接口
public interface ISubscribe
{
event SubscribeHandle
SubscribeEvent;
}
}
IPublish
namespace TJVictor.DesignPattern.SubscribePublish
{
//定义发布事件
public delegate void PublishHandle(string str);
//定义发布接口
public interface IPublish
{
event PublishHandle PublishEvent;
void Notify(string str);
}
}
然后我们来设计订阅器。显然订阅器要实现双向解耦,就一定要继承上面两个接口,这也是我为什么用接口不用抽象类的原因(类是单继承)。
namespace
TJVictor.DesignPattern.SubscribePublish
{
public class SubPubComponet : ISubscribe, IPublish
{
private string _subName;
public SubPubComponet(string
subName)
{
this._subName = subName;
PublishEvent
+= new PublishHandle(Notify);
}
#region ISubscribe Members
event SubscribeHandle
subscribeEvent;
event SubscribeHandle
ISubscribe.SubscribeEvent
{
add {
subscribeEvent += value; }
remove {
subscribeEvent -= value; }
}
#endregion
#region IPublish Members
public PublishHandle PublishEvent;
event PublishHandle IPublish.PublishEvent
{
add {
PublishEvent += value; }
remove {
PublishEvent -= value; }
}
#endregion
public void Notify(string str)
{
if
(subscribeEvent != null)
subscribeEvent.Invoke(string.Format("消息来源{0}:消息内容:{1}", _subName, str));
}
}
}
接下来是设计订阅者S。S类中使用了ISubscribe来与S.P进行解耦。代码如下:
namespace
TJVictor.DesignPattern.SubscribePublish
{
public class Subscriber
{
private string _subscriberName;
public Subscriber(string subscriberName)
{
this._subscriberName = subscriberName;
}
public ISubscribe AddSubscribe { set { value.SubscribeEvent += Show; } }
public ISubscribe RemoveSubscribe {
set { value.SubscribeEvent -= Show; } }
private void Show(string str)
{
Console.WriteLine(string.Format("我是{0},我收到订阅的消息是:{1}", _subscriberName, str));
}
}
}
最后是发布者P,继承IPublish来对S.P发布消息通知。
namespace
TJVictor.DesignPattern.SubscribePublish
{
public class Publisher:IPublish
{
private string _publisherName;
public Publisher(string publisherName)
{
this._publisherName = publisherName;
}
private event PublishHandle PublishEvent;
event PublishHandle
IPublish.PublishEvent
{
add {
PublishEvent += value; }
remove {
PublishEvent -= value; }
}
public void Notify(string str)
{
if
(PublishEvent != null)
PublishEvent.Invoke(string.Format("我是{0},我发布{1}消息", _publisherName, str));
}
}
}
至此,一个简单的订阅发布模式已经完成了。下面是调用代码及运行结果。调用代码模拟了图2中的订阅发布关系,大家可以从代码,运行结果和示例图三方面对照着看。
#region
TJVictor.DesignPattern.SubscribePublish
//新建两个订阅器
SubPubComponet subPubComponet1 = new SubPubComponet("订阅器1");
SubPubComponet subPubComponet2 = new SubPubComponet("订阅器2");
//新建两个发布者
IPublish publisher1 = new Publisher("TJVictor1");
IPublish publisher2 = new Publisher("TJVictor2");
//与订阅器关联
publisher1.PublishEvent += subPubComponet1.PublishEvent;
publisher1.PublishEvent += subPubComponet2.PublishEvent;
publisher2.PublishEvent += subPubComponet2.PublishEvent;
//新建两个订阅者
Subscriber s1 = new Subscriber("订阅人1");
Subscriber s2 = new Subscriber("订阅人2");
//进行订阅
s1.AddSubscribe = subPubComponet1;
s1.AddSubscribe = subPubComponet2;
s2.AddSubscribe = subPubComponet2;
//发布者发布消息
publisher1.Notify("博客1");
publisher2.Notify("博客2");
//发送结束符号
Console.WriteLine("".PadRight(50,'-'));
//s1取消对订阅器2的订阅
s1.RemoveSubscribe = subPubComponet2;
//发布者发布消息
publisher1.Notify("博客1");
publisher2.Notify("博客2");
//发送结束符号
Console.WriteLine("".PadRight(50, '-'));
#endregion
#region Console.ReadLine();
Console.ReadLine();
#endregion
运行结果图:
设计模式---订阅发布模式(Subscribe/Publish)的更多相关文章
- JavaScript设计模式 - 订阅发布模式(观察者模式)
var Event = (function() { var global = this, Event, _default = 'default'; Event = function() { var _ ...
- js设计模式之代理模式以及订阅发布模式
为啥将两种模式放在一起呢?因为这样文章比较长啊. 写博客的目的我觉得首要目的是整理自己的知识点,进而优化个人所得知识体系.知识成为个人的知识,就在于能够用自己的话表达同一种意义. 本文是设计模式系列文 ...
- ionic2踩坑之订阅发布模式的实现
原文地址:http://www.cnblogs.com/eccainiao/p/6429536.html 转载请说明. 在ionic2中实现订阅发布模式,需要用到Events. Events下面有三个 ...
- Java里观察者模式(订阅发布模式)
创建主题(Subject)接口 创建订阅者(Observer)接口 实现主题 实现观察者 测试 总结 在公司开发项目,如果碰到一些在特定条件下触发某些逻辑操作的功能的实现基本上都是用的定时器 比如用户 ...
- Publisher/Subscriber 订阅-发布模式
Publisher/Subscriber 订阅-发布模式 本博后续将陆续整理这些年做的一些预研demo,及一些前沿技术的研究,与大家共研技术,共同进步. 关于发布订阅有很多种实现方式,下面主要介绍WC ...
- saltstack系列(三)——zmq订阅/发布模式
zmq订阅发布模式 server端代码: #coding=utf-8 ''''' 服务端,发布模式 ''' import zmq from random import randrange contex ...
- RabbitMQ下的生产消费者模式与订阅发布模式
所谓模式,就是在某种场景下,一类问题及其解决方案的总结归纳.生产消费者模式与订阅发布模式是使用消息中间件时常用的两种模式,用于功能解耦和分布式系统间的消息通信,以下面两种场景为例: 数据接入 假设 ...
- Kafka下的生产消费者模式与订阅发布模式
原文:https://blog.csdn.net/zwgdft/article/details/54633105 在RabbitMQ下的生产消费者模式与订阅发布模式一文中,笔者以“数据接入”和“事 ...
- AngularJS的简单订阅发布模式例子
控制器之间的交互方式广播 broadcast, 发射 emit 事件 类似于 js中的事件 , 可以自己定义事件 向上传递直到 document 在AngularJs中 向上传递直到 rootScop ...
随机推荐
- Servlet上传文件
Servlet上传文件 1.准备工作 (1)利用FileUpload组件上传文件,须要到apache上下载commons-fileupload-1.3.1.jar 下载地址:http://common ...
- quick-cocos2d-x游戏开发【3】——display.newSprite创建向导
游戏嘛.没有图片没有图片可以称为你的游戏,所以,我们看一下使用quick如何创建精灵的方式. quick的api精灵族的创造仍然是非常具体的解释.因此,建立非常easy. display.newSpr ...
- poj 1274The Perfect Stall
第一次接触二分图匹配. 这题是一个匈牙利算法的模板题直接套即可. 题意是 给你奶牛和谷仓的个数a和b,接下来a行是奶牛喜欢去的谷仓.第一个是谷仓个数,接下来是谷仓编号. 这里我们把行当奶牛,列当谷仓 ...
- (四)左右ng-app自己主动bootstrap相框
博客之前 (三)ng-app的使用困惑和angularJS框架的自己主动载入 提出了使用ng-app指令的情况.之前认为出现第4和第5种情况非常奇怪,由于仅仅看到了现象,没有看到本质.JS错误.最直观 ...
- python基础课程_学习笔记13:标准库:有些收藏夹——sys
标准库:有些收藏夹 sys sys这个模块可以让你访问和python解释器联系紧密的变量和函数. sys模块中一些重要的函数和变量 函数/变量 描写叙述 argv 命令行參数,包含脚本名称 exit( ...
- SQL Mirroring[Hot back up with Double machine]
Background: It's fairly common for businesses to want to provide some high availability for their SQ ...
- solaris X86-64下一个ORACLE战斗11.2.0.3.8在一波折叠补丁
solaris X86-64下一个ORACLE战斗11.2.0.3.8补丁: 正确的步骤: 1.BUG6880880 .OPATCH补丁 2.BUG16902043.11.2.0.3.8补丁 情感是练 ...
- Linux设备驱动实现自己主动创建设备节点
#include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #inclu ...
- MYSQL-用户权限的验证过程(转)
知识点 因为MySQL是使用User和Host两个字段来确定用户身份的,这样就带来一个问题,就是一个客户端到底属于哪个host. 如果一个客户端同时匹配几个Host,对用户的确定将按照下面的优先级来排 ...
- cocos2d-x 网络请求
[cocos2dx]rapidjson用法以及中文显示的解决方法 cocos2dx 读取json及解析 cocos2dx rapidjson 高速解析JSON --- [cocos2d-x官方文档] ...