咱們公司現有一塊業務叫作搶紅包,最初的想法只是實現了一個初代版本,就是給指定的好友單發紅包,隨着業務的發展,發紅包和搶紅包的場景也愈來愈多,目前主要應用的場景有:單聊發紅包、羣聊發紅包、名片發紅包、直播場景中的主播發紅包/觀衆給主播發紅包/定時搶紅包,接下來,若是出現其它產品的業務,也將大機率的出現搶紅包的需求。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