《仁王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型別進行排序怎麼辦?
//陣列型別變成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個型別的變化,來來回回寫好多遍
而且因為你寫死了比較的標準,你也不能通過傳入Object[] arr的方式來泛化,因為Object[] arr是無法用<符號來比較大小的,會報錯
怎麼辦呢?策略模式就是我們不關注演算法的具體過程 前提這個演算法有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方法的 所以你的比較標準還是有問題的
所以我們傳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是 通過實現子類來指定兩個物件之間如何比較大小的方式
它是一種策略模式
而一般策略模式,我們可以有很多種實現類,我們可以寫好,然後在配置檔案中指定,靈活選擇,自己的標準