1. 程式人生 > 遊戲攻略 >《仁王2》冷門魂核推薦

《仁王2》冷門魂核推薦

假設現在我有個需求,把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是 通過實現子類來指定兩個物件之間如何比較大小的方式
它是一種策略模式

而一般策略模式,我們可以有很多種實現類,我們可以寫好,然後在配置檔案中指定,靈活選擇,自己的標準