Как элегантно использовать шаблон стратегии, шаблон шаблона и шаблон фабрики в проектах

Шаблоны проектирования

Вы можете самостоятельно ознакомиться с основными понятиями, преимуществами и недостатками шаблона стратегии, шаблона шаблона и шаблона фабрики.Вот как элегантно использовать эти три шаблона для обеспечения соответствия сервиса: SRP (принцип единой ответственности) и OCP (открыто-закрытое). Principle), низкая связанность, высокая масштабируемость и сокращение множества сценариев кода if else.


Режим стратегии:


1.Роль среды (контекста):Содержит ссылку на стратегию.

2.Абстрактная стратегия (стратегия) роль:Это абстрактная роль, обычно реализуемая интерфейсом или абстрактным классом. Эта роль предоставляет все интерфейсы, требуемые конкретными классами политик.

3.Бетонная стратегическая роль:Обертывает связанные алгоритмы или поведение.


Возможно, вы использовали этот классический и простой шаблон стратегии, но у этого шаблона стратегии есть недостаток:

Режим стратегии подходит для многотипных сценариев.При вызове стратегии должно быть большое количество if else.Если в будущем необходимо использовать новый тип стратегии, если еще нужно добавить.Изменения кода большой, что приводит к отсутствию масштабируемости модуля.Высокий и будет генерировать много if else кода, не простой в обслуживании.


Чтобы лучше понять процесс оптимизации, сначала укажите предысторию требований:

Служба должна отображать для пользователей три различных графика тенденций: график тенденций изменения индекса, график тенденций количества новых обучающих программ и график тенденций количества новых комментариев.




Оптимизация модуля Раунд 1:

Чтобы решить вышеуказанные проблемы, используйте режим стратегии + пользовательскую аннотацию + режим шаблона (режим шаблона не имеет ничего общего с оптимизацией, только для нужд бизнеса) для проектирования:



Во-первых, требуется определение стратегии абстрактного родительского класса:

@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);
        return getValuesOperation(trendChartDataVos);
    }

    /**
     * 趋势图的每个点的数值处理(默认是返回与前七天平均值的差值)
     *
     * @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);
        });
        return newTrendChartDataVos;
    }

    @Autowired
    public AbstractTrendChartHandler(EducationManager educationManager) {
        this.educationManager = educationManager;
    }
}


можно увидеть,Метод handle(EducationQuery query) является унифицированным методом обработки, и подкласс может наследовать или использовать метод родительского класса по умолчанию.getTrendChartData(EducationQuery query) — это шаблонный метод, подкласс обязательно выполнит этот метод и может переопределить родительский класс.Метод getValuesOperation(List trendChartDataVos).


Три подкласса следующие:

@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.COURSEWARE_COUNT)
public class NewClassesHandler extends AbstractTrendChartHandler {

    @Autowired
    public NewClassesHandler(EducationManager educationManager) {
        super(educationManager);
    }
}

@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.COMMENT_COUNT)
public class NewCommentsHandler extends AbstractTrendChartHandler {

    @Autowired
    public NewCommentsHandler(EducationManager educationManager) {
        super(educationManager);
    }
}

@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);
        return trendChartDataVos;
    }

    @Autowired
    public PigeonsIndexHandler(EducationManager educationManager) {
        super(educationManager);
    }
}

Как видите, подклассNewClassesHandler иКласс NewCommentsHandler по умолчанию использует шаблон родительского класса для реализации логики этих двух графиков тенденций. иКласс PigeonsIndexHandler переписывает метод в шаблоне родительского класса, использует логику родительского класса, а затем использует логику, переписанную в подклассе.


Выше приведены правила политики, за которыми следует класс получения политики TrendChartHandlerContext:

@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);
    }
}

Этот класс используется для преобразования типа, переданного внешним интерфейсом, в объект подкласса.

Значение handlerMap в TrendChartHandlerContext — это значение в перечислении трех типов диаграмм трендов. Зависит от

Класс TrendChartHandlerProcessor единообразно сканирует значение пользовательской аннотации и единообразно помещает объекты типа и подкласса в handlerMap.


Используйте стратегию:

    /**
     * 查看指数/新增课件数/新增点评数走势图
     *
     * @param query
     * @return
     */
    @GetMapping("/charts")
    public Object queryDeviceBrand(@Validated(value = {EducationGroup.GetTrendChart.class}) EducationQuery query) {
        return ResultBuilder.create().ok().data(educationService.queryTrendChartData(query)).build();
    }

Реализация сервисной логики:

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

Видно, что при использовании стратегии вам нужно только вызвать метод-обработчик стратегии, вам не нужно обращать внимание на тип и избегать большого количества кода if else.



Инструменты:

@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) {
        return applicationContext.getBean(clazz);
    }
}

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));
        }

        return classes;
    }

    @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() {
        return this.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;
    }
}


Таким образом, даже если проблема класса if else будет решена, вы обнаружите, что нужно ввести множество инструментов, что увеличивает объем кода и выглядит неприглядно.


Оптимизация модуля Раунд 2:

Продолжение следует