Вы можете самостоятельно ознакомиться с основными понятиями, преимуществами и недостатками шаблона стратегии, шаблона шаблона и шаблона фабрики.Вот как элегантно использовать эти три шаблона для обеспечения соответствия сервиса: 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
Три подкласса следующие:
@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:
Продолжение следует