设计模式 —— 策略模式

目录

一、策略模式定义

二、深入浅出拨开策略模式

1、普通整型排序

2、新增自定义类(猫类)并排序

3、实体类实现 Comparable接口,避免Sort相关类越来越多

4、实体类实现带泛型 Comparable接口,避免重写compareTo方法时类型强转

5、通过比较器实现:实体类比较大小的不同策略


一、策略模式定义

策略模式定义了一系列的方法,并将每个方法封装起来,使每个方法可以相互替代,使用方法本身和使用方法的客户端分割开来,相互独立

大白话:同一个类的同一个方法存在不同策略,且策略根据客户端来决定选择哪种策略适合。

二、深入浅出拨开策略模式

这么介绍可能很抽象,还是较难理解策略模式。下面我们将从实际代码一层层拨开策略模式的知识。

1、普通整型排序

定义一个对 整型 int 排序的类及排序方法(为了好理解,以选择排序为例);应用 T01_Main中 main 方法对其调用排序。

a) 下面是对 整型 int 排序的类及排序方法

public class T01_Sorter {
    public static void sort(int[] arr) {

        for (int time = 0; time < arr.length - 1; time++) {
            int min = time; // 取当前剩下数组第一个元素下标为最小下标
            for (int i = time + 1; i < arr.length; i++) {
                // 找到比当前元素值更小的下标,从当前数组序列头开始一直到结尾
                if (arr[i] < arr[min]) {
                    min = i;
                }
            }

            //System.out.println("minPos: " + minPos);
            // 找到后,将两个值进行交换
            swap(arr, min, time);

            //System.out.println("经过第" + i + "次循环之后,数组的内容:");
        }

    }

    private static void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

b) 下面是应用 T01_Main中 main 方法对其调用排序

