策略模式

假設(shè)現(xiàn)在我有個(gè)需求,把1個(gè)數(shù)組里的整數(shù)值進(jìn)行由低到高的排序
我可以先寫1個(gè)工具類Sorter出來,然后其他類new 調(diào)用它的方法

//這里使用的是選擇排序
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;
    }
}

當(dāng)我們其他類需要實(shí)現(xiàn)排序功能時(shí) 就調(diào)用這個(gè)類里的方法

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));
    }
}
實(shí)現(xiàn)了1個(gè)簡(jiǎn)單的選擇排序功能

現(xiàn)在我的需求變更了, 如果我想對(duì)double類型進(jìn)行排序怎么辦?
我可以對(duì)Sorter類里面的sort方法進(jìn)行重載

//數(shù)組類型變成double
public class Sorter {
    public void sort(double[] arr){
...里面的比較邏輯不變
    }

    static void swap(double[] arr,int i,int j){
        double temp;
..交換邏輯不變
    }
}

如果我們還需要對(duì)float,short,long進(jìn)行排序呢?都要重新寫一篇嗎?
我們發(fā)現(xiàn),按照重載的方法,我們需要把1個(gè)大部分都相同的算法,就因?yàn)?個(gè)類型的變化,來來回回寫好多遍


但是如果我不重載 直接傳double[]數(shù)組 進(jìn)入 程序就崩潰了 過不了編譯器

而且因?yàn)槟銓懰懒吮容^的標(biāo)準(zhǔn),你也不能通過傳入Object[] arr的方式來泛化,因?yàn)镺bject[] arr是無法用<符號(hào)來比較大小的,會(huì)報(bào)錯(cuò)
怎么辦呢?我傳入Object[] 然后讓對(duì)象的類去實(shí)現(xiàn)標(biāo)準(zhǔn)?(后面會(huì)講)

怎么辦呢?策略模式就是我們不關(guān)注算法的具體過程 前提這個(gè)算法有1個(gè)唯一的標(biāo)準(zhǔn)(我們發(fā)現(xiàn)只要計(jì)算出來了 哪個(gè)值小于哪個(gè)值 只要確定好了這個(gè)比較標(biāo)準(zhǔn)minPos = arr[j] < arr[minPos]? j : minPos; 這個(gè)算法就可以大部分成立) 然后我們把這個(gè)標(biāo)準(zhǔn)外包給對(duì)象的類里面去實(shí)現(xiàn)

上面問題 同樣適用于對(duì)象里的比較排序,但是這個(gè)對(duì)象里排序的標(biāo)準(zhǔn),是不確定的,
那么既然我們不知道Sorter里的標(biāo)準(zhǔn)到底是哪個(gè)?那么我就把Sorter里面這個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn)的動(dòng)作 交給其他人 創(chuàng)建對(duì)象的類(這里比如 我創(chuàng)建1個(gè)Cat類型 Cat里面2個(gè)屬性weight,heigth)里面去實(shí)現(xiàn),我只是調(diào)用1個(gè)CompareTo方法 具體的實(shí)現(xiàn)外包給對(duì)象的類 (達(dá)到策略模式的一半)
所以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++) {
//通過對(duì)象的compareTo方法 告訴我誰小誰大
                minPos = arr[j].compareTo(arr[minPos])==-1 ? j : minPos;
            }
            swap(arr,i,minPos);
        }
    }
....后面swap方法實(shí)現(xiàn)省略

假如我們的比較標(biāo)準(zhǔn)是Cat的weigth
我們?cè)贑at中可以實(shí)現(xiàn)1個(gè)比較標(biāo)準(zhǔn)的方法CompareTo,比較他們的weigth

public class Cat {
    int weight,height;

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

    //自己要在類中定義1個(gè)比較的標(biāo)準(zhǔn)CompareTo
    public int compareTo(Cat c){
        if(this.weight < c.weight) return -1;
        else if(this.weight > c.weight) return 1;
        else return 0;
    }

    //因?yàn)?Arrays.toString(Cat[] a)
    //其實(shí)toString最后調(diào)用的是Cat類里的自己toString方法
    //所以為了方便 打印進(jìn)行重寫
    @Override
    public String toString() {
        return "Cat{" +
                "weight=" + weight +
                ", height=" + height +
                '}';
    }
}

Main創(chuàng)建Cat并賦值 并且利用Sorter工具類進(jìn)行對(duì)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對(duì)象的排序結(jié)果

利用這種"外包標(biāo)準(zhǔn)實(shí)現(xiàn)"的策略思想,這樣我就不僅可以對(duì)8種基本數(shù)據(jù)類型的包裝類(注意:不是byte、short、int、long、float、double、boolean、char 基本數(shù)據(jù)類型 而是Byte Short Integer Long Float Double Character Boolean包裝類 因?yàn)橹挥袑?duì)象里面才可以實(shí)現(xiàn)CompareTo方法)進(jìn)行排序外 還可以對(duì)對(duì)象進(jìn)行排序

基本數(shù)據(jù)類型的包裝類 里面都 自己實(shí)現(xiàn)有自己的用于比較CompareTo方法

