咱们公司现有一块业务叫作抢红包,最初的想法只是实现了一个初代版本,就是给指定的好友单发红包,随着业务的发展,发红包和抢红包的场景也愈来愈多,目前主要应用的场景有:单聊发红包、群聊发红包、名片发红包、直播场景中的主播发红包/观众给主播发红包/定时抢红包,接下来,若是出现其它产品的业务,也将大几率的出现抢红包的需求。git
红包的场景不管怎么变化,其核心算法不变,这部分是能够抽象的内容,随着迭代发展,咱们以前一般都是经过增长红包的类型(业务)来扩展,可是随着肉眼可见的发展,部分业务的改动若是须要对红包业务进行调整和优化对话,将有可能产生牵一发而动全身的debuff效果。github
其实这些业务代码早该优化一下,我就是懒+忙(借口),正好有位新同事入职,这块的优化任务就交给他来作了,从头至尾我都没有参与(不知道有没有吐槽个人代码,捂脸~),我初步看了一下,代码的实现质量仍是挺高的,正好也是一个比较好的应用场景,我就简单实现一下他作的适配器模式,完全的将各个红包业务类型分离,很好的实现了设计模式的开闭原则,加入某天某个场景的抢红包业务下线了,这种作法是很是有利于业务的扩展和维护。算法
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 属性时,指定不一样的名字便可。在接口实现的方法中,各自的业务还须要执行不一样的业务检查,好比单聊红包就须要检查接收人是否存在,群聊红包还须要检查群是否存在,该群是否被冻结等等,直播红包须要检查主播是否在直播中,观众是否在直播房间内,这些都是不一样业务场景产生的特殊的业务处理需求。api
public void ConfigureServices(IServiceCollection services) { services.AddScoped(typeof(IRedPacket), typeof(ChatOneRedPacket)) .AddScoped(typeof(IRedPacket), typeof(ChatGroupRedPacket)) .AddScoped(typeof(IRedPacket), typeof(LiveRedPacket)); ... }
容器实例的建立很是简单,只须要将已实现 IRedPacket 接口的子类注册到服务管道便可。ide
[Route("api/[controller]")] [ApiController] public class HomeController : ControllerBase { private readonly IEnumerable<IRedPacket> redpackets; public HomeController(IEnumerable<IRedPacket> redpackets) { this.redpackets = redpackets; } }
经过创建一个控制台 HomeController 用于演示,在 HomeController 的构造方法中,使用 IEnumerable
[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中不一样的业务实体去调用发红包的接口,分别将结果输出到客户端this
// 单聊红包 { "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