public class T01_Main {
    public static void main(String[] args) {
        int[] a = {9,2,3,5,7,1,4};
        T01_Sorter sorter = new T01_Sorter();
        sorter.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

这似乎世界很友好,也能解决当前问题。现在来了个新需求:新增猫类,并需要对其进行排序。于是我们继续分析


2、新增自定义类(猫类)并排序

接到了新需求:新增猫类,并需要对其进行排序。首先,我们先定义一个 Bean 类 猫类 Cat。

public class T02_Cat {
    int weight, height;

    public T02_Cat(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }
    // 定义猫的比较方法
    public int compareTo(T02_Cat c) {
        if (this.weight < c.weight) return -1;
        else if (this.weight > c.weight) return 1;
        else return  0;
    }

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

再编写一个对猫进行排序的类,因为猫的排序方式是按照身高或体重来排序的,不能复用之前的整型的排序方式

public class T02_Sorter {
    public static void sort(T02_Cat[] arr) {

        for (int time = 0; time < arr.length - 1; time++) {
            int min = time; // 取当前剩下数组第一个元素下标为最小下标
            for (int i = time + 1; i < arr.length; i++) {
                // 找到比当前元素值更小的下标,从当前数组序列头开始一直到结尾
                min = arr[i].compareTo(arr[min]) == -1 ? i : min;
            }

            //System.out.println("minPos: " + minPos);
            // 找到后,将两个值进行交换
            swap(arr, min, time);

            //System.out.println("经过第" + i + "次循环之后,数组的内容:");
        }

    }

    private static void swap(T02_Cat[] arr, int i, int j){
        T02_Cat temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

再创建应用类,对猫进行排序

public class T02_Main {
    public static void main(String[] args) {
        T02_Cat[] a = {new T02_Cat(3,3), new T02_Cat(5,5), new T02_Cat(1,1)};
        T02_Sorter sorter = new T02_Sorter();
        sorter.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

这似乎也能解决需求新增类实体并排序问题,但仔细观察我们发现,如果此时又来一个新的需求:新增狗类并排序。如果按照上述思路继续,我们又得增加狗的Bean类、增加狗的排序类。类越来越多,后期维护起来麻烦。

既然意识到此问题,我们应该怎样解决问题呢?实体Bean类、及对应的排序类是一一对应关系,将比较大小的方式统一写在实体Bean类就可以解决类越来越多的问题,是不错的解决方案。


3、实体类实现 Comparable接口,避免Sort相关类越来越多

为了统一实体类的比较大小的方式,实体类需要实现 Comparable 接口,重写 compareTo方法,这样的好写,实体类的比较方法都是统一的,且大家都需要重写 compareTo 方法。

public interface T03_Comparable {
    public int compareTo(Object a);
}

接下来在定义实体Bean 类时,使用 implements 关键字来实现 Comarable接口

public class T03_Cat implements T03_Comparable {
    int weight, height;

    public T03_Cat(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }
    // 定义猫的比较方法
    @Override
    public int compareTo(Object c) {
        T03_Cat cat = (T03_Cat)c;
        if (this.weight < cat.weight) return -1;
        else if (this.weight > cat.weight) return 1;
        else return  0;
    }

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

排序类 Sorter 不再是对Bean 类进行排序了,而是对接口进行排序。具体比较大小的规则在对应的实体类内部实现,既后续只需要扩展新增对应的实体类(要求实现 implement Compare接口)即可。

public class T03_Sorter {
    public static void sort(T03_Comparable[] arr) {

        for (int time = 0; time < arr.length - 1; time++) {
            int min = time; // 取当前剩下数组第一个元素下标为最小下标
            for (int i = time + 1; i < arr.length; i++) {
                // 找到比当前元素值更小的下标,从当前数组序列头开始一直到结尾
                min = arr[i].compareTo(arr[min]) == -1 ? i : min;
            }

            //System.out.println("minPos: " + minPos);
            // 找到后,将两个值进行交换
            swap(arr, min, time);

            //System.out.println("经过第" + i + "次循环之后,数组的内容:");
        }

    }

    private static void swap(T03_Comparable[] arr, int i, int j){
        T03_Comparable temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

应用类对实体类对象进行排序调用保持不变

public class T03_Main {
    public static void main(String[] args) {
        T03_Cat[] a = {
                new T03_Cat(3,3),
                new T03_Cat(5,5),
                new T03_Cat(1,1)
        };
        T03_Sorter sorter = new T03_Sorter();
        sorter.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

至此: 实体类通过实现 Compare接口,重写 compareTo比较大小方法;比较类 Sorter只需要对接口进行排序即可,算是解决Sorter不再随着实现类新增而新增问题。

但是,上述这种方式不太方便,每次在实体类重定 compareTo 比较大小方法,都需要进行类型强转。为此,可以引用泛型来避免类型强转问题。补充说明一下:泛型是JavaSE5 引入的一个新概念。


4、实体类实现带泛型 Comparable接口,避免重写compareTo方法时类型强转

第三个版本使用Object,实现类在重写 compareTo方法需要进行类型强转,非常不便,可以使用泛型解决

补充说明:泛型是指在接口中,不确定实现此接口的类型是什么,先由泛型T 代替,在实现类中需要指定泛型类型

// 第三个版本使用Object,实现类需要进行强转;非常不便,可以使用泛型
public interface T04_Comparable<T> {
    public int compareTo(T a);
}

实体类实现 Comparable 接口,同时指定泛型类型

// 实体类实现Comparable 时指定泛型类型
public class T04_Cat implements T04_Comparable<T04_Cat> {
    int weight, height;

    public T04_Cat(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }
    // 定义猫的比较方法
    @Override
    public int compareTo(T04_Cat cat) {
        if (this.weight < cat.weight) return -1;
        else if (this.weight > cat.weight) return 1;
        else return  0;
    }

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

Sorter 类排序还是一样对 Compareable排序,与第三个版本保持不变

public class T04_Sorter {
    public static void sort(T04_Comparable[] arr) {

        for (int time = 0; time < arr.length - 1; time++) {
            int min = time; // 取当前剩下数组第一个元素下标为最小下标
            for (int i = time + 1; i < arr.length; i++) {
                // 找到比当前元素值更小的下标,从当前数组序列头开始一直到结尾
                min = arr[i].compareTo(arr[min]) == -1 ? i : min;
            }

            //System.out.println("minPos: " + minPos);
            // 找到后,将两个值进行交换
            swap(arr, min, time);

            //System.out.println("经过第" + i + "次循环之后,数组的内容:");
        }

    }

    private static void swap(T04_Comparable[] arr, int i, int j){
        T04_Comparable temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

应用类与第三个版本应用类保持不变

public class T04_Main {
    public static void main(String[] args) {
        T04_Cat[] a = {
                new T04_Cat(3,3),
                new T04_Cat(5,5),
                new T04_Cat(1,1)
        };
        T04_Sorter sorter = new T04_Sorter();
        sorter.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

这似乎能满足大部分场景,答案是:可以!但到了大型后台业务系统中,就可能水土不服了。为什么这么说呢?

我们还是接着前面猫的比较大小的场景,在业务系统中可能需要按照体重比较大小,也有可能按照身高比较大小,即同一个实体类在不同的业务情况下比较大小的方式不一样。按照上述方式依然无法解决此问题(只能解决一种比较大小的方式),难道就没有办法解决了么?答案是:有的!

终于,引入了正题,前面都是铺垫,是为了更好的理解策略模式。


5、通过比较器实现:实体类比较大小的不同策略

先定认猫类,这里猫类不再实现 Comparable 接口,因此该方式只能实现一种比较大小的策略,如下:

public class T05_Cat {
    public int weight, height;

    public T05_Cat(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }

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

猫类比较大小的不同策略,都是compare 方法,为此先定义一个接口比较器 T05_Comparator<T>

,后续按体重比较策略类、按身高比较策略类都实现 T05_Comparator<T> 接口即可

@FunctionalInterface  // 如果确认只有这一个方法,不编写@FunctionalInterface 也是可以的
public interface T05_Comparator<T> {
    int compare(T o1, T o2);
}

再定义身高大小比较器、体重大小比较器 

// 按身高大小的比较器
public class T05_CatHeightComparator implements T05_Comparator<T05_Cat> {

    @Override
    public int compare(T05_Cat o1, T05_Cat o2) {
        if (o1.height > o2.height) return -1;
        else if (o1.height < o2.height) return 1;
        else return 0;
    }
}
// 按体重大小的比较器
public class T05_CatWeightComparator implements T05_Comparator<T05_Cat> {

    @Override
    public int compare(T05_Cat o1, T05_Cat o2) {
        if (o1.weight < o2.weight) return -1;
        else if (o1.weight > o2.weight) return 1;
        else return 0;
    }
}

再定义排序类,将比较器作为参数传入,按照比较器策略来进行大小比较

// 排序类中,将比较器作为参数传入,按照比较器策略来进行大小比较
public class T05_Sorter<T> {
    public void sort(T[] arr, T05_Comparator comparator) {

        for (int time = 0; time < arr.length - 1; time++) {
            int min = time; // 取当前剩下数组第一个元素下标为最小下标
            for (int i = time + 1; i < arr.length; i++) {
                // 找到比当前元素值更小的下标,从当前数组序列头开始一直到结尾
                min = comparator.compare(arr[i], arr[min]) == -1 ? i : min;
            }

            //System.out.println("minPos: " + minPos);
            // 找到后,将两个值进行交换
            swap(arr, min, time);

            //System.out.println("经过第" + i + "次循环之后,数组的内容:");
        }
    }

    private void swap(T[] arr, int i, int j){
        T temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

最后,通过应用类来选择不同的比较策略,按需所取

public class T05_Main {
    public static void main(String[] args) {
        T05_Cat[] a = {
                new T05_Cat(3,3),
                new T05_Cat(5,5),
                new T05_Cat(1,1)
        };
        T05_Sorter<T05_Cat> sorter = new T05_Sorter<T05_Cat>();
        //sorter.sort(a, new T05_CatWeightComparator());
        // 采用策略模型:
        //sorter.sort(a, new T05_CatHeightComparator());

        // 写法上,可以采用lambda 表达式进行
        sorter.sort(a, (o1, o2) -> {
            T05_Cat cat1 = (T05_Cat)o1;
            T05_Cat cat2 = (T05_Cat)o2;

            if (cat1.weight < cat2.weight) return -1;
            else if (cat1.weight > cat2.weight) return 1;
            else return 0;
        });

        // 如果一个策略每次调用,都需要 new,因此应该把 DefaultStrategy -> Singleton
        // 通过配置文件加载策略类名,可以动态调整:对修改关闭、对扩展开放(Extensibility Scalability)
        String goodStrategyClassName = "";//PrppertyMgr.get("goodWay");
        try {
            //T05_CatHeightComparator goodStrategy = (T05_CatHeightComparator)Class.forName(goodStrategyClassName).newInstance();
            T05_CatHeightComparator goodStrategy = (T05_CatHeightComparator)Class.forName(goodStrategyClassName).getDeclaredConstructor().newInstance();
            sorter.sort(a, goodStrategy);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        // 还可以通过加载类名

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

小总结:

核心点,将策略封装成一个接口,然后可以通过实现策略接口方式实现各式各样的不同策略;应用类调用时按需传入不同的策略即可,或者使用匿名内部类的方式实现接口方法

 


文章最后,给大家推荐一些受欢迎的技术博客链接

  1. JAVA相关的深度技术博客链接
  2. Flink 相关技术博客链接
  3. Spark 核心技术链接
  4. 设计模式 —— 深度技术博客链接
  5. 机器学习 —— 深度技术博客链接
  6. Hadoop相关技术博客链接
  7. 超全干货--Flink思维导图,花了3周左右编写、校对
  8. 深入JAVA 的JVM核心原理解决线上各种故障【附案例】
  9. 请谈谈你对volatile的理解?--最近小李子与面试官的一场“硬核较量”
  10. 聊聊RPC通信,经常被问到的一道面试题。源码+笔记,包懂
  11. 深入聊聊Java 垃圾回收机制【附原理图及调优方法】

欢迎扫描下方的二维码或 搜索 公众号“大数据高级架构师”,我们会有更多、且及时的资料推送给您,欢迎多多交流!

                                           

       

 

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不埋雷的探长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值