策略模式

假设现在我有个需求,把1个数组里的整数值进行由低到高的排序
我可以先写1个工具类Sorter出来,然后其他类new 调用它的方法

//这里使用的是选择排序
public class Sorter {
    public void sort(int[] arr){
        for (int i = 0; i < arr.length - 1; i++) {
            int minPos = i;
            for (int j = i+1; j < arr.length; j++) {
                minPos = arr[j] < arr[minPos]? j : minPos;
            }
            swap(arr,i,minPos);
        }
    }

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

当我们其他类需要实现排序功能时 就调用这个类里的方法

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] a = {9,2,3,15,24,1,41};
        Sorter sorter = new Sorter();
        sorter.sort(a);
        System.out.println(Arrays.toString(a));
    }
}
实现了1个简单的选择排序功能

现在我的需求变更了, 如果我想对double类型进行排序怎么办?
我可以对Sorter类里面的sort方法进行重载

//数组类型变成double
public class Sorter {
    public void sort(double[] arr){
...里面的比较逻辑不变
    }

    static void swap(double[] arr,int i,int j){
        double temp;
..交换逻辑不变
    }
}

如果我们还需要对float,short,long进行排序呢?都要重新写一篇吗?
我们发现,按照重载的方法,我们需要把1个大部分都相同的算法,就因为1个类型的变化,来来回回写好多遍

但是如果我不重载 直接传double[]数组 进入 程序就崩溃了 过不了编译器

而且因为你写死了比较的标准,你也不能通过传入Object[] arr的方式来泛化,因为Object[] arr是无法用<符号来比较大小的,会报错
怎么办呢?我传入Object[] 然后让对象的类去实现标准?(后面会讲)

怎么办呢?策略模式就是我们不关注算法的具体过程 前提这个算法有1个唯一的标准(我们发现只要计算出来了 哪个值小于哪个值 只要确定好了这个比较标准minPos = arr[j] < arr[minPos]? j : minPos; 这个算法就可以大部分成立) 然后我们把这个标准外包给对象的类里面去实现

上面问题 同样适用于对象里的比较排序,但是这个对象里排序的标准,是不确定的,
那么既然我们不知道Sorter里的标准到底是哪个?那么我就把Sorter里面这个标准实现的动作 交给其他人 创建对象的类(这里比如 我创建1个Cat类型 Cat里面2个属性weight,heigth)里面去实现,我只是调用1个CompareTo方法 具体的实现外包给对象的类 (达到策略模式的一半)
所以Sorter里的代码 就变成了

public class Sorter<T> {
    public void sort(Cat[] arr){
        for (int i = 0; i < arr.length - 1; i++) {
            int minPos = i;
            for (int j = i+1; j < arr.length; j++) {
//通过对象的compareTo方法 告诉我谁小谁大
                minPos = arr[j].compareTo(arr[minPos])==-1 ? j : minPos;
            }
            swap(arr,i,minPos);
        }
    }
....后面swap方法实现省略

假如我们的比较标准是Cat的weigth
我们在Cat中可以实现1个比较标准的方法CompareTo,比较他们的weigth

public class Cat {
    int weight,height;

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

    //自己要在类中定义1个比较的标准CompareTo
    public int compareTo(Cat c){
        if(this.weight < c.weight) return -1;
        else if(this.weight > c.weight) return 1;
        else return 0;
    }

