我這里的業務場景是根據消息類型將離線消息存入mongoDB不同的collection中。其中就涉及到大量的分支判斷,為了增強代碼的可讀性和可維護性,對之前的代碼進行了重構。
先對比一下使用反射+策略模式前后的代碼:
重構前:
重構后:
我們可以看到重構前的代碼充斥着大量的分支判斷,以后每增加一個新的消息類型就要增加一個新的具體實現類和增加一個新的分支判斷,可拓展性是相當差的;而重構后的代碼當需要增加一個新的消息類型時,只需要增加一個具體的實現類就可以實現,根本不需要考慮分支判斷,這也是我們希望看到的。
接下來我們看一下具體的實現過程:
1.抽象策略類
定義了具體策略類需要執行的具體操作,並且對外部提供了一個觸發操作的方法。抽象策略類中還定義了兩個屬性LogSource和Operation,這樣mongoDB中的Collection名和消息枚舉類型Operation就形成一對一的關系。注意我們把這兩個屬性的set權限定義為protected ,屬性賦值操作在具體策略類實現。實現了不同的消息類型對應不同的具體策略類對應不同的mongo Collection。
1 public abstract class SaveOffLineMessageTemplate<T> 2 { 3 /// <summary> 4 /// 日志源和mongo表名 5 /// </summary> 6 public string LogSource { get; protected set; } 7 /// <summary> 8 /// 操作類型 9 /// </summary> 10 public Operation Operation { get; protected set; } 11 12 /// <summary> 13 /// 保存消息到mongoDB 14 /// </summary> 15 /// <param name="message">消息</param> 16 /// <param name="userRole">接收用戶角色</param> 17 /// <param name="messageSendTag">是否發送標志 0未發送 1已發送</param> 18 /// <returns></returns> 19 public async Task<bool> AddMessageToMongo(string message, UserRoleEnum userRole, int messageSendTag) 20 { 21 //消息反序列化 22 var model = DeserializeObject(message); 23 //獲取用戶設備集合 24 var devices = QueryUserDevice(model); 25 //組裝數據 26 var combineData = CombineData(model, devices, userRole); 27 //記錄日志 28 Log.MyLog.Info(LogSource + "AddMessageToMongo", "保存消息到mongoDB", JsonConvert.SerializeObject(model) + JsonConvert.SerializeObject(devices)); 29 //保存到mongoDB 30 return await MessageDB.AddOffLineMessage(combineData, LogSource); 31 } 32 /// <summary> 33 /// 消息反序列化 34 /// </summary> 35 /// <typeparam name="T">模板類</typeparam> 36 /// <param name="message">消息</param> 37 /// <returns></returns> 38 public abstract T DeserializeObject(string message); 39 40 /// <summary> 41 /// 獲取用戶設備集合 42 /// </summary> 43 /// <returns></returns> 44 public abstract List<DevicesMongoModel> QueryUserDevice(T model); 45 46 /// <summary> 47 /// 組裝數據 48 /// </summary> 49 /// <returns></returns> 50 public abstract MessageMongoModel CombineData(T model, List<DevicesMongoModel> devices, UserRoleEnum userRole, int messageSendTag); 51 }
2.具體策略類
實現了抽象策略類定義的操作。在此處給抽象策略類的消息操作類型Operation和LogSource進行賦值。
1 public class SaveLoginPasswordModify : SaveOffLineMessageTemplate<FinanceBase> 2 { 3 public SaveLoginPasswordModify() : base() 4 { 5 this.Operation = Operation.登陸密碼變更消息推送; 6 this.LogSource = "Retail"; 7 } 8 9 public override FinanceBase DeserializeObject(string message) 10 { 11 FinanceBase model = JsonConvert.DeserializeObject<FinanceBase>(message); 12 return model; 13 } 14 15 public override List<DevicesMongoModel> QueryUserDevice(FinanceBase model) 16 { 17 //設備編碼集合 18 List<DevicesMongoModel> devicesList = null; 19 //用戶信息 20 ClientsMongoModel mongoModel = MongoOper.QueryUserDevices(model.UserId); 21 22 if(mongoModel!=null && mongoModel.DeviceIdList!=null && mongoModel.DeviceIdList.Count>0) 23 { 24 //組裝設備編碼集合 25 devicesList = (from d in mongoModel.DeviceIdList 26 select new DevicesMongoModel 27 { 28 Device = d, 29 MessageSendTag = 0 30 }).ToList(); 31 } 32 return devicesList; 33 } 34 35 public override MessageMongoModel CombineData(FinanceBase model, List<DevicesMongoModel> devices, UserRoleEnum userRole, int messageSendTag) 36 { 37 MessageMongoModel mongoModel = new MessageMongoModel() 38 { 39 MessageId = model.MessageId, 40 MessageTitle = model.MsgTitle, 41 MessageContent = model.MsgContent, 42 MessageExtras = model.MessageExtras, 43 MessageType = model.MessageType, 44 UserId = model.UserId, 45 UserRole = userRole, 46 Devices = devices //設備集合 47 }; 48 return mongoModel; 49 } 50 }
3.環境類
定義了一個字典用於存放消息類型Operation和抽象策略類SaveOffLineMessageTemplate<T>的對應關系,字典Value中實際上存放的是具體的策略類(里氏替換原則)。這里就是把switch case中每個消息類型Operation及其對應的分支操作抽象為字典中一對一的關系。
然后利用反射動態的創建具體策略類實例並將其加入字典,每次請求過來時,都會匹配字典中是否存在以此次請求的消息類型Operation為key的項,如果存在就會執行抽象策略類中的AddMessageToMongo方法,實際上就是執行了具體策略類中的操作方法。這樣就間接實
現了switch case根據請求帶來的參數分發到不同的處理類。
此處應該注意的是反射的效率是比較低的,所以環境類SaveOffMessageToMongo<T>的構造函數應該設為static靜態的,保證只有第一次請求時才會執行反射創建對象,而之后的所有請求都不再創建對象。而switch case實現方式中每次請求都會創建對象。這也是使用反射+策略模式的一個優點,避免了創建實例過程中的資源和時間的消耗。
1 public class SaveOffMessageToMongo<T> 2 { 3 public static Dictionary<Operation, SaveOffLineMessageTemplate<T>> dicSaveOffMessage; 4 5 #region 利用反射+策略模式解決operation大量的switch case 6 /// <summary> 7 /// 利用反射+策略模式解決operation大量的switch case 8 /// </summary> 9 static SaveOffMessageToMongo() 10 { 11 //1.創建一個字典用於存放 消息類型-具體策略 12 dicSaveOffMessage = new Dictionary<Operation, SaveOffLineMessageTemplate<T>>(); 13 //2.獲取類型的 System.Type 對象 14 Type abjType = typeof(SaveOffLineMessageTemplate<T>); 15 //3.獲取此類型所在的程序集 16 Assembly assem = abjType.Assembly; 17 //4.遍歷獲取此程序集中所有的類 18 foreach (Type t in assem.GetTypes()) 19 { 20 //5.是類並且不是抽象類並且繼承自抽象策略類(只有具體策略類符合) 21 if (t.IsClass && !t.IsAbstract && t.IsSubclassOf(abjType)) 22 { 23 //6.如果符合就創建一個具體策略類的實例,並裝換為抽象策略類類型 24 SaveOffLineMessageTemplate<T> template = Activator.CreateInstance(t) as SaveOffLineMessageTemplate<T>; 25 //7.如果字典中不存在以實例的消息類型Operation為key的項,就添加至字典 26 if (template != null && !dicSaveOffMessage.ContainsKey(template.operation)) 27 { 28 dicSaveOffMessage.Add(template.operation, template); 29 } 30 } 31 } 32 } 33 #endregion 34 35 #region 添加消息到MongoDB 36 /// <summary> 37 /// 添加消息到MongoDB 38 /// </summary> 39 /// <param name="message">消息</param> 40 /// <param name="operation">操作類型</param> 41 /// <param name="userRole">用戶類型</param> 42 /// <param name="messageSendTag">消息發送標志</param> 43 public static void AddMessageToMongo(string message, Operation operation, UserRoleEnum userRole, int messageSendTag = 0) 44 { 45 //8.如果字典中存在以operation為key的項,就調用對應的抽象策略類中的AddMessageToMongo方法 46 if (dicSaveOffMessage.ContainsKey(operation)) 47 { 48 dicSaveOffMessage[operation].AddMessageToMongo(message, userRole, messageSendTag); 49 } 50 } 51 #endregion 52 }
總結:
使用反射+策略模式代替項目中大量的switch case判斷的優點:
1.代碼的擴展性好,當有新的消息類型需要處理時,只需要添加一個具體策略類進行處理即可,完全不必關心環境類的實現。
2.避免了創建實例過程中的資源和時間的消耗。
缺點:
1.反射的效率較低,如果抽象策略類所在的程序集擁有的類較多時,反射效率較低的缺點就會比較明顯。因為需要進行大量的循環遍歷才能找到符合條件的具體策略類。