About the strategy pattern and template pattern and the basic concept of the factory pattern and to understand the advantages and disadvantages, here basically is about how to use these three models to ensure service gracefully: SRP (single responsibility principle) and the OCP (open closed principle), low coupling, high extensibility, and reduce a lot of the if else code scenario.


Strategy mode:



1. Context role: Holds a reference to a Strategy.

2. Abstract Strategy Role: This is an abstract role, usually implemented by an interface or abstract class. This role gives all the interfaces required by the specific policy classes.

3. ConcreteStrategy roles: Wrap related algorithms or behaviors.


This is a classic and simple strategy pattern that you may have already used, but there is a disadvantage to this strategy pattern:

The policy mode is applicable to multi-type scenarios. There will be a large number of if else when calling the policy. If else needs to be added later when a new type of policy needs to be used, the code changes greatly, resulting in low scalability of the module and a large number of IF else code, which is difficult to maintain.


In order to better experience the process of optimization, first give a requirement background:

A service needs to show users three different trend charts: index change trend chart, new courseware number trend chart and new comment number trend chart.




Module optimization Round 1:

To solve the above problems, use the policy pattern + custom annotations + template pattern (template pattern has nothing to do with optimization, just business needs) to design:




First we need to abstract the definition of the parent class policy:

@Slf4j
public abstract class AbstractTrendChartHandler {

    private final EducationManager educationManager;

    public List<TrendChartDataVo> handle(EducationQuery query){
        return getTrendChartData(query);
    }

    private List<TrendChartDataVo> getTrendChartData(EducationQuery query) {
        DateRangeQueryVo dateRangeQueryVo = new DateRangeQueryVo();
        dateRangeQueryVo.setStartDate(LocalDate.now().minusWeeks(TrendChartConstant.towMonthsWeeks));
        dateRangeQueryVo.setEndDate(LocalDate.now());
        List<TrendChartDataVo> trendChartDataVos = educationManager.getTrendChartData(query.getAreaCode(),
                AreaRankingType.Companion.toAreaRankingType(query.getAreaRankingType()), dateRangeQueryVo);
        returngetValuesOperation(trendChartDataVos); } /** * Numerical processing of each point of the trend chart (default is to return the difference from the average value of the previous seven days) ** @param trendChartDataVos * @return
     */
    protected List<TrendChartDataVo> getValuesOperation(List<TrendChartDataVo> trendChartDataVos) {
        if (CollectionUtils.isEmpty(trendChartDataVos) || trendChartDataVos.size() < TrendChartConstant.towMonthsWeeks) {
            return new ArrayList<>();
        }
        List<TrendChartDataVo> newTrendChartDataVos = new ArrayList<>();
        IntStream.range(0, trendChartDataVos.size()).forEach(i -> {
            if (i == 0){
                return;
            }
            TrendChartDataVo trendChartDataVo = new TrendChartDataVo();
            trendChartDataVo.setDate(trendChartDataVos.get(i).getDate());
            trendChartDataVo.setValue(trendChartDataVos.get(i).getValue() - trendChartDataVos.get(i - 1).getValue());
            newTrendChartDataVos.add(trendChartDataVo);
        });
        returnnewTrendChartDataVos; } @Autowired public AbstractTrendChartHandler(EducationManager educationManager) { this.educationManager = educationManager; }}Copy the code


As you can see, the Handle (EducationQuery Query) method is a unified processing method, and subclasses can inherit or default to parent methods. GetTrendChartData (EducationQuery Query) is a template method that must be executed by subclasses, You can also override the getValuesOperation(List
trendChartDataVos) method of the parent class.



The three subclasses are as follows:

@Service @Slf4j @TrendChartType(TrendChartTypeEnum.COURSEWARE_COUNT) public class NewClassesHandler extends AbstractTrendChartHandler { @Autowired public NewClassesHandler(EducationManager educationManager) { super(educationManager); }}Copy the code

@Service @Slf4j @TrendChartType(TrendChartTypeEnum.COMMENT_COUNT) public class NewCommentsHandler extends AbstractTrendChartHandler { @Autowired public NewCommentsHandler(EducationManager educationManager) { super(educationManager); }}Copy the code

@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.SCHOOL_INDEX)
public class PigeonsIndexHandler extends AbstractTrendChartHandler {

    public List<TrendChartDataVo> getValuesOperation(List<TrendChartDataVo> trendChartDataVos) {
        if (trendChartDataVos.size() <= TrendChartConstant.towMonthsWeeks) {
            return new ArrayList<>();
        }
        trendChartDataVos.remove(0);
        returntrendChartDataVos; } @Autowired public PigeonsIndexHandler(EducationManager educationManager) { super(educationManager); }}Copy the code

