c# 使用计时器和观察者模式实现报警推送需求
作者:长沙大鹏 发布时间:2022-05-24 02:16:46
前言
这两天面试了一个物联网公司高级研发,面试题是下面这样子
公司领导,部门主管,小组组长,组成员4级,假如有个 疫情预警,先通知组人员(对个人,主要有一个处理就算处理了) 如果3分钟没处理,就往组长发短信,如果组长3分钟没处理就往上级推送。一级一级的。 要求单程序并发至少支持1000tps预警事件
说实话上面需求在我做的过往项目里有过类似需求,我也只是依稀记得自己是怎么做的。类似于使用第三方任务调度框架完成上面的3分钟超时处理,然后至于分级发送则更简单了,再不济就是使用if、else这样的最原始验证即可。但是这样的题目丢到面试题上我是一下就不知所措了。自然最终的结果也不尽人意(我最终提交的代码就是采用一个事件总线实现报警的推送,但是并未对其进行超时分级发送) 这个自然是错误的,我其实当时也想过使用Timer,但是内心对Timer就是感觉用性能做代价去换取最终的结果。
解析需求
过了几天还是觉得要手纯撸代码将上面功能撸出来,不然如何成长呢!
拆分下需求可以得到的消息是有一个事件。这个事件就是报警事件,通过报警事件需要将消息推送给不同职位的工作人员,而且必须遵循岗位从下至上,但凡人员收到报警消息则表示报警通知已完成,其次就是有一个三分钟需要处理。
通过上面的需求分析可以知道我们必须要定义一个枚举,记录职称高低。
/// <summary>
/// 工作级别
/// </summary>
public enum JobLevel : int
{
公司领导 = 1,
部门主管 = 2,
小组组长 = 3,
组成员 = 4
}
其次我们至少存在两个类,一个是产生报警的对象,这个对象有需要通知的报警信息和报警信息发送结果,当然更加少不了我们订阅了报警消息的订阅者。这里想了下,可以采用观察者设计模式进行解耦。
/// <summary>
/// 发布者接口
/// </summary>
public interface IPublish
{
/// <summary>
/// 新增订阅者(观察者)
/// </summary>
/// <param name="subscriber"></param>
void AddSubscriber(SubscriberPeopleBase subscriber);
/// <summary>
/// 移除订阅者(观察者)
/// </summary>
/// <param name="subscriber"></param>
void RemoveSubscriber(SubscriberPeopleBase subscriber);
/// <summary>
/// 发送报警消息
/// </summary>
void SendNotify();
/// <summary>
/// 出现警报
/// </summary>
/// <param name="msg">警报消息</param>
void CreateJB(string msg);
接下来就是实现上面接口了!
/// <summary>
/// 报警发布者
/// </summary>
public class Baojing : IPublish
{
/// <summary>
/// 订阅者集合
/// </summary>
public List<SubscriberPeopleBase> SubscriberLst { get; set; }
/// <summary>
/// 报警消息
/// </summary>
public string Msg { get; set; }
/// <summary>
/// 有无接收成功
/// </summary>
public bool IsSucess { get; set; }
/// <summary>
/// 消息通知计数器
/// </summary>
public Timer NotifyTimer { get; set; }
/// <summary>
/// 记录当前发送消息的岗位
/// </summary>
public JobLevel CurrentJobLevel = JobLevel.组成员;
public void AddSubscriber(SubscriberPeopleBase subscriber)
{
if (SubscriberLst == null) SubscriberLst = new List<SubscriberPeopleBase>();
SubscriberLst.Add(subscriber);
}
public void CreateJB(string msg)
{
Msg = msg;
}
public void RemoveSubscriber(SubscriberPeopleBase subscriber)
{
if (SubscriberLst != null) SubscriberLst.Remove(subscriber);
}
/// <summary>
/// 发送报警消息
/// </summary>
public void SendNotify()
{
if (SubscriberLst?.Count > 0)
{
//循环从职位低到高
SubscriberLst.OrderByDescending(p => (int)p.Title);
//立即执行CheckNotifySendResult,以为3秒为间隔
NotifyTimer = new Timer(CheckNotifySendResult, null, 0, 3000);
}
}
public void CheckNotifySendResult(object stat)
{
//先要停止定时器,防止并发
NotifyTimer.Change(-1, 3000);
//第一次发给职位最小 枚举最大的组成员
SubscriberPeopleBase subscriberPeople = SubscriberLst.Find(p =>
(((int)CurrentJobLevel + 1) - (int)p.Title) == 1);
if (subscriberPeople == null) return; //已经是最小的
if (!IsSucess)
{
IsSucess = subscriberPeople.ReceiveData(this);
if (!IsSucess)
{
CurrentJobLevel = CurrentJobLevel!= JobLevel.公司领导?(JobLevel)((int)CurrentJobLevel) - 1: JobLevel.公司领导;
NotifyTimer.Change(3000, 3000);
}
}
else
{
Console.WriteLine($"停止计数器 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
NotifyTimer.Dispose();
}
}
}
还需要有订阅者,订阅者使用抽象类方式降低耦合性(其实还是要用事件总线,将耦合性和扩展性提高一个档次)
/// <summary>
/// 订阅者
/// 其实这里还不够完善,理论上后续如果每个职位的订阅者对报警消息的处理不同则在下面的接收消息里会存在一些冗余代码,
/// 理想情况是不同级别应该有不同的实现,这样可以足够抽象,后续扩展也更加方便
/// </summary>
public abstract class SubscriberPeopleBase
{
/// <summary>
/// 工作职位(级别)
/// </summary>
public JobLevel Title { get; set; }
public SubscriberPeopleBase(JobLevel title)
{
Title = title;
}
public virtual bool ReceiveData(Baojing baojing)
{
if (Title == JobLevel.公司领导)
{
Console.WriteLine($"出现报警信息:{baojing.Msg},当前订阅者:{Title.ToString()} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
return true;
}
else
{
Console.WriteLine($"出现报警信息:{baojing.Msg},当前订阅者:{Title.ToString()},默认未应答 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
return false;
}
}
}
再加个子类,重写下接收警报的方法
public class SubscriberPeople : SubscriberPeopleBase
{
public SubscriberPeople(JobLevel title) : base(title) { }
public override bool ReceiveData(Baojing baojing)
{
if (Title == JobLevel.公司领导)
{
Console.WriteLine($"出现最新报警信息:{baojing.Msg},当前订阅者:{Title.ToString()} 已成功应答 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
return true;
}
else
{
Console.WriteLine($"出现报警信息:{baojing.Msg},当前订阅者:{Title.ToString()},默认未应答 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
return false;
}
}
}
最终我的main方法是这样
static void Main(string[] args)
{
IPublish publish = new Baojing();
publish.AddSubscriber(new SubscriberPeople(JobLevel.组成员));
publish.AddSubscriber(new SubscriberPeople(JobLevel.公司领导));
publish.AddSubscriber(new SubscriberPeople(JobLevel.部门主管));
publish.AddSubscriber(new SubscriberPeople(JobLevel.小组组长));
publish.CreateJB("主机温度过高!");
publish.SendNotify();
Console.ReadLine();
}
调试的效果如下:
明天还是完善下,将使用EventBus,将耦合性再度降低,同时也能让接收方法更加灵活性,能实现不同级别的职员做出不同的响应!
来源:https://www.cnblogs.com/hunanzp/p/12343863.html


猜你喜欢
- 感谢《Android源码设计模式解析与实战》 何红辉 关爱民 著适配器模式在我们的开发中使用率极高,从代码中随处可见的Adapter就可以判
- 示例1:public static String hello() { String s = "商务&qu
- 微服务之间token传递问题假设现在有A服务,B服务,外部使用RESTApi请求调用A服务,在请求头上有token字段,A服务使用完后,B服
- 调取钉钉考勤接口的功能公司需要做一个钉钉考勤的页面,让我去写这个功能。结果却比我想象的要麻烦一些!具体是怎么个麻烦呢下面直入正题首先我们找到
- 1.通过用FTP进行上传文件,首先要实现建立FTP连接,一般建立FTP连接,需要知道FTP配置有关的信息。一般要在Bean中建立一个Serv
- 前言在项目开发过程中,时常会碰到这种情况:1.同一个Project的同一个API,有几个不同的接口,比如内部测试用的Server,和当前版本
- 前言:当工具类对多个模型类进行排序,比较等操作的时候,需要书写大量重复代码,因为懒人总要想怎么省事的,所以考虑使用泛型这个玩意简化代码案例:
- 最近碰到个项目要使用到滚动选择器,原生的NumberPicker可定制性太差,不大符合UI要求。网上开源的WheelView是用Scroll
- 本文实例为大家分享了java实现银行管理系统的具体代码,供大家参考,具体内容如下Bank类package First;import java
- 本文实例讲述了C#实现HSL颜色值转换为RGB的方法。分享给大家供大家参考。具体实现方法如下://This method converts
- 举一个生活中的小例子,大凡开过学或者毕过业的都会体会到这样一种郁闷:你要去 n个地方办理 n 个手续(现在大学合并后就更加麻烦,因为可能那
- 由于Android项目开源所致,市面上出现了N多安卓软件市场。为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们
- 日期格式化标准 DateTime 格式字符串如果格式字符串只包含下表列出的某个单个格式说明符,则它们被解释为标准格式说明符。如果指定的格式字
- ListView,就如其名,是用来显示列表的一种View,而RecycleView,是其的加强版,今天带来的是这两个几乎具有相同的功能的对比
- 问题引出:最近开了新项目,项目中用到了数据字典,列表查询数据返回的时候需要手动将code转换为name,到前台展示。项目经理表示可以封装一个
- C#的MVC寻找对应的控制器首先是寻找当前域的如果找不到就会寻找备用的..但是有些时候我们是不想他去寻找备用的控制器.这里就涉及到了一个Da
- Navigator 的 push 和 pop方法Navigator 导航器的 push 和 pop 方法可以携带参数在页面间传递,其他变形的
- 前言一个说难不难,说简单竟看不出来是哪里问题的一个bug。是的 可能自己能力和经验尚浅无法识别,下面你们能否用火眼金睛一眼让bug原形毕露(
- 一、排序与去重日常工作中,总会有一些场景需要对结果集进行一些过滤。比如,与第三方交互后获取的结果集,需要再次排序去重,此时就会根据某个字段来
- 前言AndroidStudio升级到3.0之后,gradle版本也随之升级到了3.0.0版本。当gradle插件升级到3.0.0及以上后,我