需求又來了:如果我還有個(gè)狗,企鵝,雞等類 這些類里面也要實(shí)現(xiàn) 這些代碼需要在Sorter類里面再重載寫1遍 就像上面int[],double[]的比較嗎? 不需要了,因?yàn)槎际菍?duì)象了,我們改成傳入object類型,因?yàn)閖ava里面一切皆對(duì)象,object是一切對(duì)象的父類,在我們用包裝類的情況下,一切都解決了,在自己對(duì)象的類里面的CompareTo方法里 自己進(jìn)行強(qiáng)轉(zhuǎn)就行了
這樣想有問題嗎? 其實(shí)是有問題的,因?yàn)镾ort傳Object類型的話,Object類型里面是沒有CompateTo方法的 所以你的比較標(biāo)準(zhǔn)還是有問題的


報(bào)錯(cuò) Object沒有CompareTo方法

所以我們傳1個(gè)Comparable接口(對(duì)象)數(shù)組,我們要保證,這個(gè)Comparable接口里面一定有CompareTo方法,然后上面的arr[j].compareTo標(biāo)準(zhǔn)才不會(huì)出錯(cuò)
所以我們?cè)傩陆?個(gè)接口叫做Comparable 它里面有個(gè)抽象方法compareTo(Object o)

public interface Comparable {
//注意 這里CompareTo傳入的類型一定不能寫死 
//寫死了 那么其他類就無法去實(shí)現(xiàn)自己comparaTo方法 定制自己類的比較標(biāo)準(zhǔn)了
    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等類 如果你想排序的話 就去實(shí)現(xiàn)Comparable接口 實(shí)現(xiàn)自己的比較標(biāo)準(zhǔn)compareTo(object類型) 才能調(diào)用Sorter里的sort方法

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

    //自己要在類中定義1個(gè)比較的標(biāo)準(zhǔn)CompareTo
    public int compareTo(Object o){
        //實(shí)現(xiàn)compareTo接口 強(qiáng)制轉(zhuǎn)為 自己的類的對(duì)象
        Cat c = (Cat)o;
        if(this.weight < c.weight) return -1;
        else if(this.weight > c.weight) return 1;
        else return 0;
    }


    //因?yàn)?Arrays.toString(Cat[] a)
    //其實(shí)toString最后調(diào)用的是Cat類里的自己toString方法
    //所以為了方便 打印進(jìn)行重寫
    @Override
    public String toString() {
        return "Cat{" +
                "weight=" + weight +
                ", height=" + height +
                '}';
    }
}

現(xiàn)在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));
    }
}
排序成功

同理再來個(gè)Dog,Panda,F(xiàn)rog也是一樣的

//要排序,就要實(shí)現(xiàn)Comparable接口
public class Dog implements Comparable {
    int age,size;
    public Dog(int age,int size){
        this.age = age;
        this.size = size;
    }
//還必須要實(shí)現(xiàn) 比較的標(biāo)準(zhǔn)
    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方法的調(diào)用是基本不變的

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));
    }
}

優(yōu)化:如果我們寫Cat類的時(shí)候,如果需要調(diào)用一下compareTo()方法,但是不小心傳進(jìn)入的不是Cat類的對(duì)象,那么compareTo里面執(zhí)行強(qiáng)制轉(zhuǎn)換(Cat)o直接就報(bào)錯(cuò)了,這樣寬進(jìn)嚴(yán)出是不是有一點(diǎn)風(fēng)險(xiǎn),所以我們可以加個(gè)泛型<T>,指定你進(jìn)來的必須是我指定的類型<T> ,不然我編譯的時(shí)候就報(bào)錯(cuò)
那么我們需要把Comparable接口改一下 Object改為泛型

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

然后Cat實(shí)現(xiàn)接口的時(shí)候就指定好Comparable里面的類

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

    //自己要在類中定義1個(gè)比較的標(biāo)準(zhǔn)CompareTo
    public int compareTo(Cat o){
    //  Cat c = (Cat)o;  不需要強(qiáng)制轉(zhuǎn)換了
        if(this.weight < c.weight) return -1;
        else if(this.weight > c.weight) return 1;
        else return 0;
    }
....其他省略
    }
}

好的,現(xiàn)在我們總結(jié)一下,現(xiàn)在的情況
我們有1個(gè)Sorter工具類,它里面實(shí)現(xiàn)了排序方法Sort(Comparable[] arr),不過這個(gè)方法必須傳入的參數(shù)是Comparable接口數(shù)組
我們?nèi)绻袑?duì)象數(shù)組要實(shí)現(xiàn)排序的功能的話,必須要去實(shí)現(xiàn)Comparable<T>接口,在compareTo() 里實(shí)現(xiàn)自己排序的依據(jù)
然后我們Main要對(duì)對(duì)象數(shù)組進(jìn)行排序的時(shí)候 就直接new出來 然后sort(arr)就完了

