多场景抢红包业务引发.NETCore下使用适配器模式实现业务接口分离
事情的起因
我们公司现有一块业务叫做抢红包,最初的想法只是实现了一个初代版本,就是给指定的好友单发红包,随着业务的发展,发红包和抢红包的场景也越来越多,目前主要应用的场景有:单聊发红包、群聊发红包、名片发红包、直播场景中的主播发红包/观众给主播发红包/定时抢红包,接下来,如果出现其它产品的业务,也将大概率的出现抢红包的需求。
大同小异的抢红包业务
红包的场景无论怎么变化,其核心算法不变,这部分是可以抽象的内容,随着迭代发展,我们之前通常都是通过增加红包的类型(业务)来扩展,但是随着肉眼可见的发展,部分业务的改动如果需要对红包业务进行调整和优化对话,将有可能产生牵一发而动全身的debuff效果。
新的改变
其实这些业务代码早该优化一下,我就是懒+忙(借口),正好有位新同事入职,这块的优化任务就交给他来做了,从头到尾我都没有参与(不知道有没有吐槽我的代码,捂脸~),我初步看了一下,代码的实现质量还是挺高的,正好也是一个比较好的应用场景,我就简单实现一下他做的适配器模式,彻底的将各个红包业务类型分离,很好的实现了设计模式的开闭原则,加入某天某个场景的抢红包业务下线了,这种做法是非常有利于业务的扩展和维护。
定义抢红包接口
public interface IRedPacket
{
    string Name { get; }
    string Put(int org_id, int money, int count, string reason);
    string Get(int id);
}
以上接口包含一个属性和2个方法,用于设置业务名称和收发红包。初次之外,我们还需要定义一个实现业务的基类,用于处理公共业务。
红包基类业务实现
public abstract class RedPacket : IRedPacket
{
    public abstract string Name { get; }
    public abstract string Put(int org_id, int money, int count, string reason);
    public abstract string Get(int id);
    protected string Create(string reason, int money, int count)
    {
            Console.WriteLine("创建了红包:{0},金额:Money:{1},数量:{2}", reason, money, count);
            return "成功";
    }
    protected string Fighting()
    {
            Console.WriteLine("调用了抢红包方法:{0}", nameof(Fighting));
            return "成功";
    }
}
在基类中,我们选择不实现接口,将接口方法定义为抽象类型。同时,定义并实现两个受保护的方法 Create(创建红包)/Fighting(抢红包),接口方法由子类实现具体的业务细节,当子类针对具体的业务细节实现完成后,他们应该会调用Create(创建红包)/Fighting(抢红包)的方法,直至最终完成整个红包的流程。
实现单聊红包
 public class ChatOneRedPacket : RedPacket
 {
     public override string Name { get; } = "ChatOne";
     public override string Put(int org_id, int money, int count, string reason)
     {
         Console.WriteLine("检查接收人ID:{0}是否存在", org_id);
         return base.Create(reason, money, count);
     }
     public override string Get(int id)
     {
         Console.WriteLine("检查红包ID:{0},是否具有领取资格", id);
         return base.Fighting();
     }
 }