    //因为 Arrays.toString(Cat[] a)
    //其实toString最后调用的是Cat类里的自己toString方法
    //所以为了方便 打印进行重写
    @Override
    public String toString() {
        return "Cat{" +
                "weight=" + weight +
                ", height=" + height +
                '}';
    }
}

Main创建Cat并赋值 并且利用Sorter工具类进行对Cat排序

public class Main {
    public static void main(String[] args) {
        Cat[] a = {new Cat(1, 2), new Cat(9, 2),
                new Cat(6, 2), new Cat(5, 2)};
        Sorter sorter = new Sorter();
        sorter.sort(a);
        System.out.println(Arrays.toString(a));
}}
Cat对象的排序结果

利用这种"外包标准实现"的策略思想,这样我就不仅可以对8种基本数据类型的包装类(注意:不是byte、short、int、long、float、double、boolean、char 基本数据类型 而是Byte Short Integer Long Float Double Character Boolean包装类 因为只有对象里面才可以实现CompareTo方法)进行排序外 还可以对对象进行排序

基本数据类型的包装类 里面都 自己实现有自己的用于比较CompareTo方法

需求又来了:如果我还有个狗,企鹅,鸡等类 这些类里面也要实现 这些代码需要在Sorter类里面再重载写1遍 就像上面int[],double[]的比较吗? 不需要了,因为都是对象了,我们改成传入object类型,因为java里面一切皆对象,object是一切对象的父类,在我们用包装类的情况下,一切都解决了,在自己对象的类里面的CompareTo方法里 自己进行强转就行了
这样想有问题吗? 其实是有问题的,因为Sort传Object类型的话,Object类型里面是没有CompateTo方法的 所以你的比较标准还是有问题的

报错 Object没有CompareTo方法

所以我们传1个Comparable接口(对象)数组,我们要保证,这个Comparable接口里面一定有CompareTo方法,然后上面的arr[j].compareTo标准才不会出错
所以我们再新建1个接口叫做Comparable 它里面有个抽象方法compareTo(Object o)

public interface Comparable {
//注意 这里CompareTo传入的类型一定不能写死 
//写死了 那么其他类就无法去实现自己comparaTo方法 定制自己类的比较标准了
    int compareTo(Object o);
}

于是Sorter类最终成了

public class Sorter<T> {
    public void sort(Comparable[] arr){
        for (int i = 0; i < arr.length - 1; i++) {
            int minPos = i;
            for (int j = i+1; j < arr.length; j++) {
                minPos = arr[j].compareTo(arr[minPos])==-1 ? j : minPos;
              //  minPos = (arr[j] < arr[minPos]) ? j : minPos;
            }
            swap(arr,i,minPos);
        }
    }
//这里的Object[] arr 改成Comparable[] arr也可以
    static void swap(Object[] arr, int i, int j){
        Object temp = arr[i];
        arr[i]=arr[j];
        arr[j] = temp;
    }
}

然后Cat等类 如果你想排序的话 就去实现Comparable接口 实现自己的比较标准compareTo(object类型) 才能调用Sorter里的sort方法

public class Cat implements Comparable {
    int weight,height;
    public Cat(int weight,int height){
        this.weight = weight;
        this.height = height;
    }

    //自己要在类中定义1个比较的标准CompareTo
    public int compareTo(Object o){
        //实现compareTo接口 强制转为 自己的类的对象
        Cat c = (Cat)o;
        if(this.weight < c.weight) return -1;
        else if(this.weight > c.weight) return 1;
        else return 0;
    }


