用枚举类写策略模式,codeReview时被“喷”了

前言

策略模式在日常开发经常使用,但是每个人的写法不一样,之前用枚举类写策略模式做充值卡推荐逻辑实现,大家看到这种写法,感觉很新奇,这里简单总结下策略模式的几种写法。我们简单以学生排序展示作为demo,前端根据age,name,score来进行排序展示。

经典写法

先定义一个排序接口

public interface SortStudent {
    List<Student> sort(List<Student> studentSource);
}

根据年龄排序

/**
 * @author 爱琴孩
 */
public class SortStudentByAge implements SortStudent {
    @Override
    public List<Student> sort(List<Student> studentSource) {
        return studentSource.stream().sorted(Comparator.comparing(Student::getAge))
                .collect(Collectors.toList());
    }
}

根据成绩分数排序

/**
 * @author 爱琴孩
 */
public class SortStudentByScore implements SortStudent {
    @Override
    public List<Student> sort(List<Student> studentSource) {
        return studentSource.stream().sorted(Comparator.comparing(Student::getScore))
                .collect(Collectors.toList());
    }
}

根据姓名排序

/**
 * @author 爱琴孩
 */
public class SortStudentByName implements SortStudent {
    @Override
    public List<Student> sort(List<Student> studentSource) {
        return studentSource.stream().sorted(Comparator.comparing(Student::getName))
                .collect(Collectors.toList());
    }
}

构建一个上下文类

/**
 * @author 爱琴孩
 */
public class SortStudentContext {

    private SortStudent sortStudent;

    public SortStudentContext(SortStudent sortStudent) {
        this.sortStudent = sortStudent;
    }

    public SortStudentContext() {
    }

    public List<Student> sortStudent(List<Student> studentList) {
        return sortStudent.sort(studentList);
    }
}

业务调用方法

    /**
     * 经典策略模式
     *
     * @param sortType 排序模式
     * @return java.util.List<com.example.study.model.Student>
     * @author 爱琴孩
     */
    public List<Student> testStrategy(String sortType) {
        SortStudentContext sortStudentContext;
        switch (sortType) {
            case "sortByName":
                sortStudentContext = new SortStudentContext(new SortStudentByName());
                break;
            case "sortByScore":
                sortStudentContext = new SortStudentContext(new SortStudentByScore());
                break;
            default:
                sortStudentContext = new SortStudentContext(new SortStudentByAge());
        }
        return sortStudentContext.sortStudent(students);

    }

我们可以看到经典方法,创建了一个接口、三个策略类,还是比较啰嗦的。调用类的实现也待商榷,新增一个策略类还要修改榜单实例(可以用抽象工厂解决,但是复杂度又上升了)。加之我们有更好的选择,所以此处不再推荐经典策略模式。

枚举策略类写法

/**
 * @author 爱琴孩
 */
public enum SortStudentEnum {
    SortByAge {
        @Override
        public List<Student> sortStudents(List<Student> studentList) {
            return studentList.stream().sorted(Comparator.comparing(Student::getAge)).
                    collect(Collectors.toList());
        }
    },
    SortByName {
        @Override
        public List<Student> sortStudents(List<Student> studentList) {
            return studentList.stream().sorted(Comparator.comparing(Student::getName)).
                    collect(Collectors.toList());
        }
    },
    SortByScore {
        @Override
        public List<Student> sortStudents(List<Student> studentList) {
            return studentList.stream().sorted(Comparator.comparing(Student::getScore)).
                    collect(Collectors.toList());
        }
    };

    public abstract List<Student> sortStudents(List<Student> studentList);
}

业务调用方法

    /**
     * 枚举类策略模式
     *
     * @param sortType 排序模式
     * @return java.util.List<com.example.study.model.Student>
     * @author 爱琴孩
     */
    public List<Student> testStrategyByEnum(String sortType) {
        SortStudentEnum sortStudentEnum;
        try {
            sortStudentEnum = Enum.valueOf(SortStudentEnum.class, sortType);
        } catch (IllegalArgumentException e) {
            throw e;
        }
        return sortStudentEnum.sortStudents(students);
    }