群聊红包
public class ChatGroupRedPacket : RedPacket
{
    public override string Name { get; } = "ChatGroup";
    public override string Put(int org_id, int money, int count, string reason)
    {
        Console.WriteLine("检查群ID:{0},是否存在", org_id);
        return base.Create(reason, money, count);
    }
    public override string Get(int id)
    {
        Console.WriteLine("检查是否群ID:{0},当前用户是否群成员", id);
        return base.Fighting();
    }
}
直播红包
public class LiveRedPacket : RedPacket
{
    public override string Name { get; } = "Live";
    public override string Put(int org_id, int money, int count, string reason)
    {
        Console.WriteLine("检查直播ID:{0}是否存在", org_id);
        return base.Create(reason, money, count);
    }
    public override string Get(int id)
    {
        Console.WriteLine("检查红包ID:{0} 是否当前主播红包", id);
        return base.Fighting();
    }
}
为了方便演示,上面的三种红包子类仅简单的实现类属性 Name="ChatOne",除此之外,还实现类接口的收发红包接口,子类实现 Name 属性主要是便于我们在DI中去灵活的区分调用的主体,实现业务的分离。除了单聊红包外,我们还有群聊和直播红包,都采用上面的处理方式,只是各自实现的 Name 属性时,指定不同的名字即可。在接口实现的方法中,各自的业务还需要执行不同的业务检查,比如单聊红包就需要检查接收人是否存在,群聊红包还需要检查群是否存在,该群是否被冻结等等,直播红包需要检查主播是否在直播中,观众是否在直播房间内,这些都是不同业务场景产生的特殊的业务处理需求。
创建容器实例
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(typeof(IRedPacket), typeof(ChatOneRedPacket))
            .AddScoped(typeof(IRedPacket), typeof(ChatGroupRedPacket))
            .AddScoped(typeof(IRedPacket), typeof(LiveRedPacket));
    ...
}
容器实例的创建非常简单,只需要将已实现 IRedPacket 接口的子类注册到服务管道即可。
依赖注入,以实例集的方式
[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
    private readonly IEnumerable<IRedPacket> redpackets;
    public HomeController(IEnumerable<IRedPacket> redpackets)
    {
        this.redpackets = redpackets;
    }
}
通过建立一个控制台 HomeController 用于演示,在 HomeController 的构造方法中,使用 IEnumerable 获得在服务中创建的所有实现接口 IRedPacket 的实例。下面将在 HomeController 中 创建两个接口进行演示发红包/抢红包。
发红包
[HttpPost]
public ActionResult<string> Post([FromBody] RedPacketViewModel model)
{
    var rp = this.redpackets.Where(f => f.Name == model.Type).FirstOrDefault();
    if (rp == null)
    {
        var msg = $"红包业务类型:{model.Type}不存在";
        Console.WriteLine(msg);
        return msg;
    }
    var result = rp.Put(model.Org_Id, model.Money, model.Count, model.Reason);
    return result;
}
为了演示方便,我们构造4中不同的业务实体去调用发红包的接口,分别将结果输出到客户端
// 单聊红包
{
  "type":"ChatOne",
  "org_id":1,
  "money":8,
  "count":1,
  "reason":"恭喜发财,大吉大利!"
}
// 群聊红包
{
  "type":"ChatGroup",
  "org_id":2,
  "money":9,
  "count":3,
  "reason":"恭喜发财,大吉大利!"
}
// 直播红包
{
  "type":"Live",
  "org_id":3,
  "money":8,
  "count":1,
  "reason":"恭喜发财,大吉大利!"
}
//圈子红包
{
  "type":"Quanzi",
  "org_id":4,
  "money":8,
  "count":1,
  "reason":"恭喜发财,大吉大利!"
}
输出结果为:
// 单聊红包
检查接收人ID:1是否存在
红包类型:ChatOne,创建了红包:恭喜发财,大吉大利!,金额:Money:8,数量:1
// 群聊红包
检查群ID:2,是否存在
红包类型:ChatGroup,创建了红包:恭喜发财,大吉大利!,金额:Money:9,数量:3
// 直播红包
检查直播ID:3是否存在
红包类型:Live,创建了红包:恭喜发财,大吉大利!,金额:Money:8,数量:1
//圈子红包
红包业务类型:Quanzi不存在
抢红包
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
    // 生产环境下,该红包消息应该是从数据库中读取
    var model = GetRedPacket(id);
    var rp = this.redpackets.Where(f => f.Name == model.Type).FirstOrDefault();
    var result = rp.Get(id);
    return result;
}
private RedPacketViewModel GetRedPacket(int id)
{
    int type = --id;
    string[] redPackets = { "ChatOne", "ChatGroup", "Live" };
    var model = new RedPacketViewModel
    {
        Count = 3,
        Money = 8,
        Org_Id = 115,
        Reason = "恭喜发财,大吉大利!",
        Type = redPackets[type]
    };
    return model;
}
抢红包的过程,传入一个红包ID,然后跟进该ID到数据库进行查找,得到红包后,根据红包类型找出 IRedPacket 的实现类,并进行调用,完成抢红包的操作。可能有的同学会觉得比较奇怪,为什么不直接拆红包呢?这是因为我们要根据红包设计的初衷,不同的红包,其所执行的业务规范性检查是不同的,不能直接进行暴力拆包。
结束语
上面我们创建了3个IRedPacket的实现类,并将他们注册到服务管道中,然后在HomeController中获得服务依赖注入的实例对象,通过在不同的参数传入,实现了不同的红包业务场景的拆分,很好的实现了设计模式中所说的开闭原则。
演示代码下载
https://github.com/lianggx/Examples/tree/master/Ron.RedPacketTest
多场景抢红包业务引发.NETCore下使用适配器模式实现业务接口分离的更多相关文章
- .NetCore 下开发独立的(RPL)含有界面的组件包 (六)实现业务功能
		.NetCore 下开发独立的(RPL)含有界面的组件包 (一)准备工作 .NetCore 下开发独立的(RPL)含有界面的组件包 (二)扩展中间件及服 务 .NetCore 下开发独立的(RPL)含 ... 
- .NetCore 下开发独立的(RPL)含有界面的组件包 (五)授权过滤参数处理
		.NetCore 下开发独立的(RPL)含有界面的组件包 (一)准备工作 .NetCore 下开发独立的(RPL)含有界面的组件包 (二)扩展中间件及服 务 .NetCore 下开发独立的(RPL)含 ... 
