设计模式实战-策略模式,想让你的代码变幻莫测吗?

时间:2022-07-22
本文章向大家介绍设计模式实战-策略模式,想让你的代码变幻莫测吗?,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1、定义

策略模式(Strategy Pattern:Define a family of algorithms,encapsulate each one,and make them interchangeable.)中文解释为:定义一组算法,然后将这些算法封装起来,以便它们之间可以互换,属于一种对象行为型模式。总的来说策略模式是一种比较简单的模式,听起来可能有点费劲,其实就是定义一组通用算法的上层接口,各个算法实现类实现该算法接口,封装模块使用类似于 Context 的概念,Context 暴漏一组接口,Context 内部接口委托到抽象算法层。

大家在实际编程中,可能会用到 TreeSet 这种对象,TreeSet 构造时可以传入一个排序实现类以便指定集合元素被遍历时的顺序,当然不传使用默认的自然排序,如下,我们定义一个 TreeSet 并指定排序规则为自然排序的逆序:

TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        // 使用自然排序的逆序排列
        return o2.compareTo(o1);
    }
});
treeSet.add("lily");
treeSet.add("yerkim");
treeSet.add("admin");

for (String s : treeSet) {
    System.out.println(s); // yerkim lily admin
}

结果比较明显,依次打印:yerkim lily admin,为什么要提到 TreeSet 这种数据结构,其实策略模式有点类似这种,我们上面所说的通用算法接口就好比 compare 接口,具体算法实现类就好比我们上面自行实现的排序类,而所谓的 Context 就好比一个调用入口,隔离底层算法实现。

2、组成角色

策略模式的通用类图如下:

包含的角色罗列如下:

  • 上下文角色(Context):该角色一般是一个实现类或者封装类,起到一定的封装及隔离作用,实际接受请求并将请求委托给实际的算法实现类处理,避免外界对底层策略的直接访问;
  • 抽象策略角色(Strategy):该角色一般是一个抽象角色,为接口或者抽象类扮演,定义具体策略角色的公共接口;
  • 具体策略角色(ConcreteStrategy):实现抽象策略角色的接口,为策略的具体实现类。

3、策略模式代码实现

上文中的类图我们来看下如下用代码实现:

首先是抽象策略角色:

// 抽象策略角色
interface Strategy {
    void algorithmInterface();
}

然后是具体策略角色:

// 具体策略角色1
class ConcreteStrategy1 implements Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("具体策略1");
    }
}
// 具体策略角色2
class ConcreteStrategy2 implements Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("具体策略2");
    }
}

最后是我们上下文角色,比较简单,直接贴代码了:

// 上下文角色
class Context {
    private Strategy strategy = null;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    // 对外接口
    public void contextInterface() {
        this.strategy.algorithmInterface();
    }
}

4、优缺点

策略模式的优点如下:

  • 所有策略放入一组抽象策略接口中,方便统一管理与实现;

策略模式的缺点如下:

  • 策略模式每种策略都是单独类,策略很多时策略实现类也很可观;
  • 客户端初始化 Context 的时候需要指定策略类,这样就要求客户端要熟悉各个策略,对调用方要求较高。

5、应用场景

策略模式的应用场景如下:

  • 需要自由切换算法的场景
  • 需要屏蔽算法实现细节的场景

6、使用实例

还是拿我们最上面的排序为例进行说明,对于一个 List 的字符串集合,我们使用不同的排序策略,比如自然排序、逆序两种策略,注意我们这里把排序规则称之为一种排序策略或算法实现,首先是要定义我们的抽象策略角色:

// 字符串的抽象排序策略
interface IStringSortStrategy {
    List<String> sort(List<String> list);
}

这里我们只定义了一个排序的策略接口,入参出参均是字符串列表,下面看看该策略的两种实现:

// 排序策略——正序
class StringSortStrategyNormal implements IStringSortStrategy{
    @Override
    public List<String> sort(List<String> list) {
        Collections.sort(list);
        return list;
    }
}
// 排序策略——倒序
class StringSortStrategyReverse implements IStringSortStrategy{
    @Override
    public List<String> sort(List<String> list) {
        Collections.sort(list);
        Collections.reverse(list);
        return list;
    }
}

然后是我们的上下文角色:

// 上下文角色
class StringSortContext {
    private IStringSortStrategy strategy;

    public StringSortContext(IStringSortStrategy strategy) {
        this.strategy = strategy;
    }

    // 获取排序结果
    public List<String> getSortList(List<String> list) {
        return this.strategy.sort(list);
    }
}

上下文角色中定义了一个外部调用的 api 接口 getSortList,这样我们只需要初始化 StringSortContext 的时候指定排序策略,再调用 getSortList 即可获取排序结果,具体的排序策略如何实现对客户端是不可见的。测试类就是我们的 main 方法:

List<String> list = new ArrayList<>(3);
list.add("admin");
list.add("code-shop");
list.add("lucy");

StringSortContext context = new StringSortContext(new StringSortStrategyReverse());
List<String> reverseSortedList = context.getSortList(list);
System.out.println(reverseSortedList); // [lucy, code-shop, admin]

StringSortContext context2 = new StringSortContext(new StringSortStrategyNormal());
List<String> normalSortedList = context2.getSortList(list);
System.out.println(normalSortedList); // [admin, code-shop, lucy]

7、总结

这节我们介绍了策略模式,总的来说比较简单,重点在于策略的切换,虽然说具体策略的实现如何客户端是不可见的,但是客户端进行初始化 Context 上下文角色的时候需要明确知晓系统有多少策略,这就对客户端要求较高了。