可以看到,如果策略简单的话,基于枚举的策略模式优雅许多,调用方也做到了0修改,但正确地使用枚举策略模式需要额外考虑以下几点。

  • 枚举的策略类是公用且静态,这意味着这个策略过程不能引入非静态的部分,扩展性受限
  • 策略模式的目标之一,是优秀的扩展性和可维护性,最好能新增或修改某一策略类时,对其他类是无改动的。而枚举策略如果过多或者过程复杂,维护是比较困难的,可维护性受限

基于工厂的策略模式

为了解决良好的扩展性和可维护性,我更推荐以下利用spring自带beanFactory的优势,实现一个基于工厂的策略模式。策略类改动只是添加了@Service注解,并指定了Service的value属性

/**
 * @author 爱琴孩
 */
@Service("SortByAge")
public class SortStudentByAge implements SortStudent {
    @Override
    public List<Student> sort(List<Student> studentSource) {
        return studentSource.stream().sorted(Comparator.comparing(Student::getAge))
                .collect(Collectors.toList());
    }
}


/**
 * @author 爱琴孩
 */
@Service("SortByName")
public class SortStudentByName implements SortStudent {
    @Override
    public List<Student> sort(List<Student> studentSource) {
        return studentSource.stream().sorted(Comparator.comparing(Student::getName))
                .collect(Collectors.toList());
    }
}

/**
 * @author 爱琴孩
 */
@Service("SortByScore")
public class SortStudentByScore implements SortStudent {
    @Override
    public List<Student> sort(List<Student> studentSource) {
        return studentSource.stream().sorted(Comparator.comparing(Student::getScore))
                .collect(Collectors.toList());
    }
}

业务调用方法

@Service
public class SpringTestService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SpringTestService.class);

    private static List<Student> students;

    @Resource
    private Map<String, SortStudent> sortStudentMap;


    static {
        students = new ArrayList<Student>() {
   
   {
            add(new Student("霸都爱琴孩", 23, 99));
            add(new Student("青山剑客", 26, 99));
            add(new Student("西域刀郎", 43, 89));
            add(new Student("西湖法海", 33, 98));
            add(new Student("华山令狐冲", 22, 100));
        }};
    }

    /**
     * 通过Spring工厂来实现策略模式
     * @author 爱琴孩
     * @param sortType 排序类型
     * @return java.util.List<com.example.study.model.Student>
     */
    public List<Student> testStrategyByFactory(String sortType) {
        if (!sortStudentMap.containsKey(sortType)) {
            throw new ParameterException(RetCode.PARAM_ERROR.getCode());
        }
        LOGGER.info("sortStudentMap ={}", JSONObject.toJSON(sortStudentMap));
        SortStudent sortStudent = sortStudentMap.get(sortType);
        return sortStudent.sort(students);
    }
}

  @Resource
   private Map<String, SortStudent> sortStudentMap;

上面这种写法使用了Spring的注解方式进行依赖注入。其中,@Resource注解用于标注需要注入的组件,Map<String, SortStudent> sortStudentMap则表示将Spring容器中所有类型为SortStudent的Bean注入到该Map中,并以Bean的name作为key存储。具体原理如下:

  1. Spring容器在启动时会将所有带有@Component、@Service、@Repository和@Controller注解的类实例化成Bean,并以Bean的name存储到容器中。
  2. 当使用@Resource注解进行依赖注入时,Spring通过类型匹配的方式,在容器中查找与该属性类型相同的Bean,将其注入到对应的属性中。
  3. 对于一个Map类型的属性,Spring将会将所有类型相同的Bean注入到该Map中,并以Bean的name作为key存储。

总结

经典模式显然不合适了,如果逻辑比较简单,比如说文中这种简单的排序展示,并且策略比较多的时候,选用枚举策略模式是很合适的,这样可以减少策略类,避免造成类泛滥。如果每个策略的业务逻辑比较复杂,策略类比较少,那么结合spring 的工厂模式是比较合适的。

猜你喜欢

转载自blog.csdn.net/qq_28165595/article/details/131345238