策略模式的实际应用

平时学习设计模式应用的地方有限,只有阅读一些框架源码的时候可以比较频繁的见到。最近正好有一个业务场景符合策略模式。

业务场景

搜索某一种物品以及根据规则搜索其相似物品,一共有三种搜索规则 SA,SB,SC。

通常可以使用 switch 或者 if else 结合枚举实现,如果后期增加新的搜索策略,有可能会出现遗漏的情况,可维护性差,代码冗余可读性差。所以我决定采用策略模式实现。

实现

我们采用 guava 提供的 immutableMap 存储策略,key 为对应的策略枚举,value 为对应的方法。

这里可以思考一下如何将方法存入 value。

Guava ImmutableMap

根据guava文档所描述的,ImmutableMap是一个不可变的Map对象,其构造器如下所示,用于创建不可变Map实例。Guava提供了每个java.util的不可变版本。使用 ImmutableMap 映射 。每当我们尝试修改它时,它都会抛出 UnsupportedOperationException。

为什么需要不可变的集合呢?

  1. 保证线程安全:在并发程序中,使用Immutable既保证线程安全性,也大大增强了并发时的效率(跟并发锁方式相比)。尤其当一个对象是值对象时,更应该考虑采用Immutable方式;
  2. 被不可信的类库使用时会很安全;
  3. 如果一个对象不需要支持修改操作(mutation),将会节省空间和时间的开销;经过分析,所有不可变的集合实现都比可变集合更加有效地利用内存;
  4. 可以当作一个常量来对待,并且这个对象在以后也不会被改变。 将一个对象复制一份成immutable的,是一个防御性编程技术。

ImmutableMap 完全符合我们策略模式存储搜索模式的需求

Enum

枚举类型定义策略类型,后期有新加的类型便于维护,遵循 OOP 开闭原则。

public enum SearchType {
    SA('1', 'sa'),
    SB('2', 'sb'),
    SC('3', 'sc');

    private static final Map<String, String> codeTypeMap =
        Stream.of(values()).collect(
        Collectors.toMap(CompoundSearchType::getCode, CompoundSearchType::getType)
    );

    private String code;
    private String type;

    SearchType(String code, String type) {
        this.code = code;
        this.type = type;
    }

    public static String getTypeByCode(String code) {
        return codeTypeMap.get(code);
    }

    // Getter and Setter
}

Function

采用函数式编程存储方法至 Map(本质是实现匿名内部类),使用方法的时候再传入参数

public class Strategy {
    /**
 	 * 有参有返回值接口
 	 */
    @FunctionalInterface
    public interface Function<T, R, E extends Throwable> {
        R apply(T t) throws E;
    }
}

Initialize

业务代码中使用

@Service
public class Service implements IService {
    // initialize strategy map
    private final Map<String, Strategy.Function> searchStrategyMap = ImmutableMap.<String, Strategy.Function>builder()
        .put(SearchType.SA.getType(), (Strategy.Function<SearchFilter, List<String>, Exception>) filter -> searchA(filter))
        .put(SearchType.SB.getType(), (Strategy.Function<SearchFilter, List<String>, Exception>) filter -> searchB(filter))
        .put(SearchType.SC.getType(), (Strategy.Function<SearchFilter, List<String>, Exception>) filter -> searchC(filter))
        .build();
    
    @Override
    public List<String> strategySearch(SearchType searchType, String filter) {
        Strategy.Function function = searchStrategyMap.get(searchType.getType());
        if (ObjectUtils.isEmpty(function)) throw new RuntimeExcepiton("查询类型不存在");
        return (List<String>) function.apply(filter);
    }
    
    private List<String> searchA(String filter) {
        // Strategy A
        return list;
    }
    
        
    private List<String> searchB(String filter) {
        // Strategy B
        return list;
    }
    
        
    private List<String> searchC(String filter) {
        // Strategy C
        return list;
    }
}

小结

​ 这样代码是不是清爽了很多,网上许多结合 Spring 的策略模式都是利用注解加多态实现的,针对稍微有些规模的设计可以使用,比如文件存储策略,或者全文检索策略可以使用。但是针对这种中小型业务代码没有必要大动干戈,可以采用 map 结合 lambda 表达式小规模的使用,后期如果进行代码优化也比较方便。设计模式的应用不仅可以大到系统架构级别的设计,也可以应用到平时一些小的业务场景,最重要的是合适的场景选择合适的方法。