【行为型模式一】策略模式

一、简单工厂模式的缺陷

在简单工厂模式一文中,我们使用简单工厂模式实现了一个基本计算器,客户端暴露给用户的方法如下:

    public static int computeByFactory(int num1, String operator, int num2) throws Exception {
        Operation operation = OperationFactory.createOperation(operator);
        return operation.operate(num1,num2);
    }

但在实现computeByFactory方法时,需知道所有操作运算类的父类为Operation,这样操作的具体实现仍与操作的调用逻辑耦合,可通过对OperationFactory实例化的对象的进一步封装,来降低耦合性,我们可以考虑策略模式

二、策略模式

策略模式(Strategy Pattern):策略模式是一种对象行为型模式。定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。

三、策略模式的角色

使用策略模式实现计算器的类图:
策略模式类图

Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。例如OperationContext类。

Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。例如Operation类。

ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。例如SumOperation类,SubOperation类,MulOperation类,DivOperation类。

四、策略模式实现计算器

4.1 策略上下文类

在使用工厂类配置实例化哪个对象后,在策略上下文类中封装实例化后的对象可能被用户调用的方法。
在工厂类的基础上,加入如下方法:

//OperationContext.java
    public static int execute(int num1,String operator,int num2) throws Exception {
        return newOperationInstance(operator).operate(num1,num2);
    }

4. 2客户端直接调用策略上下文类

    /**
     * 客户端无需知道操作类对象和操作运算的实现,操作运算的实现与操作的调用完全解耦
     */
    public static int computeByStrategy(int num1, String operator, int num2) throws Exception {
        return OperationContext.execute(num1,operator,num2);
    }

这样无论操作运算的实现逻辑怎么更改、增加或减少,客户端都无需修改调用操作的逻辑。

五、策略模式总结

5.1 优点

  • 策略模式提供了对 “开闭原则” 的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。

  • 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。

  • 策略模式提供了一种可以替换继承关系的办法。如果不使用策略模式而是通过继承,这样算法的使用就和算法本身混在一起,不符合 “单一职责原则”,而且使用继承无法实现算法或行为在程序运行时的动态切换。

  • 使用策略模式可以避免多重条件选择语句。多重条件选择语句是硬编码,不易维护。

  • 策略模式提供了一种算法的复用机制,由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。

5.2 缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。

  • 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。

  • 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。

六、策略模式的应用

6.1 适用场景

  • 一个系统需要动态地在几种算法中选择一种,那么可以将这些算法封装到一个个的具体算法类中,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,根据 “里氏代换原则” 和面向对象的多态性,客户端可以选择使用任何一个具体算法类,并只需要维持一个数据类型是抽象算法类的对象。

  • 一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。

  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性。

6.2 Java Comparator 中的策略模式

java.util.Comparator 接口是比较器接口,可以通过 Collections.sort(List,Comparator) 和 Arrays.sort(Object[],Comparator) 对集合和数据进行排序,下面为示例程序

一个学生类,有两个属性 id 和 name

@Data
@AllArgsConstructor
public class Student {
    private Integer id;
    private String name;

    @Override
    public String toString() {
        return "{id=" + id + ", name='" + name + "'}";
    }
}

实现两个比较器,比较器实现了 Comparator 接口,一个升序,一个降序

// 降序
public class DescSortor implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o2.getId() - o1.getId();
    }
}

// 升序
public class AscSortor implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getId() - o2.getId();
    }
}

通过 Arrays.sort() 对数组进行排序

public class Test1 {
    public static void main(String[] args) {
        Student[] students = {
                new Student(3, "张三"),
                new Student(1, "李四"),
                new Student(4, "王五"),
                new Student(2, "赵六")
        };
        toString(students, "排序前");

        Arrays.sort(students, new AscSortor());
        toString(students, "升序后");

        Arrays.sort(students, new DescSortor());
        toString(students, "降序后");
    }

    public static void toString(Student[] students, String desc){
        for (int i = 0; i < students.length; i++) {
            System.out.print(desc + ": " +students[i].toString() + ", ");
        }
        System.out.println();
    }
}

输出

排序前: {id=3, name='张三'}, 排序前: {id=1, name='李四'}, 排序前: {id=4, name='王五'}, 排序前: {id=2, name='赵六'}, 
升序后: {id=1, name='李四'}, 升序后: {id=2, name='赵六'}, 升序后: {id=3, name='张三'}, 升序后: {id=4, name='王五'}, 
降序后: {id=4, name='王五'}, 降序后: {id=3, name='张三'}, 降序后: {id=2, name='赵六'}, 降序后: {id=1, name='李四'}, 