現(xiàn)在有來了個(gè)需求:我現(xiàn)在不想對(duì)Cat.weight進(jìn)行比較了,而想對(duì)Cat.height進(jìn)行比較,怎么辦?
我們已經(jīng)把Cat.compareTo(Cat o)方法實(shí)現(xiàn)了 而且現(xiàn)在不是改變的傳入?yún)?shù),而是里面的邏輯,不能重寫或重載方法,所以要改變的話,需要修改Cat.compareTo(Cat o)里面的源碼,這十分麻煩,而且一般類的屬性不只兩個(gè),有時(shí)候我們經(jīng)常要改變比較準(zhǔn)備,經(jīng)常改源碼,會(huì)累死人,而且修改線上源碼也不合適,根據(jù)開閉原則,“軟件中的對(duì)象(類,模塊,函數(shù)等等)應(yīng)該對(duì)于擴(kuò)展是開放的,但是對(duì)于修改是封閉的”,怎么辦呢?

我們把比較標(biāo)準(zhǔn)Comparable接口的代碼 單獨(dú)從類的源碼里抽出來,不在類的源碼里實(shí)現(xiàn)了,把源碼里的變成比較器Comparator而不是具體的比較標(biāo)準(zhǔn),當(dāng)我們用的時(shí)候,自己寫比較標(biāo)準(zhǔn)器Comparator傳到Sorter里去調(diào)用它的compareTo()方法,這個(gè)compareTo()是我們?cè)谕饷婢涂梢孕薷牡谋容^策略,不同的比較策略都可以按照自己的想法實(shí)現(xiàn),這樣就更好了
所以我新建1個(gè)比較器Comparator接口

public interface Comparator<T> {
    //因?yàn)槭菑脑创a類里面剝離出來的比較器
    //所以我們使用Comparator的時(shí)候
    //this指針指向不是對(duì)象的類本身 不能this.weight < c.weight了
    //所以我們需要傳2個(gè)比較的對(duì)象參數(shù)o1 o2才能比較
    int compare(T o1,T o2);
}

比如,我現(xiàn)在按照Dog.age對(duì)象數(shù)組進(jìn)行排序,我們就需要自己創(chuàng)建1個(gè)類,DogAgeComparator比較器,它要實(shí)現(xiàn)比較器,然后自己實(shí)現(xiàn)它的標(biāo)準(zhǔn)

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類的源碼
傳入的參數(shù)是2個(gè): 1個(gè)是對(duì)象數(shù)組 1個(gè)就是我們定義的比較策略器類

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方法 創(chuàng)建Dog數(shù)組對(duì)象 然后new出Sorter工具類,然后創(chuàng)建自己寫的比較策略器,把對(duì)象數(shù)組和比較器 傳到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));
    }
}
可擴(kuò)展的策略器

那么現(xiàn)在 我們不僅可以往Sorter里面?zhèn)鞲鞣N各樣的類型 而且還可以讓我們不斷擴(kuò)展自己的比較策略 可以體重 可以身高 也可以體重乘以身高 比comparable的寫法擴(kuò)展性好得多


策略模式

這樣我要擴(kuò)展對(duì)象的排序的話 我sort方法是不用變的 需要變的只是 添加新的策略 Cat3C 然后實(shí)現(xiàn)comparator 然后傳進(jìn)去 就自動(dòng)幫我們排好序了
這種寫法,其實(shí)就是jdk中原生的java.util.Comparator類的實(shí)現(xiàn)邏輯,它里面也有個(gè)compare(T,T)方法和我們實(shí)現(xiàn)的compareTo(T,T)方法一樣的,這個(gè)Comparator其實(shí)用的就是策略模式,用原類的對(duì)象里剝離出來的自定義策略

優(yōu)化:我們實(shí)現(xiàn)自己比較策略器CompareTor時(shí),我們可以不"創(chuàng)建"類,用lamda表達(dá)式來寫,因?yàn)槲覀兊慕涌诶锩嬷挥?個(gè)方法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> 因?yàn)楹竺鎠ort(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));
    }
}

這個(gè)也被稱為 函數(shù)式接口 functional interface 接口里面只有1個(gè)方法
是函數(shù)式接口的 我們還可以聲明 它是1個(gè)@functional interface 但是我們確認(rèn) 它里面只有1個(gè)方法 不寫functional interface也可以

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

jdk1.8后接口有個(gè)特性,interface里面可以寫具體邏輯實(shí)現(xiàn)default void func(){邏輯處理;}
為什么接口里必須有方法實(shí)現(xiàn)? 因?yàn)樗С謑ambda表達(dá)式
根據(jù)開閉原則,之前的代碼都沒有寫default的 所以為了向前兼容 只用實(shí)現(xiàn)抽象方法 不實(shí)現(xiàn)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可以直接調(diào)用

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();
    }
}
直接調(diào)用接口里的方法

回顧:
認(rèn)識(shí)Comparable接口和Comparator比較器
jdk中 Comparable接口在java.lang.Comparable包中;Comparator類在java.util.Comparator包中 ;Comparable不是策略模式 Comparator是 通過實(shí)現(xiàn)子類來指定兩個(gè)對(duì)象之間如何比較大小的方式
它是一種策略模式

而一般策略模式,我們可以有很多種實(shí)現(xiàn)類,我們可以寫好,然后在配置文件中指定,靈活選擇,自己的標(biāo)準(zhǔn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容