[TOC] 引入 大家先考慮一個場景, 有一個整形數組, 我們希望通過調用一個工具類的排序方法就能對該數組進行排序. 請看下麵的代碼: Comparable介面的來龍去脈 通過上面的代碼, 我們能夠輕易地對整形數組進行排序, 那麼如果現在有了新需求, 需要對浮點類型數據進行排序, 排序 ...
目錄
引入
- 大家先考慮一個場景, 有一個整形數組, 我們希望通過調用一個工具類的排序方法就能對該數組進行排序. 請看下麵的代碼:
public class Strategy {
public static void main(String[] args) {
int[] arr = {5, 3, 1, 7, 2};
new DataSorter().sort(arr);//調用工具類進行排序
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
}
}
class DataSorter{//用於排序的工具類
public void sort(int[] arr){//調用sort方法進行排序, 此處使用冒泡排序
for(int i = arr.length - 1; i > 0; i--){
for(int j = 0; j < i; j++){
if(arr[j] > arr[j + 1])
swap(arr, j, j + 1);
}
}
}
private void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
Comparable介面的來龍去脈
- 通過上面的代碼, 我們能夠輕易地對整形數組進行排序, 那麼如果現在有了新需求, 需要對浮點類型數據進行排序, 排序工具類應該如何做呢?
- 或許你會想, 不如就新添加一個排序方法, 方法的參數類型為
float
類型, 把int類型數組的排序演算法複製一遍不就可以了嗎? - 那如果我繼續追問, 如果現在要對一隻貓進行排序, 那應該怎麼做呢? 貓的類如下
class Cat{
private int age;//貓的年齡
private int weight;//貓的體重
//get / set 方法...
}
- 你也許會順著原來的思路回答, 照樣copy一份排序的演算法, 修改方法參數, 然後在比較的地方指定比較貓的年齡或體重不就可以了嗎?
public void sort(Cat[] arr){//以貓數組作為參數
for(int i = arr.length - 1; i > 0; i--){
for(int j = 0; j < i; j++){
if(arr[j].getAge() > arr[j + 1].getAge())//根據貓的年齡作比較
swap(arr, j, j + 1);
}
}
}
- 但仔細想想, 如果還要繼續比較小狗, 小雞, 小鴨等各種對象, 那麼這個排序工具類的代碼量豈不是變得很大? 為了能讓排序演算法的可重用性高一點, 我們希望排序工具中的
sort()
方法可以對任何調用它的對象進行排序. - 你可能會想: 到對任何對象都能排序, 把
sort()
方法的參數改為Object
類型不久可以了嘛. 這個方向是對的, 但是問題是, 當拿到兩個Object
類型對象, 應該根據什麼規則進行比較呢? - 這個時候我們自然而然地就希望調用工具類進行排序的對象本身就具備自己的
比較法則
, 這樣在排序的時候就能直接調用對象的排序法則進行排序了. - 我們把比較法則抽象為
Comparable
介面, 凡是要進行比較的類都要實現Comparable
介面, 並且定義自己的比較法則, 也就是CompareTo()
方法. - 這樣當我們在封裝工具時, 就可以直接對實現了
Comparable
介面的對象進行比較, 不用擔心比較的細節了.
public class Strategy {
public class Strategy {
public static void main(String[] args) {
// Integer[] arr = {5, 3, 1, 7, 2};//註意這裡把int改為Integer, Integer是Object的子類
Cat[] arr = {new Cat(3, 3), new Cat(1, 1), new Cat(5, 5)};
DataSorter ds = new DataSorter();
ds.sort(arr);
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
}
}
}
class DataSorter{//用於排序的工具類
public void sort(Object[] arr){//參數類型為Object
for(int i = arr.length - 1; i > 0; i--){
for(int j = 0; j < i; j++){
Comparable c1 = (Comparable) arr[j];//先轉為Comparable類型
Comparable c2 = (Comparable) arr[j + 1];
if(c1.CompareTo(c2) == 1)//調用CompareTo()進行比較, 不關心具體的實現
swap(arr, j, j + 1);
}
}
}
private void swap(Object[] arr, int i, int j){
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
class Cat implements Comparable{
private int age;
private int weight;
@Override
public int CompareTo(Object o) {
if(o instanceof Cat){//先判斷傳入的是否是Cat類對象, 不是則拋異常
Cat c = (Cat) o;
if(this.age > c.age) return 1;
else if (this.age < c.age) return -1;
else return 0;
}
throw null == o ? new NullPointerException() : new ClassCastException();
}
// get / set ...
//toString() ...
}
interface Comparable{
public int CompareTo(Object o);
}
引入Comparator介面
- 相信看了上面的
Comparable
介面來由, 大家會感覺整個設計又美好了一些, 但是其中還有漏洞. 我們在Cat
類的CompareTo()
方法中, 對貓的比較策略是寫死的, 現在我們按貓的年齡比較大小, 如果哪天我們想按照貓的體重比較大小, 又要去修改源碼了. 有沒有擴展性更好的設計? - 我們可以讓用戶自己定義一個比較器類, 對象可以根據用戶指定的比較器比較大小.
- 整個邏輯是: 如果這個對象需要進行比較, 那麼它必須實現
Comparable
介面, 但是它具體是怎麼比較的, 則通過具體的Comparator
比較器進行比較. - 當然這裡少不了多態, 我們首先要定義一個比較器介面
Comparator
, 用戶的比較器需要實現Comparator
介面, 下麵上代碼:
public class Strategy {
public static void main(String[] args) {
Cat[] arr = {new Cat(3, 3), new Cat(1, 1), new Cat(5, 5)};
DataSorter ds = new DataSorter();
ds.sort(arr);
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
}
}
class DataSorter{//用於排序的工具類
public void sort(Object[] arr){//參數類型為Object
for(int i = arr.length - 1; i > 0; i--){
for(int j = 0; j < i; j++){
Comparable c1 = (Comparable) arr[j];//先轉為Comparable類型
Comparable c2 = (Comparable) arr[j + 1];
if(c1.CompareTo(c2) == 1)//背後已經轉換為使用Comparator的定義的規則進行比較
swap(arr, j, j + 1);
}
}
}
private void swap(Object[] arr, int i, int j){
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
class Cat implements Comparable{
private int age;
private int weight;
private Comparator comparator = new CatAgeComparator();//預設持有年齡比較器
@Override
public int CompareTo(Object o) {
return comparator.Compare(this, o);//調用比較器比較而不是直接在此寫比較法則
}
// get / set / toString ...
}
interface Comparable{
public int CompareTo(Object o);
}
interface Comparator{
public int Compare(Object o1, Object o2);
}
//用戶自己定義的, 按照貓的年齡比較大小的比較器
class CatAgeComparator implements Comparator{
@Override
public int Compare(Object o1, Object o2) {
Cat c1 = (Cat) o1;
Cat c2 = (Cat) o2;
if(c1.getAge() > c2.getAge()) return 1;
else if(c1.getAge() < c2.getAge()) return -1;
else return 0;
}
}
//按照貓的體重比較大小的比較器
class CatWeightComparator implements Comparator{
@Override
public int Compare(Object o1, Object o2) {
Cat c1 = (Cat) o1;
Cat c2 = (Cat) o2;
if(c1.getWeight() > c2.getWeight()) return 1;
else if(c1.getWeight() < c2.getWeight()) return -1;
else return 0;
}
}
什麼是策略模式?
- 在上面的例子中, 我們自己定義了
Comparable
介面和Comparator
介面, 其實這兩個介面都是Java自帶的, 通過上面的代碼示例, 想必大家也應該知道了為什麼會有這兩個介面. - 其實
Comparable
定義的就是一種比較的策略, 這裡的策略你可以理解為一個功能, 然而策略有了, 我們還需要有具體的策略實現, 於是便有了Comparator
介面.
- 這裡再舉一個例子方便大家理解.
- 現在有一個坦克小游戲, 坦克要能夠發射炮彈, 那麼我們可以認為發射炮彈就是一種策略, 但是具體到發送什麼炮彈, 這可以由具體的策略實現.
- 到GitHub上看看該坦克游戲
- 首先定義發射炮彈這種策略
public interface Fire {
public void fire();//發射炮彈的策略
}
- 為了實現發射炮彈這種策略, 定義策略的具體實現, 也就是定義發射炮彈動作
public interface FireAction {
public void fireAction(Tank tank);
}
- 坦克想要發送炮彈必須實現
Fire()
介面, 而且坦克擁有發射炮彈的動作, 至於動作的具體實現, 這裡預設給出只發射一顆炮彈的動作.
public class Tank implements TankHitListener, Fire {
//省略各種屬性方法...
private FireAction fireAction = new NormalFireAction();//預設動作是只發射一顆炮彈
@Override
public void fire() {
fireAction.fireAction(this);
}
//...
使用了策略模式有什麼好處?
- 以上面的坦克游戲為例, 當把發射炮彈定義為一種策略後, 能發射炮彈的對象就不只坦克一個了, 如果游戲中有機關, 可以讓機關也實現
fire()
介面, 獲得發射炮彈的能力. - 而且在定義策略後我們可以根據策略給出不同的實現方式, 比方說坦克發射炮彈的動作是每次只發射一顆炮彈, 而機關是每次向八個方向發射一顆炮彈. 非常靈活.
- 結束