策略模式&模板模式&工廠模式 如何優雅地用在項目中

關於策略模式、模板模式和工廠模式的基礎概念和優缺點能夠自行了解一下,這裏主要講的是如何優雅地使用這三種模式保證服務符合:SRP(單一職責原則)和OCP(開閉原則)、耦合度低、可擴展性高和減小大量if else代碼的場景。前端


策略模式:es6


1.環境(Context)角色:持有一個Strategy的引用。算法

2.抽象策略(Strategy)角色:這是一個抽象角色,一般由一個接口或抽象類實現。此角色給出全部的具體策略類所需的接口。spring

3.具體策略(ConcreteStrategy)角色:包裝了相關的算法或行爲。bash


這種的經典並簡單的策略模式你們也許已經使用過了,可是這樣的策略模式會有一個缺點:app

策略模式適用於多類型場景,調用策略時一定會有大量的if else,後續若有新的類型的策略須要被使用時則須要增長if else,代碼改動較大,從而致使該模塊可擴展性不高且會產生大量if else代碼,不易維護。ide


爲更好體驗到優化的過程,首先給一個需求背景:工具

某服務須要展現給用戶三種不一樣的趨勢圖:指數變化趨勢圖、新增課件數趨勢圖、新增點評數趨勢圖。優化




模塊優化Round 1:ui

爲了解決以上的問題,使用策略模式+自定義註解+模板模式(模板模式和優化無關,只是業務須要)來設計一下:



首先須要抽象父類策略的定義:

@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<TrendChartDataVo> 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);
    }
}複製代碼

該類用於將前端傳入的type轉爲子類對象。

TrendChartHandlerContext中的handlerMap的值則爲這三種趨勢圖的類型的枚舉中的值。由

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();
    }複製代碼

service邏輯實現:

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

能夠看到,使用策略時只須要調用策略的handler方法便可,無需關注type,規避掉大量的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的問題了,可是你們會發現有大量的工具須要引入,增長了許多代碼量,看上去會不夠美觀。


模塊優化Round 2:

未完待續

相關文章
相關標籤/搜索