学好Java设计模式--策略模式

从实现一个简单的排序器学习策略模式

策略模式是对算法的封装,把一系列的算法分别封装到对应的类中,并且这些类实现相同的接口,相互之间可以替换。当对象有某个行为,但是在不同的场景中该行为有不同的实现算法,这时便可以使用策略模式这一设计模式。

1、问题引入

假设我们有一个排序器,使用简单的插入排序实现对int类型的数组进行排序,代码如下所示:

package com.rul.designpattern.strategy;

import java.util.Arrays;

/**
 * 简单插入排序排序器
 *
 * @author LuoRu
 */
public class Sorter01 {

    public void sort(int[] array) {

        for (int i = 1; i < array.length; i++) {
            int temp = array[i];
            int j = i;
            while (j > 0 && temp < array[j - 1]) {
                //元素向后移动
                array[j] = array[j - 1];
                j--;
            }
            //插入元素
            array[j] = temp;
        }
    }

    public static void main(String[] args) {
        int[] array = new int[]{2, 1, 5, 3, 4};

        Sorter01 sorter = new Sorter01();
        sorter.sort(array);

        System.out.println(Arrays.toString(array));
    }
}

如果某一天我们想要对double类型数组排序怎么办、想对自定义类型数组排序怎么办?直接往排序器里面加sort方法,实现很多的重载方法?这样每当我们想要对一种新类型进行排序,就需要新写一个重载方法,最终会使代码变得特别臃肿。我们可以这样实现:

package com.rul.designpattern.strategy;

/**
 * 规定每个需要使用Sorter02进行排序的类必须实现Comparable接口
 *
 * @param 实现类的类型
 * @author LuoRu
 */
@FunctionalInterface
public interface Comparable<T> {
    int compareTo(T o);
}

我们设计一个Comparable接口,包含一个compareTo方法,该方法用来实现两个对象比较大小的策略。同时我们规定想要使用排序器进行排序的类型必须实现该接口。如下,我们定义了Cat类型,并实现了Comparable接口,实现的compareTo方法表示age小的Cat小。

package com.rul.designpattern.strategy;

/**
 * 自定义类型,我们想要通过排序器对该类型的数组进行排序
 *
 * @author LuoRu
 */
public class Cat implements Comparable<Cat> {
    int age;
    int weight;

    public Cat(int age, int weight) {
        this.age = age;
        this.weight = weight;
    }

    @Override
    public int compareTo(Cat o) {
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "age=" + age +
                ", weight=" + weight +
                '}';
    }
}

然后我们便可以将排序器的代码改成下面这样,就可以对所有实现了Comparable接口的类型的数组进行排序了。

package com.rul.designpattern.strategy;

import java.util.Arrays;

/**
 * 对实现了Comparable接口的对象进行排序
 *
 * @author LuoRu
 */
public class Sorter02 {
    public void sort(Comparable[] array) {

        for (int i = 1; i < array.length; i++) {
            Comparable temp = array[i];
            int j = i;
            while (j > 0 && temp.compareTo(array[j - 1]) < 0) {
                //元素向后移动
                array[j] = array[j - 1];
                j--;
            }
            //插入元素
            array[j] = temp;
        }
    }

    public static void main(String[] args) {
        Cat[] cats = new Cat[]{new Cat(3, 3), new Cat(1, 1),
                new Cat(4, 4), new Cat(5, 5), new Cat(2, 2)};

        Sorter02 sorter = new Sorter02();
        sorter.sort(cats);

        System.out.println(Arrays.toString(cats));
    }
}

但是有一天,我们又突然想对Cat类型的数组按照weight而不是age进行排序了,怎么办?最容易想到的办法就是修改Cat中的compareTo方法的实现,但是这样违反了软件设计中的”开闭原则–对拓展开放,对修改关闭“,而且这样不能使两种排序策略在程序中同时存在,也就是说不能在同一程序中某处按照age排序,而在另外一处则使用weight排序。

2、策略模式登场

我们可以通过策略模式很简单地实现前面的需求。首先定义一个泛型接口,compare规定两个对象比较大小的策略。

package com.rul.designpattern.strategy;

/**
 * 比较策略接口
 *
 * @param 对需要进行比较对象的类型
 * @author LuoRu
 */
@FunctionalInterface
public interface Comparator<T> {
    int compare(T t1, T t2);
}

我们将Comparator实现类作为参数传入sort方法,这样每当我们想要按照新的排序规则进行排序的时候,只需要新创建一个Comparator接口的实现类就可以了。

package com.rul.designpattern.strategy;

import java.util.Arrays;

/**
 * 将Comparator接口作为排序器的sort方法的参数
 *
 * @param 需排序的对象数组的类型
 * @author LuoRu
 */
public class Sorter03<T> {

    public void sort(T[] array, Comparator<T> comparator) {

        for (int i = 1; i < array.length; i++) {
            T temp = array[i];
            int j = i;
            while (j > 0 && comparator.compare(temp, array[j - 1]) < 0) {
                //元素向后移动
                array[j] = array[j - 1];
                j--;
            }

            //插入元素
            array[j] = temp;
        }
    }

    public static void main(String[] args) {
        Cat[] cats = new Cat[]{new Cat(3, 3), new Cat(1, 1),
                new Cat(4, 4), new Cat(5, 5), new Cat(2, 2)};

        Sorter03<Cat> sorter = new Sorter03<>();

        sorter.sort(cats, ((t1, t2) -> t1.age - t2.age));
        System.out.println("按照age从小到大排序:" + Arrays.toString(cats));

        sorter.sort(cats, ((t1, t2) -> t2.weight - t1.weight));
        System.out.println("按照weight从大到小排序:" + Arrays.toString(cats));
    }
}

3、策略模式的结构

①Context上下文:

Context上下文角色,也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。对应上面实例中的Sorter03。

②抽象策略角色:

抽象策略角色,是对策略、算法的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。对应上面实例中的Comparator接口。

③具体策略角色:

具体策略角色通常由一组封装了算法的类来担任,这些类之间可以根据需要自由替换。对应上面实例中的Sorter03中main方法中的Lambda表达式。

你可能感兴趣的:(设计模式)