使用反射+策略模式代替项目中大量的switch case判断
我这里的业务场景是根据消息类型将离线消息存入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.反射的效率较低,如果抽象策略类所在的程序集拥有的类较多时,反射效率较低的缺点就会比较明显。因为需要进行大量的循环遍历才能找到符合条件的具体策略类。