    //因为 Arrays.toString(Cat[] a)
    //其实toString最后调用的是Cat类里的自己toString方法
    //所以为了方便 打印进行重写
    @Override
    public String toString() {
        return "Cat{" +
                "weight=" + weight +
                ", height=" + height +
                '}';
    }
}

现在Main方法不用改变

public class Main {
    public static void main(String[] args) {
        Cat[] a = {new Cat(189, 2), new Cat(9, 2),
                new Cat(6, 2), new Cat(5, 2)};
        Sorter sorter = new Sorter();
        sorter.sort(a);
        System.out.println(Arrays.toString(a));
    }
}
排序成功

同理再来个Dog,Panda,Frog也是一样的

//要排序,就要实现Comparable接口
public class Dog implements Comparable {
    int age,size;
    public Dog(int age,int size){
        this.age = age;
        this.size = size;
    }
//还必须要实现 比较的标准
    public int compareTo(Object o){
        Dog d = (Dog)o;
        if(this.age < d.age) return -1;
        else if(this.age > d.age) return 1;
        else return 0;
    }

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

Main方法的调用是基本不变的

public class Main {
    public static void main(String[] args) {
//        Cat[] a = {new Cat(189, 2), new Cat(9, 2),
//                new Cat(6, 2), new Cat(5, 2)};
        Dog[] a = {new Dog(189, 2), new Dog(9, 2),
                new Dog(6, 2), new Dog(5, 2)};
        Sorter sorter = new Sorter();
        sorter.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

优化:如果我们写Cat类的时候,如果需要调用一下compareTo()方法,但是不小心传进入的不是Cat类的对象,那么compareTo里面执行强制转换(Cat)o直接就报错了,这样宽进严出是不是有一点风险,所以我们可以加个泛型<T>,指定你进来的必须是我指定的类型<T> ,不然我编译的时候就报错
那么我们需要把Comparable接口改一下 Object改为泛型

public interface Comparable<T> {
    int compareTo(T o);
}

然后Cat实现接口的时候就指定好Comparable里面的类

//泛型接口
public class Cat implements Comparable<Cat> {
    int weight,height;
    public Cat(int weight,int height){
        this.weight = weight;
        this.height = height;
    }

    //自己要在类中定义1个比较的标准CompareTo
    public int compareTo(Cat o){
    //  Cat c = (Cat)o;  不需要强制转换了
        if(this.weight < c.weight) return -1;
        else if(this.weight > c.weight) return 1;
        else return 0;
    }
....其他省略
    }
}

好的,现在我们总结一下,现在的情况
我们有1个Sorter工具类,它里面实现了排序方法Sort(Comparable[] arr),不过这个方法必须传入的参数是Comparable接口数组
我们如果有对象数组要实现排序的功能的话,必须要去实现Comparable<T>接口,在compareTo() 里实现自己排序的依据
然后我们Main要对对象数组进行排序的时候 就直接new出来 然后sort(arr)就完了

现在有来了个需求:我现在不想对Cat.weight进行比较了,而想对Cat.height进行比较,怎么办?
我们已经把Cat.compareTo(Cat o)方法实现了 而且现在不是改变的传入参数,而是里面的逻辑,不能重写或重载方法,所以要改变的话,需要修改Cat.compareTo(Cat o)里面的源码,这十分麻烦,而且一般类的属性不只两个,有时候我们经常要改变比较准备,经常改源码,会累死人,而且修改线上源码也不合适,根据开闭原则,“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,怎么办呢?

我们把比较标准Comparable接口的代码 单独从类的源码里抽出来,不在类的源码里实现了,把源码里的变成比较器Comparator而不是具体的比较标准,当我们用的时候,自己写比较标准器Comparator传到Sorter里去调用它的compareTo()方法,这个compareTo()是我们在外面就可以修改的比较策略,不同的比较策略都可以按照自己的想法实现,这样就更好了
所以我新建1个比较器Comparator接口

public interface Comparator<T> {
    //因为是从源码类里面剥离出来的比较器
    //所以我们使用Comparator的时候
    //this指针指向不是对象的类本身 不能this.weight < c.weight了
    //所以我们需要传2个比较的对象参数o1 o2才能比较
    int compare(T o1,T o2);
}

比如,我现在按照Dog.age对象数组进行排序,我们就需要自己创建1个类,DogAgeComparator比较器,它要实现比较器,然后自己实现它的标准

public class DogAgeComparator implements Comparator<Dog> {
    public int compare(Dog d1,Dog d2){
        if(d1.age<d2.age)return -1;
        else if(d1.age>d2.age) return 1;
        else return 0;
    }
}

然后我们也需要修改1下Sorter类的源码
传入的参数是2个: 1个是对象数组 1个就是我们定义的比较策略器类

public class Sorter<T> {
    public void sort(T[] arr, Comparator<T> comparator){
        for (int i = 0; i < arr.length - 1; i++) {
            int minPos = i;
            for (int j = i+1; j < arr.length; j++) {
  //自定义的比较策略器comparator
                minPos = comparator.compare(arr[j],arr[minPos])==-1? j : minPos;        
            }
            swap(arr,i,minPos);
        }
    }
.......
}

Main方法 创建Dog数组对象 然后new出Sorter工具类,然后创建自己写的比较策略器,把对象数组和比较器 传到sort方法里

public class Main {
    public static void main(String[] args) {
        Dog[] a = {new Dog(189, 6), new Dog(9, 2),
                new Dog(6, 2), new Dog(5, 5151)};
        Sorter sorter = new Sorter();
        DogAgeComparator comparator=new DogAgeComparator() ;
        sorter.sort(a,comparator);
        System.out.println(Arrays.toString(a));
    }
}
可扩展的策略器

那么现在 我们不仅可以往Sorter里面传各种各样的类型 而且还可以让我们不断扩展自己的比较策略 可以体重 可以身高 也可以体重乘以身高 比comparable的写法扩展性好得多

策略模式

这样我要扩展对象的排序的话 我sort方法是不用变的 需要变的只是 添加新的策略 Cat3C 然后实现comparator 然后传进去 就自动帮我们排好序了
这种写法,其实就是jdk中原生的java.util.Comparator类的实现逻辑,它里面也有个compare(T,T)方法和我们实现的compareTo(T,T)方法一样的,这个Comparator其实用的就是策略模式,用原类的对象里剥离出来的自定义策略

优化:我们实现自己比较策略器CompareTor时,我们可以不"创建"类,用lamda表达式来写,因为我们的接口里面只有1个方法CompareTo

public class Main {
    public static void main(String[] args) {
        Dog[] a = {new Dog(189, 6), new Dog(9, 2),
                new Dog(6, 2), new Dog(5, 5151)};
//Sorter里面一定要指定<Dog> 因为后面sort(T[],ComparaTor<T>)不知道到底是什么类的策略方法
        Sorter<Dog> sorter = new Sorter();
        sorter.sort(a,(o1,o2)->{
            if(o1.size < o2.size) return -1;
            else if(o1.size > o2.size) return 1;
            else return 0;
        });
        System.out.println(Arrays.toString(a));
    }
}

这个也被称为 函数式接口 functional interface 接口里面只有1个方法
是函数式接口的 我们还可以声明 它是1个@functional interface 但是我们确认 它里面只有1个方法 不写functional interface也可以

//函数式接口声明
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1,T o2);
}

jdk1.8后接口有个特性,interface里面可以写具体逻辑实现default void func(){逻辑处理;}
为什么接口里必须有方法实现? 因为它要支持lambda表达式
根据开闭原则,之前的代码都没有写default的 所以为了向前兼容 只用实现抽象方法 不实现defalut方法是可以的

public interface Comparator<T> {
    int compare(T o1,T o2);
    static void m123(){
        System.out.println("m123");
    }
    default void m(){
        System.out.println("m");
    }
}

接口Comparator里的方法m123可以直接调用

public class DogAgeComparator implements Comparator<Dog> {
    public int compare(Dog d1,Dog d2){
        if(d1.age<d2.age)return -1;
        else if(d1.age>d2.age) return 1;
        else return 0;
    }

    public static void main(String[] args) {
        Comparator.m123();
    }
}
直接调用接口里的方法

回顾:
认识Comparable接口和Comparator比较器
jdk中 Comparable接口在java.lang.Comparable包中;Comparator类在java.util.Comparator包中 ;Comparable不是策略模式 Comparator是 通过实现子类来指定两个对象之间如何比较大小的方式
它是一种策略模式

而一般策略模式,我们可以有很多种实现类,我们可以写好,然后在配置文件中指定,灵活选择,自己的标准

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值