給jdk寫注釋系列之jdk1.6容器(9)-Strategy設計模式之Comparable&Comparator接口
排序類:
1 public class DataSort { 2 3 public static void sort( int[] arr) { 4 for (int i = arr.length; i > 0; i--) { 5 for (int j = 0; j < i - 1; j++) { 6 // 如果前一個比后一個大,那么就把大值交換到后面去 7 if (arr[j] > arr[j + 1]) { 8 int temp = arr[j]; 9 arr[j] = arr[j + 1]; 10 arr[j + 1] = temp; 11 } 12 } 13 } 14 } 15 }
測試類:
1 public class Test { 2 3 public static void main(String[] args) { 4 int[] arr = new int[] { 9, 5, 2, 7 }; 5 DataSort. sort(arr); 6 for (int i : arr) { 7 System. out.print(i + " " ); 8 } 9 } 10 }
運行一下看看結果:
ok,我們已經完成排序了,但是,我不僅要去對int進行排序,還要對其他的事物進行排序,比如說人,那怎么做呢?
首先我們需要先定義一個Penson類,有什么屬性呢,簡單一點就有姓名,年齡和收入,定義一下:
1 public class Person { 2 3 private String name ; 4 private int age; 5 private int money; 6 7 public Person(String name, int age, int money) { 8 this.name = name; 9 this.age = age; 10 this.money = money; 11 } 12 13 public String getName() { 14 return name ; 15 } 16 17 public void setName(String name) { 18 this.name = name; 19 } 20 21 public int getAge() { 22 return age ; 23 } 24 25 public void setAge(int age) { 26 this.age = age; 27 } 28 29 public int getMoney() { 30 return money ; 31 } 32 33 public void setMoney(int money) { 34 this.money = money; 35 } 36 37 @Override 38 public String toString() { 39 return "Person [name=" + name + ", age=" + age + ", money=" + money 40 + "]"; 41 } 42 43 } 44
Penson這個類定義完成了,怎么進行排序呢,比如你說誰收入高誰老大,ok那么我們就按收入寫一下排序方法:
1 public class DataSort { 2 3 public static void sort( int[] arr) { 4 for (int i = arr.length; i > 0; i--) { 5 for (int j = 0; j < i - 1; j++) { 6 // 如果前一個比后一個大,那么就把大值交換到后面去 7 if (arr[j] > arr[j + 1]) { 8 int temp = arr[j]; 9 arr[j] = arr[j + 1]; 10 arr[j + 1] = temp; 11 } 12 } 13 } 14 } 15 16 public static void sort(Person[] arr) { 17 for (int i = arr.length; i > 0; i--) { 18 for (int j = 0; j < i - 1; j++) { 19 // 如果前一個比后一個大,那么就把大值交換到后面去 20 if (arr[j].getMoney() > arr[j + 1].getMoney()) { 21 Person temp = arr[j]; 22 arr[j] = arr[j + 1]; 23 arr[j + 1] = temp; 24 } 25 } 26 } 27 } 28 }
我們在DataSort中重寫了一個sort(Person[] arr)方法,用來給Person類進行排序,測試一下吧:
1 public class Test { 2 3 public static void main(String[] args) { 4 // int[] arr = new int[] { 9, 5, 2, 7 }; 5 // DataSort.sort(arr); 6 // for (int i : arr) { 7 // System.out.print(i + " "); 8 // } 9 10 Person p1 = new Person("張三" , 25, 100); // 張三,25歲,年薪100w 11 Person p2 = new Person("李四" , 30, 10); // 李四,30歲,年薪10w 12 Person p3 = new Person("王五" , 20, 1000); // 王五,25歲,年薪1000w 13 Person[] arr = new Person[] { p1, p2, p3 }; 14 15 DataSort. sort(arr); 16 for (Person p : arr) { 17 System. out.println(p + " " ); 18 } 19 } 20 }
看下結果:
Person [name=李四, age=30, money=10] Person [name=張三, age=25, money=100] Person [name=王五, age=20, money=1000]
結果正確對不對,是不是感覺自己so牛x,我寫的排序類,既可以排序整數int,又可以排序自定義的Person類,是不是有點飄飄然了。
等等,這有一盆冷水,我還要求可以對阿貓阿狗進行排序,你說再重寫一個sort方法唄,那我還要求對電腦手機進行排序,對花花草草進行排序。。。現在是不是很苦惱,你一定在想,我要寫一種萬能的排序方法可以對任何東西進行排序。這個時候你沒有瘋而是進入設計的大門了,此時什么多態、封裝,繼承等等概念撲面而來,可惜的是你還是寫不出萬能的排序方法。能不能換一種思路,我們來提供一個標準,一個方法論,只提供排序的算法,具體的怎么比較大小你自己看著辦,這么做可以嗎?來試一下:
2.排序的方法論
2.1 comparable
我們先明確下目標,我們要實現的任然是排序,但是我們不去進行大小比較,比較大小的功能由具體的類自己負責,這么一想好像就清晰了許多的樣子。
首先我們定義一個接口,提供一個標準給要進行排序的類:
1 public interface MyComparable { 2 3 /** 4 * 返回值大于0說明當前比較的Object大,小于0說明被比較的Object大, 5 * 等于0說明兩個Object相等 6 */ 7 public int compareTo(Object o); 8 }
MyComparable接口我們寫好了,我們規定,只要排序就必須實現MyComparable接口,而且要重寫compareTo方法,返回一個int值來告訴我誰大誰小。
DataSort的排序方法sort怎么做呢,很簡單了:
1 public class DataSort { 2 3 public static void sort(MyComparable[] arr) { 4 for (int i = arr.length; i > 0; i--) { 5 for (int j = 0; j < i - 1; j++) { 6 if (arr[j].compareTo(arr[j + 1]) > 0) { 7 MyComparable temp = arr[j]; 8 arr[j] = arr[j + 1]; 9 arr[j + 1] = temp; 10 } 11 } 12 } 13 } 14 15 }
是不是很簡單了,只要用compareTo的返回結果就可以了,下面我們讓Person實現MyComparable接口試一下:
1 public class Person implements MyComparable { 2 3 private String name ; 4 private int age; 5 private int money; 6 7 public Person(String name, int age, int money) { 8 this.name = name; 9 this.age = age; 10 this.money = money; 11 } 12 13 public String getName() { 14 return name ; 15 } 16 17 public void setName(String name) { 18 this.name = name; 19 } 20 21 public int getAge() { 22 return age ; 23 } 24 25 public void setAge(int age) { 26 this.age = age; 27 } 28 29 public int getMoney() { 30 return money ; 31 } 32 33 public void setMoney(int money) { 34 this.money = money; 35 } 36 37 @Override 38 public String toString() { 39 return "Person [name=" + name + ", age=" + age + ", money=" + money 40 + "]"; 41 } 42 43 @Override 44 public int compareTo(Object o) { 45 Person p = (Person)o; 46 if (this.money > p. money) { 47 return 1; 48 } else { 49 return -1; 50 } 51 } 52 53 }
測試一下:
1 public class Test { 2 3 public static void main(String[] args) { 4 // int[] arr = new int[] { 9, 5, 2, 7 }; 5 // DataSort.sort(arr); 6 // for (int i : arr) { 7 // System.out.print(i + " "); 8 // } 9 10 Person p1 = new Person("張三" , 25, 100); // 張三,25歲,年薪100w 11 Person p2 = new Person("李四" , 30, 10); // 李四,30歲,年薪10w 12 Person p3 = new Person("王五" , 20, 1000); // 王五,25歲,年薪1000w 13 Person[] arr = new Person[] { p1, p2, p3 }; 14 15 DataSort. sort(arr); 16 for (Person p : arr) { 17 System. out.println(p + " " ); 18 } 19 } 20 }
看一下結果:
Person [name=李四, age=30, money=10] Person [name=張三, age=25, money=100] Person [name=王五, age=20, money=1000]
和預期的一樣對不對,也就是說明我們的排序沒有問題,現在你又開始飄飄然了,我寫的排序終于完美了,可以對任何類進行排序,什么阿貓阿狗你只要實現MyComparable接口,統統來吧,哈哈哈。
等等,這里還有一盆冷水,我讓你對長整型Long進行排序,,Long沒問題啊、只要實現。。。實現什么,是不是傻了,Long是已經存在的類,你不可能重新編譯它讓它實現你的MyComparable接口吧,哎,這可怎么辦。。。
等等先別哭,我還有另一盆冷水,對于Person類我的想法變了,不想用收入作為比較了,我想按照年齡進行比較,也沒準我某天想按照年齡+收入的組合進行比較,反正我就是這么任性,反正我就是讓你現在猜不透。你的需要一天三變,我不能把代碼該來該去吧,這樣的話開發急了會和產品打架的,怎么辦呀,這兩個問題我一個不會弄。。。
2.2 comparator
那么問題來了,想一下,能不能進一步的封裝,既然我不能去改變一些類的代碼,那么我能不能將比較大小的邏輯拿出來呢?既然你的需要總是變,而我又預測不到,那么我能不能把你的需求也進行抽象,你得需求細節你自己實現,我提供給你邏輯框架呢?答案是肯定的,說干就干!
我們要將比較大小的邏輯拿出來,首先還是要定義一個標準,要使用我進行排序,就得安裝規矩來。
1 public interface MyComparator { 2 public int compare(Object o1, Object o2); 3 }
注意,這個接口不是讓你的排序類來實現的,看看我sort怎么寫:
1 public class DataSort { 2 3 public static void sort(MyComparable[] arr) { 4 for (int i = arr.length; i > 0; i--) { 5 for (int j = 0; j < i - 1; j++) { 6 if (arr[j].compareTo(arr[j + 1]) > 0) { 7 MyComparable temp = arr[j]; 8 arr[j] = arr[j + 1]; 9 arr[j + 1] = temp; 10 } 11 } 12 } 13 } 14 15 public static void sort(Object[] arr, MyComparator c) { 16 for (int i = arr.length; i > 0; i--) { 17 for (int j = 0; j < i - 1; j++) { 18 if (c.compare(arr[j], arr[j + 1]) > 0) { 19 Object temp = arr[j]; 20 arr[j] = arr[j + 1]; 21 arr[j + 1] = temp; 22 } 23 } 24 } 25 } 26 27 }
我又重寫了一個sort方法,你只要把你的比較大小邏輯提供給我,我就能給你排序了。來試一下:
首先我寫一個具體的比較大小邏輯類:
1 public class PersonAgeComparator implements MyComparator { 2 3 @Override 4 public int compare(Object o1, Object o2) { 5 Person p1 = (Person) o1; 6 Person p2 = (Person) o2; 7 8 if (p1.getAge() - p2.getAge() > 0) { 9 return 1; 10 } else { 11 return -1; 12 } 13 } 14 15 }
具體看看怎么來用:
1 public class Test { 2 3 public static void main(String[] args) { 4 // int[] arr = new int[] { 9, 5, 2, 7 }; 5 // DataSort.sort(arr); 6 // for (int i : arr) { 7 // System.out.print(i + " "); 8 // } 9 10 Person p1 = new Person("張三" , 25, 100); // 張三,25歲,年薪100w 11 Person p2 = new Person("李四" , 30, 10); // 李四,30歲,年薪10w 12 Person p3 = new Person("王五" , 20, 1000); // 王五,25歲,年薪1000w 13 Person[] arr = new Person[] { p1, p2, p3 }; 14 15 DataSort. sort(arr, new PersonAgeComparator()); 16 for (Person p : arr) { 17 System. out.println(p + " " ); 18 } 19 } 20 }
我只需要把我的比較大小邏輯類傳入sort就可以了,看下結果:
Person [name=王五, age=20, money=1000] Person [name=張三, age=25, money=100] Person [name=李四, age=30, money=10]
哇,成功了,現在你在告訴我,要比較Long類型,ok啊,寫一個LongComparator就行了,,還要組合Person類的年齡和收入,那我寫一個PersonAgeAndMoneyComparator就好了,這下完美了,我已經做到了足夠靈活,任意的擴展,哈哈哈。。。
別著急,我還有問題(我弄死你),這次不是冷水了,放心。想一個問題,現在Person類和PersonAgeComparator類兩個是獨立的,它們是靠sort這個排序方法聯系在一起的。但是我想讓他們兩個聯系密切一些,我們在講低耦合的時候也在講高內聚,畢竟Person類和他的比較大小邏輯是緊密聯系的,怎么辦呢,那就是將Comparator封裝成Person的一個屬性。
來看一下:
1 public class Person implements MyComparable { 2 3 private String name ; 4 private int age; 5 private int money; 6 7 private MyComparator comparator = new PersonAgeComparator(); 8 9 public Person(String name, int age, int money) { 10 this.name = name; 11 this.age = age; 12 this.money = money; 13 } 14 15 public Person(String name, int age, int money, MyComparator comparator) { 16 this.name = name; 17 this.age = age; 18 this.money = money; 19 this.comparator = comparator; 20 } 21 22 public String getName() { 23 return name ; 24 } 25 26 public void setName(String name) { 27 this.name = name; 28 } 29 30 public int getAge() { 31 return age ; 32 } 33 34 public void setAge(int age) { 35 this.age = age; 36 } 37 38 public int getMoney() { 39 return money ; 40 } 41 42 public void setMoney(int money) { 43 this.money = money; 44 } 45 46 @Override 47 public String toString() { 48 return "Person [name=" + name + ", age=" + age + ", money=" + money 49 + "]"; 50 } 51 52 @Override 53 public int compareTo(Object o) { 54 return comparator .compare(this, o); 55 } 56 57 }
我們將MyComparator接口封裝成了Person的一個屬性,具體要用什么樣的比較大小邏輯,你調用方傳給我,當然你不傳的話,我自己也有一個默認的策略,這樣我就不怕你忘記了。
講到這里Comparable和Comparator就講完了,但是好像有個概念我們還沒有說,那就是什么是Strategy設計模式 。
3.Strategy設計模式
Strategy設計模式中文叫做策略設計模式,其實我們就算不知道什么是策略模式不是也將上面的問題搞定了么,所以啊,不要太在意于概念的東西,首先你要會用,能解決。
不過還是得來解釋下策略模式的概念,大體說,不標準:策略模式是針對一組算法,將每個算法封裝到具有共同接口的獨立的類中,使得他們可以互相的替換,而客戶端在調用的時候能夠互不影響。
策略模式通常有這么幾個角色:
(1) 環境(Context)角色: 持有一個Strategy的引用。——Person類
(2) 抽象策略(Strategy)角色: 這是一個抽象角色,通常由一個接口或抽象類實現。此角色給出所有的具體策略類所需的接口。——MyComparator接口
(3) 具體策略(ConcreteStrategy)角色: 包裝了相關的算法或行為。——PersonAgeComparator類
策略模式的優缺點是什么:
優點:(1)將具體算法邏輯與客戶類分離,(2)避免了大量的if else判斷
缺點:(1)每個算法一個類,產生了太多的類,(2)客戶端要知道所有的策略類,以便決定使用哪一個。
想想怎么樣能有嘗試解決策略模式的缺點。。。那就是工廠模式。ok這里不是主要講設計模式,就到這里了。
4.回憶TreeMap的比較大小
1 public V put(K key, V value) { 2 ...... 3 ...... 4 5 // split comparator and comparable paths 6 // 當前使用的比較器 7 Comparator<? super K> cpr = comparator ; 8 // 如果比較器不為空,就是用指定的比較器來維護TreeMap的元素順序 9 if (cpr != null) { 10 // do while循環,查找key要插入的位置(也就是新節點的父節點是誰) 11 do { 12 // 記錄上次循環的節點t 13 parent = t; 14 // 比較當前節點的key和新插入的key的大小 15 cmp = cpr.compare(key, t. key); 16 // 新插入的key小的話,則以當前節點的左孩子節點為新的比較節點 17 if (cmp < 0) 18 t = t. left; 19 // 新插入的key大的話,則以當前節點的右孩子節點為新的比較節點 20 else if (cmp > 0) 21 t = t. right; 22 else 23 // 如果當前節點的key和新插入的key想的的話,則覆蓋map的value,返回 24 return t.setValue(value); 25 // 只有當t為null,也就是沒有要比較節點的時候,代表已經找到新節點要插入的位置 26 } while (t != null); 27 } 28 else { 29 // 如果比較器為空,則使用key作為比較器進行比較 30 // 這里要求key不能為空,并且必須實現Comparable接口 31 if (key == null) 32 throw new NullPointerException(); 33 Comparable<? super K> k = (Comparable<? super K>) key; 34 // 和上面一樣,喜歡查找新節點要插入的位置 35 do { 36 parent = t; 37 cmp = k.compareTo(t. key); 38 if (cmp < 0) 39 t = t. left; 40 else if (cmp > 0) 41 t = t. right; 42 else 43 return t.setValue(value); 44 } while (t != null); 45 } 46 47 ...... 48 ...... 49 }
現在理解TreeMap為什么要判斷有沒有Comparator了吧。。如果沒有的話,就用key去比較大小,但是要求key實現Comparable接口。
Strategy設計模式之Comparable&Comparator接口 完!
參見: