
“代码优化”是每个程序员都会经历的一个课题,提到优化,我最先想做的事就是干掉项目里的“if-else”。代码中,如果“if-else”比较多,不仅阅读起来比较困难,而且可维护性也会变差,如果后期在此基础上增加新的业务逻辑,会很容易产生出bug。
网上有很多名为:“别再if-else走天下了”,“教你干掉if-else”之类的文章,基本上都会提到策略模式的应用。我最开始也是跟着文章学习,用多了自然有了自己的理解。本讲,我就把使用“策略模式”优化“if-else”的经验历程分享一下。
“策略模式”核心就在于“策略”,实现的方式也大同小异:服务端定义统一行为(接口或抽象类),并实现不同策略下的处理逻辑(对应实现类)。更进一步的话,可以利用工厂类来统一处理客户端逻辑。
“策略模式”的UML类图如下:

接下来,我用实践中常遇到的业务场景,深入浅出的讲解一下:
假如,对于不同来源(pc端、mobile端)的支付订单需要不同的逻辑处理。通常,项目中会有一个实体类 Order 承载变量,还有一个专用的 OrderService 来处理该类订单业务,方法里面有一坨 if-else 的逻辑,目的是根据订单的来源的做不同的处理,如下:
- /**
- * 支付订单的实体类
- **/
- @Data
- public class Order {
- // 订单来源
- private String source;
- // 支付方式
- private String payWay;
- // 订单编号
- private String orderCode;
- // 订单金额
- private BigDecimal amount;
- // ... 其他字段
- }
-
- /**
- * 处理支付订单的服务
- * 为了简化代码,处理订单的服务没有使用接口设计
- **/
- @Service
- public class OrderService {
-
- public void orderMethod(Order order) {
- if(order.getSource().equals("pc")){
- // 处理pc端订单的逻辑
- }else if(order.getSource().equals("mobile")){
- // 处理移动端订单的逻辑
- }else {
- // ...其他逻辑
- }
- }
- }
“策略模式”就是要干掉上面的一坨if-else,使得代码看起来优雅且高大上,分析下以下几种方案:
这种方案是 “策略模式”最好的诠释,完全按照UML图来,先往下看,一会再说缺点。
首先定义一个OrderHandler接口,此接口规定了处理订单的方法。
- public interface OrderHandler {
- void handle(Order order);
- }
接下来,就是实现pc端和移动端订单处理各自的handler,重写各自的处理逻辑,互补干扰。
- @Service
- public class MobileOrderHandler implements OrderHandler {
- @Override
- public void handle(Order order) {
- System.out.println("处理移动端订单");
- }
- }
-
- @Service
- public class PCOrderHandler implements OrderHandler {
- @Override
- public void handle(Order order) {
- System.out.println("处理PC端订单");
- }
- }
Order工厂类的取代了“if-else”的操作:根据不同的参数,匹配不同的实体类,以便执行不同的业务逻辑。
- public class OrderServiceFactory {
-
- private static final Map
map = new HashMap<>(); -
- static {
- map.put("pc", new PCOrderHandler());
- map.put("mobile", new MobileOrderHandler());
- }
- // 对外提供的服务
- public static IOrderHandler getOrderService(String source) {
- return map.get(source);
- }
- }
用 Junit 测试类,简单模拟一下Controllor层的业务逻辑。
- @Test
- void payWayTest() {
- Order order = new Order();
- order.setSource("pc");
- OrderHandler orderHandler = OrderServiceFactory.getOrderService(order.getSource());
- orderHandler.handle(order);
- }
- // 输出:处理PC端订单
有没有发现一个问题:开发人员不仅要关注策略实现类的业务,还要在 OrderServiceFactory 工厂类中把用到的对象先配置出来,就好像是在维护一个配置文件。
- static {
- map.put("pc", new PCOrderHandler());
- map.put("mobile", new MobileOrderHandler());
- }
这种操作实际上是有悖于提升代码可读性、可维护性的。所以,我优先想到的就是用Enum取代工厂类,毕竟Enum的可读性更好。
- @Getter
- public enum OrderServiceEnum {
-
- PC("pc", new PCOrderHandler()),
- MOBILE("mobile", new MobileOrderHandler());
-
- private String name;
- private OrderHandler handler;
-
- OrderServiceEnum(String name, OrderHandler handler) {
- this.name = name;
- this.handler = handler;
- }
-
- // 定义public的获取方式
- public static OrderHandler getEnum(String name) {
- for (OrderServiceEnum e : OrderServiceEnum.values()) {
- if (e.getName().equals(name)) {
- return e.getHandler();
- }
- }
- return null;
- }
- }
用 Junit 测试类,简单模拟一下Controllor层的业务逻辑。
- @Test
- void enumTest() {
- Order order = new Order();
- order.setSource("pc");
- OrderHandler orderHandler = OrderServiceEnum.getEnum(order.getSource());
- orderHandler.handle(order);
- }
- // 输出:处理PC端订单
虽然使用Enum对可读性有所改进,但是还没有达到预期,我在考虑有没有一种可能:工厂类自动加载所有的策略实现,不需要外界干预。日后增加产品族时,只需要关注实现类本身,不需要修改工厂类代码。
我想到了“反射”,想到了“注解”,想到了利用“反射”自动加载所有实现类的“注解”,说干就干:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Service
- public @interface OrderHandlerType {
- String source();
- }
- @Service
- @OrderHandlerType(source = "mobile")
- public class MobileOrderHandler implements OrderHandler {
- @Override
- public void handle(Order order) {
- System.out.println("处理移动端订单");
- }
- }
-
- @Service
- @OrderHandlerType(source = "pc")
- public class PCOrderHandler implements OrderHandler {
- @Override
- public void handle(Order order) {
- System.out.println("处理PC端订单");
- }
- }
利用反射,自动获取 OrderHandler 接口下所有实现类,并且识别实现类注解上的 value 值,完成 map 自动装配,从此避免认为干涉。
- @Component
- public class OrderServiceFactory {
-
- private static Map
map = new HashMap<>(); -
- static {
- // 1.利用反射,获取IMedalService接口下所有实现类的class
- List
clazzs = getAllInterfaceAchieveClass(OrderHandler.class); - for(Class clazz : clazzs){
- // 2.获取class的MedalType注解的value值
- String type = AnnotationUtils.findAnnotation(clazz, OrderHandlerType.class).source();
- try {
- // 3.保存产品族为map对象
- map.put(type, (OrderHandler) clazz.newInstance());
- } catch (InstantiationException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 对外提供的服务
- * @return
- */
- public static OrderHandler getOrderService(String medalType) {
- return map.get(medalType);
- }
-
- /**
- * 获取所有接口的实现类
- */
- public static List
getAllInterfaceAchieveClass(Class clazz){ - ArrayList
list = new ArrayList<>(); - // 判断是否是接口
- if (clazz.isInterface()) {
- try {
- ArrayList
allClass = getAllClassByPath(clazz.getPackage().getName()); - /**
- * 循环判断路径下的所有类是否实现了指定的接口
- * 并且排除接口类自己
- */
- for (int i = 0; i < allClass.size(); i++) {
- // 排除抽象类
- if(Modifier.isAbstract(allClass.get(i).getModifiers())){
- continue;
- }
- // 判断是不是同一个接口
- if (clazz.isAssignableFrom(allClass.get(i))) {
- if (!clazz.equals(allClass.get(i))) {
- list.add(allClass.get(i));
- }
- }
- }
- } catch (Exception e) {
- System.out.println("出现异常");
- }
- }
- return list;
- }
-
- /**
- * 从指定路径下获取所有类
- */
- public static ArrayList
getAllClassByPath(String packagename){ - ArrayList
list = new ArrayList<>(); - ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- String path = packagename.replace('.', '/');
- try {
- ArrayList
fileList = new ArrayList<>(); - Enumeration
enumeration = classLoader.getResources(path); - while (enumeration.hasMoreElements()) {
- URL url = enumeration.nextElement();
- fileList.add(new File(url.getFile()));
- }
- for (int i = 0; i < fileList.size(); i++) {
- list.addAll(findClass(fileList.get(i),packagename));
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- return list;
- }
-
- /**
- * 如果file是文件夹,则递归调用findClass方法,或者文件夹下的类
- * 如果file本身是类文件,则加入list中进行保存,并返回
- */
- private static ArrayList
findClass(File file,String packagename) { - ArrayList
list = new ArrayList<>(); - if (!file.exists()) {
- return list;
- }
- File[] files = file.listFiles();
- for (File file2 : files) {
- if (file2.isDirectory()) {
- // 添加断言用于判断
- assert !file2.getName().contains(".");
- ArrayList
arrayList = findClass(file2, packagename+"."+file2.getName()); - list.addAll(arrayList);
- }else if(file2.getName().endsWith(".class")){
- try {
- // 保存的类文件不需要后缀.class
- list.add(Class.forName(packagename + '.' + file2.getName().substring(0,
- file2.getName().length()-6)));
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
- return list;
- }
- }
- @Test
- void orderTest() {
- Order order = new Order();
- order.setSource("pc");
- OrderHandler orderHandler = OrderServiceFactory.getOrderService(order.getSource());
- orderHandler.handle(order);
- }
- // 输出:处理PC端订单
使用“注解”的策略模式,可以让代码维护变的更简洁,工厂类可以封装起来,开发过程中,只需要关注策略实现就可以了。
说了这么多,难道使用“策略模式”就没有缺点吗?其实,缺点也很明显: