经常看到有人把软件开发和建筑行业类比,把架构师比作工程师,个人觉得还是有一定区别的,建筑行业的成品一旦落成,是不变的,而软件面向的是变化。但相同点是,如果内心有一种现在大力鼓吹的“匠人精神”,都会让作品更优美吧。建筑行业不太了解不做过多评论,作为码农一枚,如果能时常审视自己代码,反思设计的是不是美观,代码是不是流畅,相信是会写出好的作品的。而设计模式往往可以帮助我们写出更精致的作品。
【一】需求引出问题
最近需要改造一个现有的开源CRM系统,其中一段代码是下面这个样子:
String viewName;
switch (type) {
case 1:
viewName = "leadsview";
break;
case 2:
viewName = "customerview";
break;
case 3:
viewName = "contactsview";
break;
case 4:
viewName = "productview";
break;
case 5:
viewName = "businessview";
break;
case 6:
viewName = "contractview";
break;
case 7:
viewName = "receivablesview";
break;
case 8:
viewName = "customerview";
break;
default:
return R.error("type不符合要求");
}
String from;
if (2 == type) {
from = "from " + viewName + whereSb.toString() + " and owner_user_id is not null order by " + viewName + ".update_time desc";
} else if (8 == type) {
from = "from " + viewName + whereSb.toString() + " and owner_user_id is null order by " + viewName + ".update_time desc";
} else {
from = "from " + viewName + whereSb.toString() + " order by " + viewName + ".update_time desc";
}
if (StrUtil.isNotEmpty(basePageRequest.getJsonObject().getString("excel"))) {
return R.ok().put("data", Db.find("select * " + from));
}
if (2 == type || 8 == type) {
Integer configType = Db.queryInt("select type from 72crm_admin_customer_setting");
if (1 == configType && 2 == type) {
return R.ok().put("data", Db.paginate(basePageRequest.getPage(), basePageRequest.getLimit(),
"select * , (TO_DAYS(update_time) + (SELECT followup_day FROM 72crm_admin_customer_setting) - TO_DAYS(NOW())) as pool_day ,(select count(*) from 72crm_crm_business as a where a.customer_id = " + viewName + ".customer_id) as business_count", from));
} else {
return R.ok().put("data", Db.paginate(basePageRequest.getPage(), basePageRequest.getLimit(),
"select *,(select count(*) from 72crm_crm_business as a where a.customer_id = " + viewName + ".customer_id) as business_count", from));
}
}
return R.ok().put("data", Db.paginate(basePageRequest.getPage(), basePageRequest.getLimit(), "select *", from));
这段代码的大致意思就是,所有获取列表信息的接口,都会走到该方法,根据type的不同,分别到不同的视图中进行查询,暂且先不说视图的性能问题,单是这满屏的switch…case…和if···else···语句,已经足够引起了我们对它的重视。
【二】策略模式介绍
官方定义:
定义一组算法,将每个算法封装起来,并且可以使他们互换,属于对象行为模式。
使用时机
当一个类中的操作出现多个条件分支,就可以考虑使用策略模式,将相关条件分支移入各自具体策略类。
优缺点:
优点 | 缺点 |
---|---|
算法自由切换 | 策略类数量可能很多 |
避免多重条件判断 | 所有策略类需要对外暴露 |
扩展性好 |
关于上述提到的缺点,我们可以通过其他设计模式进行调整,例如工厂模式,代理模式等,后面会提到。
角色:
- Context:上下文环境角色,封装作用,屏蔽高层模块对策略和算法的直接访问
- Strategy:抽象策略角色,定义公共策略方法,通常由一个接口或抽象类实现
- ConcreteStrategy:具体策略角色:实现具体策略算法,封装相关算法和行为
当你读完这段话,有没有一种茫然感,仿佛刚刚读的是天书,设计模式往往就是这样。我来尝试通俗的解释一下整个过程,如果你有一个长长的 if···else··· 语句,就可以尝试把每个判断分支中的逻辑单独抽到一个类中,这就是ConcreteStrategy类;让他们实现同一个接口,就是Strategy接口;Context中有一个Strategy的引用,最终外界通过调用Context来对ConcreteStrategy访问。
下面是一个简单策略模式的UML类图:
思考:
借用了代理模式思路,但与代理模式存在一定的区别:策略模式封装的角色和被封装的策略类不用同一个接口;如果使用同一接口,就是代理模式。
【三】在实际项目中的扩展
在基本的策略模式中,选择哪个具体实现是由客户端对象承担,本身并没有减少客户端选择判断的压力,可以选择与简单工厂模式结合,选择哪个具体实现的事情也可以由Context承担,就减轻了客户端的职责。一般在具体策略数量超过4个时,就可以考虑使用混合模式。
在这里,由于原系统使用的是JFinal框架,而我们对他都不了解,所以实现的方式是,在context类中提供一个Map,把if···else···语句进行抽取,如果在Spring中,直接写一个接口,这个接口的实现类,会在加载的时候直接注入到上下文中,只需要根据不同的策略,取出这个Bean就可以了。
Context类部分代码:
private static final HashMap<Integer, Class> QUERY_WRAPPER_HASH_MAP = new HashMap<>(2);
static{
QUERY_WRAPPER_HASH_MAP.put(EntityTypeConstant.CUSTOMER_ENTITY_TYPE, new CustomerQueryWrapper().getClass());
QUERY_WRAPPER_HASH_MAP.put(EntityTypeConstant.ORDER_ENTITY_TYPE, new OrderQueryWrapper().getClass());
}
public IEntityService getService(Integer type){
if (customerService.getType().equals(type)){
return customerService;
}
else if (orderService.getType().equals(type)){
return orderService;
}
throw new RuntimeException("未找到对应的type类型");
}
客户端代码
return R.ok().put("data", entityContext.getService(type).getPageList(basePageRequest.getPage(),basePageRequest.getLimit(),entityContext.getQueryWrapper(type,jsonObjectList)));
下面是改造后的UML类图:
【四】总结
策略模式在虽然项目中的使用场景很多,但很少单独使用,因为它需要暴露出所有策略,选择策略还是在客户端完成,这样并没有达到最终目的。实际项目中一般通过工厂方法实现策略类的声明。