- .NetCore 下开发独立的(RPL)含有界面的组件包 (四)授权过滤
		.NetCore 下开发独立的(RPL)含有界面的组件包 (一)准备工作 .NetCore 下开发独立的(RPL)含有界面的组件包 (二)扩展中间件及服 务 .NetCore 下开发独立的(RPL)含 ... 
- .NetCore 下开发独立的(RPL)含有界面的组件包 (三)构建界面
		.NetCore 下开发独立的(RPL)含有界面的组件包 (一)准备工作 .NetCore 下开发独立的(RPL)含有界面的组件包 (二)扩展中间件及服 务 .NetCore 下开发独立的(RPL)含 ... 
- .NetCore 下开发独立的(RPL)含有界面的组件包 (二)扩展中间件及服务
		.NetCore 下开发独立的(RPL)含有界面的组件包 (一)准备工作 .NetCore 下开发独立的(RPL)含有界面的组件包 (二)扩展中间件及服 务 .NetCore 下开发独立的(RPL)含 ... 
- .NetCore 下开发独立的(RPL)含有界面的组件包 (一)准备工作
		.NetCore 下开发独立的(RPL)含有界面的组件包 (一)准备工作 .NetCore 下开发独立的(RPL)含有界面的组件包 (二)扩展中间件及服 务 .NetCore 下开发独立的(RPL)含 ... 
- .netcore下的微服务、容器、运维、自动化发布
		原文:.netcore下的微服务.容器.运维.自动化发布 微服务 1.1 基本概念 1.1.1 什么是微服务? 微服务架构是SOA思想某一种具体实现.是一种将单应用程序作为一套小型 ... 
- 我们NetCore下日志存储设计
		日志的分类 首先往大的来说,日志分2种 ①业务日志: 即业务系统需要查看的日志, 常见的比如谁什么时候修改了什么. ②参数日志: 一般是开发人员遇到问题的时候定位用的, 一般不需要再业务系统里展示. ... 
- NetCore下模拟和使用Modbus工业通信协议
		Tips: 1.目前NetCore下与Modbus通信的框架主要选择了 Modbus.Net https://github.com/parallelbgls/Modbus.Net 2.modbus是 ... 
随机推荐
- 史上最全面的SignalR系列教程-3、SignalR 实现推送功能-集线器类实现方式
			1.概述 通过前两篇 史上最全面的SignalR系列教程-1.认识SignalR 史上最全面的SignalR系列教程-2.SignalR 实现推送功能-永久连接类实现方式 文章对SignalR的介绍, ... 
- Zookeeper_阅读源码第一步_在 IDE 里启动 zkServer(单机版)
			Zookeeper是开源的,如果想多了解Zookeeper或看它的源码,最好是能找到它的源码并在 IDE 里启动,可以debug看它咋执行的,能够帮助你理解其原理. 准备源码 所以我们很容易搞到它的源 ... 
- k8s学习笔记
			9.deployment:声明式的升级应用 9.1.使用RC实现滚动升级 #kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v ... 
- Appium+python自动化(三十二)- 代码写死一时爽,框架重构火葬场 - PageObject+unittest(超详解)
			简介 江湖有言:”代码写死一时爽,框架重构火葬场“,更有人戏言:”代码动态一时爽,一直动态一直爽 
- 8.15 day33 进程池与线程池_协程_IO模型(了解)
			进程池和线程池 开进程开线程都需要消耗资源,只不过两者比较的情况线程消耗的资源比较少 在计算机能够承受范围之内最大限度的利用计算机 什么是池?  在保证计算机硬件安全的情况下最大限度地利用计算机  ... 
- Ubuntu18设置mysql的sql_mode
			原因: MySQL 5.7.5及以上功能依赖检测功能.如果启用了ONLY_FULL_GROUP_BY SQL模式(默认情况下),MySQL将拒绝选择列表,HAVING条件或ORDER BY列表的查询引 ... 
- RSA加密的java实现
			首先科普一波: RSA的1024位是指公钥及私钥分别是1024bit,也就是1024/8=128 Bytes RSA算法密钥长度的选择是安全性和程序性能平衡的结果,密钥长度越长,安全性越好,加密解密所 ... 
- 搭建SFTP服务器,允许一个或多个用户拥有一个或多个目录的rwx权限
			1.引言 sftp可以为传输文件提供一种安全的网络的加密方法.sftp 与 ftp 有着几乎一样的语法和功能.SFTP 为 SSH的其中一部分,是一种传输档案至 Blogger 伺服器的安全方式.其实 ... 
- JavaScript计算平方数的三种方法
			console.log(2*10**3) console.log(2*Math.pow(10,3)) console.log(2e3) console.log(2*1e3) console.log(2 ... 
- Flume日志采集框架的使用
			文章作者:foochane 原文链接:https://foochane.cn/article/2019062701.html Flume日志采集框架 安装和部署 Flume运行机制 采集静态文件到h ... 