通过 Collections.sort() 对集合List进行排序

public class Test2 {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
                new Student(3, "张三"),
                new Student(1, "李四"),
                new Student(4, "王五"),
                new Student(2, "赵六")
        );
        toString(students, "排序前");

        Collections.sort(students, new AscSortor());
        toString(students, "升序后");

        Collections.sort(students, new DescSortor());
        toString(students, "降序后");
    }

    public static void toString(List<Student> students, String desc) {
        for (Student student : students) {
            System.out.print(desc + ": " + student.toString() + ", ");
        }
        System.out.println();
    }
}

输出

排序前: {id=3, name='张三'}, 排序前: {id=1, name='李四'}, 排序前: {id=4, name='王五'}, 排序前: {id=2, name='赵六'}, 
升序后: {id=1, name='李四'}, 升序后: {id=2, name='赵六'}, 升序后: {id=3, name='张三'}, 升序后: {id=4, name='王五'}, 
降序后: {id=4, name='王五'}, 降序后: {id=3, name='张三'}, 降序后: {id=2, name='赵六'}, 降序后: {id=1, name='李四'}, 

我们向 Collections.sort() 和 Arrays.sort() 分别传入不同的比较器即可实现不同的排序效果(升序或降序)。

这里 Comparator 接口充当了抽象策略角色,两个比较器 DescSortor 和 AscSortor 则充当了具体策略角色,Collections 和 Arrays 则是环境角色。

6.3 Spring Resource 中的策略模式

Spring 把所有能记录信息的载体,如各种类型的文件、二进制流等都称为资源,譬如最常用的Spring配置文件。

在 Sun 所提供的标准 API 里,资源访问通常由 java.NET.URL 和文件 IO 来完成,尤其是当我们需要访问来自网络的资源时,通常会选择 URL 类。

URL 类可以处理一些常规的资源访问问题,但依然不能很好地满足所有底层资源访问的需要,比如,暂时还无法从类加载路径、或相对于 ServletContext 的路径来访问资源,虽然 Java 允许使用特定的 URL 前缀注册新的处理类(例如已有的 http: 前缀的处理类),但是这样做通常比较复杂,而且 URL 接口还缺少一些有用的功能,比如检查所指向的资源是否存在等。

Spring 改进了 Java 资源访问的策略,Spring 为资源访问提供了一个 Resource 接口,该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。

Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略

Spring 为 Resource 接口提供的部分实现类如下:

  • UrlResource:访问网络资源的实现类。

  • ClassPathResource:访问类加载路径里资源的实现类。

  • FileSystemResource:访问文件系统里资源的实现类。

  • ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类:

  • InputStreamResource:访问输入流资源的实现类。

  • ByteArrayResource:访问字节数组资源的实现类。

  • WritableResource:写资源文件

这些 Resource 实现类,针对不同的的底层资源,提供了相应的资源访问逻辑,并提供便捷的包装,以利于客户端程序的资源访问。

可以看到 AbstractResource 资源抽象类实现了 Resource 接口,为子类通用的操作提供了具体实现,非通用的操作留给子类实现,所以这里也应用了模板方法模式。(只不过缺少了模板方法)

Resource 不仅可在 Spring 的项目中使用,也可直接作为资源访问的工具类使用。意思是说:即使不使用 Spring 框架,也可以使用 Resource 作为工具类,用来代替 URL

譬如我们可以使用 UrlResource 访问网络资源。

6.4 Spring Bean 实例化中的策略模式

Spring实例化Bean有三种方式:构造器实例化、静态工厂实例化、实例工厂实例化

譬如通过构造器实例化bean的XML示例如下:

具体实例化Bean的过程中,Spring中角色分工很明确,创建对象的时候先通过 ConstructorResolver 找到对应的实例化方法和参数,再通过实例化策略 InstantiationStrategy 进行实例化,根据创建对象的三个分支( 工厂方法、有参构造方法、无参构造方法 ), InstantiationStrategy 提供了三个接口方法。

InstantiationStrategy 为实例化策略接口,扮演抽象策略角色,有两种具体策略类,分别为 SimpleInstantiationStrategyCglibSubclassingInstantiationStrategy

SimpleInstantiationStrategy 中对这三个方法做了简单实现,如果工厂方法实例化直接用反射创建对象,如果是构造方法实例化的则判断是否有 MethodOverrides,如果有无 MethodOverrides 也是直接用反射,如果有 MethodOverrides 就需要用 cglib 实例化对象,SimpleInstantiationStrategy 把通过 cglib 实例化的任务交给了它的子类 CglibSubclassingInstantiationStrategy
参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值