通過行為參數化傳遞代碼

基礎概念

在軟件工程中,一個眾所周知的問題是,不管你做什么,用戶的需求肯定會變。行為參數化就是可以幫助你處理頻繁變更的需求的一種軟件開發模式。一言以蔽之,它意味著拿出一個代碼塊,把它準備好卻不去執行它,這個代碼塊以后可以被你程序的其它部分調用,這意味著你可以推遲這塊代碼的執行。

代碼的不斷演化

最初的樣子:

public static List<Apple> filterGreenApples(List<Apple> inventory) {
  List<Apple> result = new ArrayList<Apple>();
  for(Apple apple: inventory){
    if( "green".equals(apple.getColor() ) {
      result.add(apple);
    }
  }
return result;
}

這段代碼的作用不言而喻,篩選出綠色的蘋果集,一旦顏色有所變更,就要重復的寫一段類似的代碼,在需求顏色相對較多的時候明顯不切實際這樣寫的話,一個良好的原則是在編寫類似的代碼之后,嘗試將其抽象化。

把顏色作為參數之后:

public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {
  List<Apple> result = new ArrayList<Apple>();
  for (Apple apple: inventory){
    if ( apple.getColor().equals(color) ) {
      result.add(apple);
    }
  }
  return result;
}

現在,只要像下面這樣調用方法,農民朋友就會滿意了:

List<Apple> greenApples = filterApplesByColor(inventory, "green");
List<Apple> redApples = filterApplesByColor(inventory, "red");

取一種顏色的時候,只需要傳對應衍射參數就可以了,如果要取兩種或者兩種以上的顏色時,再將集合取一個并集就可以了。但很明顯的是,蘋果的判斷屬性可能不止顏色這一個,例如重量,產地等。

對你能想到的每個屬性做篩選:

public static List<Apple> filterApples(List<Apple> inventory, String color,int weight, int flag) {
  List<Apple> result = new ArrayList<Apple>();
  for (Apple apple: inventory){
    if ( (flag == 1 && apple.getColor().equals(color))  || (flag == 2 && apple.getWeight() > weight)){
      result.add(apple);
    }
  }
  return result;
}

你可以這么用(但真的很笨拙):

List<Apple> greenApples = filterApples(inventory, "green", 0, 1);
List<Apple> heavyApples = filterApples(inventory, "", 150, 2);

去最終結果就是集合之間的處理了,此處不再闡述。這個解決方案再差不過了。首先,客戶端代碼看上去糟透了。 1 和 2 是什么意思?此外,這個解決方案還是不能很好地應對變化的需求。如果這位農民要求你對蘋果的不同屬性做篩選,比如大小、形狀、產地等,又怎么辦?而且,如果農民要求你組合屬性,做更復雜的查詢,比如綠色的重蘋果,又該怎么辦?你會有好多個重復的 filter 方法,或一個巨大的非常復雜的方法。

行為化參數:

接口
public interface ApplePredicate{
  boolean test (Apple apple);
}
針對接口的不同實現
public class AppleHeavyWeightPredicate implements ApplePredicate{
  public boolean test(Apple apple){
    return apple.getWeight() > 150;
  }
}
public class AppleGreenColorPredicate implements ApplePredicate{
  public boolean test(Apple apple){
    return "green".equals(apple.getColor());
  }
}

你可以把這些標準看做篩選的不同標準,這些和“策略設計模式”相關,它讓你定義一族算法,把它們封裝起來(稱為“策略”),然后在運行時選擇一個算法。這就是行為參數化,讓方法接受多種行為(或策略)作為參數,并在內部使用,來完成不同的行為。它的調用代碼如下:

public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
  List<Apple> result = new ArrayList<>();
  for(Apple apple: inventory){
    if(p.test(apple)){
      result.add(apple);
    }
  }
  return result;
}

比如,如果農民讓你找出所有重量超過150克的紅蘋果,你只需要創建一個類來實現 ApplePredicate 就行了。 filterApples 方法的行為取決于你通過 ApplePredicate 對象傳遞的代碼。

對付啰嗦

我們都知道,人們都不愿意用那些很麻煩的功能或概念。目前,當要把新的行為傳遞給filterApples 方法的時候,你不得不聲明好幾個實現ApplePredicate 接口的類,然后實例化好幾個只會提到一次的 ApplePredicate 對象。

使用匿名類:

List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
  public boolean test(Apple apple){
    return "red".equals(apple.getColor());
  }
});

此時,根據篩選條件的不同,在匿名類中的實現也就不同。但是匿名類有兩個明顯的缺陷:1.占用了較多的空間;2.很多程序員覺得它用起來讓人費解,也就是不好理解的意思。整體來說,啰 嗦就不好;它讓人不愿意使用語言的某種功能,因為編寫和維護 啰 嗦的代碼需要很長時間,而且代碼也不易讀。好的代碼應該是一目了然的。即使匿名類處理在某種程度上改善了為一個接口聲明好幾個實體類的 啰 嗦問題,但它仍不能令人滿意。

在理想的情況下,我們想鼓勵程序員使用行為參數化模式,因為正如你在前面看到的,它讓代碼更能適應需求的變化。在后面會詳細介紹的,使用 Lambda 表達式,以下是使用示例:

List<Apple> result =filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));

filterApples 方法還只適用于 Apple 。你還可以將 List 類型抽象化,從而超越你眼前要處理的問題:

public interface Predicate<T>{
  boolean test(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p){
  List<T> result = new ArrayList<>();
  for(T e: list){
    if(p.test(e)){
      result.add(e);
    }
  }
  return result;
}

現在你可以把 filter 方法用在香蕉、桔子、 Integer 或是 String 的列表上了,下面是一些真實使用的例子:

1.用 Comparator 來排序
  匿名類時的代碼:
    inventory.sort(new Comparator<Apple>() {
      public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
      }
    });
  Lambda表達式:
    inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

2.用Runnable執行代碼塊
  匿名類時的代碼:
    Thread t = new Thread(new Runnable() {
      public void run(){
        System.out.println("Hello world");
      }
    });
  Lambda表達式:
    Thread t = new Thread(() -> System.out.println("Hello world"));  

小結

  1. 行為參數化,就是一個方法接受多個不同的行為作為參數,并在內部使用它們,完成不同行為的能力。
  2. 行為參數化可讓代碼更好地適應不斷變化的要求,減輕未來的工作量。
  3. 傳遞代碼,就是將新行為作為參數傳遞給方法。但在Java 8之前這實現起來很啰嗦。為接口聲明許多只用一次的實體類而造成的啰嗦代碼,在Java 8之前可以用匿名類來減少。
  4. Java API包含很多可以用不同行為進行參數化的方法,包括排序、線程和GUI處理。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容