As you can see, the subclasses NewClassesHandler and NewCommentsHandler use the parent’s template by default, implementing the logic of both trend charts. The PigeonsIndexHandler class overrides the methods in the parent’s template, using the logic from the parent and overriding the logic from the subclass.



This is the policy rule, followed by the TrendChartHandlerContext class:

@SuppressWarnings("unchecked")
public class TrendChartHandlerContext {

    private Map<Integer, Class> handlerMap;

    TrendChartHandlerContext(Map<Integer, Class> handlerMap) {
        this.handlerMap = handlerMap;
    }

    public AbstractTrendChartHandler getInstance(Integer type) {
        Class clazz = handlerMap.get(type);
        if (clazz == null) {
            throw new IllegalArgumentException("not found handler for type: " + type);
        }
        return(AbstractTrendChartHandler) BeanTool.getBean(clazz); }}Copy the code

This class is used to convert a type passed in front to a subclass object.

The value of handlerMap in TrendChartHandlerContext is the value in the enumeration of the three trend chart types. by

TrendChartHandlerProcessor class unified scan the value of custom annotations and unified in handlerMap type and subclass object.


Use strategies:

/** * query * @param query * @return
     */
    @GetMapping("/charts")
    public Object queryDeviceBrand(@Validated(value = {EducationGroup.GetTrendChart.class}) EducationQuery query) {
        return ResultBuilder.create().ok().data(educationService.queryTrendChartData(query)).build();
    }Copy the code

Service logic implementation:

    public List<TrendChartDataVo> queryTrendChartData(EducationQuery query) {
        return trendChartHandlerContext.getInstance(query.getAreaRankingType()).handle(query);
    }Copy the code

As you can see, when using a policy, you only need to call the handler method of the policy, without paying attention to type, avoiding a lot of if and else code.



Tools:

@Component
public class BeanTool implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        if (applicationContext == null) {
            applicationContext = context;
        }
    }

    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        returnapplicationContext.getBean(clazz); }}Copy the code

public class ClassScaner implements ResourceLoaderAware { private final List<TypeFilter> includeFilters = new LinkedList<>(); private final List<TypeFilter> excludeFilters = new LinkedList<>(); private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); @SafeVarargs public static Set<Class<? >> scan(String[] basePackages, Class<? extends Annotation>... annotations) { ClassScaner cs = new ClassScaner();if (ArrayUtils.isNotEmpty(annotations)) {
            for(Class anno : annotations) { cs.addIncludeFilter(new AnnotationTypeFilter(anno)); } } Set<Class<? >> classes = new HashSet<>();for (String s : basePackages) {
            classes.addAll(cs.doScan(s));
        }

        returnclasses; } @SafeVarargs public static Set<Class<? >> scan(String basePackages, Class<? extends Annotation>... annotations) {return ClassScaner.scan(StringUtils.tokenizeToStringArray(basePackages, ",; \t\n"), annotations);
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils
                .getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(
                resourceLoader);
    }

    public final ResourceLoader getResourceLoader() {
        returnthis.resourcePatternResolver; } public void addIncludeFilter(TypeFilter includeFilter) { this.includeFilters.add(includeFilter); } public void addExcludeFilter(TypeFilter excludeFilter) { this.excludeFilters.add(0, excludeFilter); } public void resetFilters(boolean useDefaultFilters) { this.includeFilters.clear(); this.excludeFilters.clear(); } public Set<Class<? >>doScan(String basePackage) { Set<Class<? >> classes = new HashSet<>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + org.springframework.util.ClassUtils .convertClassNameToResourcePath(SystemPropertyUtils .resolvePlaceholders(basePackage)) +"/**/*.class";
            Resource[] resources = this.resourcePatternResolver
                    .getResources(packageSearchPath);

            for (int i = 0; i < resources.length; i++) {
                Resource resource = resources[i];
                if (resource.isReadable()) {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    if ((includeFilters.size() == 0 && excludeFilters.size() == 0) || matches(metadataReader)) {
                        try {
                            classes.add(Class.forName(metadataReader
                                    .getClassMetadata().getClassName()));
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "I/O failure during classpath scanning", ex);
        }
        return classes;
    }

    protected boolean matches(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(metadataReader, this.metadataReaderFactory)) {
                return false; }}for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, this.metadataReaderFactory)) {
                return true; }}return false; }}Copy the code


That’s enough to fix the if else class, but you’re going to find that there’s a lot of tools to introduce, a lot of code to add, and it’s not going to look pretty.


Module optimization Round 2:

To be continued