摘要:但是到了第二天,他突然告訴你其實我還想找出所有重量超過克的蘋果。現在,農民要求需要篩選紅蘋果。那么,我們就可以根據條件創建一個類并且實現通過謂詞篩選紅蘋果并且是重蘋果酷,現在方法的行為已經取決于通過對象來實現了。
通過行為參數化傳遞代碼 行為參數化
在《Java8實戰》第二章主要介紹的是通過行為參數化傳遞代碼,那么就來了解一下什么是行為參數化吧。
在軟件工程中,一個從所周知的問題就是,不管你做什么,用戶的需求總是會變的(PM的需求總是會變的)。比方說,有個應用程序是幫助農民了解自己的庫存。這位農民可能想有一個查找庫存中所有綠色蘋果的功能。但是到了第二天,他突然告訴你:“其實我還想找出所有重量超過150克的蘋果。”,你一想簡單嘛不就是改一下條件而已。于是過了兩天,他又說:“要是我可以篩選即使綠色的蘋果,重量也超過150克的蘋果。”,這樣頻繁的改需求也不太好,面對這樣的情況理想狀態下應該把工作量降到最低。此外,類似的功能實現起來應該還是很簡單,而且利于長期維護。
行為參數化就是要幫助你處理頻繁更變的需求的一種軟件開發模式。一言以蔽之,它意味著拿出一個代碼塊,把它準備好卻不去執行它。這個代碼塊以后可以被你程序的其他部分調用,這意味著你可以推遲這塊代碼的執行。例如,你可以將代碼塊作為參數傳遞給另外一個方法,稍后再去執行它。這樣,這個方法的行為就基于那塊代碼被參數化了。
應對不斷變化的需求想要編寫能應對變化的需求并不容易。讓我們來看一個例子,我們將會逐漸的改進這個例子,以展示一些讓代碼更靈活的做法。就像農場庫存程序而言,你需要實現一個從列表中篩選綠蘋果的功能。
篩選蘋果篩選綠蘋果,可能你選擇最初的解決方案就是這樣:
private static ListfilterGreenApples(List apples) { List appleList = new ArrayList (); for (Apple apple : apples) { if ("green".equals(apple.getColor())) { appleList.add(apple); } } return appleList; }
現在代碼中就是篩選綠蘋果。但現在農民改主意了,他還想要篩選紅蘋果。按照最簡單的方法就是,把方法復制一下并且改一下條件為篩選紅蘋果的條件。是的,這樣做起來很簡單,要是農民想要篩選多種顏色:青色、深紅、淡紅...這種方法就不太適合了。
優化代碼,通過顏色作為參數篩選蘋果:
private static ListfilterApplesByColor(List apples, String color) { List appleList = new ArrayList (); for (Apple apple : apples) { if (color.equals(apple.getColor())) { appleList.add(apple); } } return appleList; }
很簡單對吧。現在,農民又有想法:“能篩選出輕蘋果和重蘋果就好啦!一般重蘋果的重量是150克。”你可能早就想到了需要通過重量來篩選蘋果,于是你又把參數穿進來作為條件進行篩選。
將重量作為參數,進行重蘋果篩選:
private static ListfilterApplesByWeight(List apples, int weight) { List appleList = new ArrayList (); for (Apple apple : apples) { if (apple.getWeight() > weight) { appleList.add(apple); } } return appleList; }
是的,解決方法很簡單,但是你復制了大部分的代碼來實現遍歷庫存,并對每個蘋果應用篩選條件。這樣破壞了DRY(Don"t Repeat Yourself 不要重復自己)的軟件工程原則。或許,你一下就想到了這辦法,將所有的參數都放在一個方法中,這樣就可以簡化很多代碼了。
第三次嘗試,對你能想到的每個屬性做篩選:
private static ListfilterApples(List apples, String color, int weight, boolean flag) { List appleList = new ArrayList (); for (Apple apple : apples) { boolean result = (flag && apple.getWeight() > weight) || (!flag && color.equals(apple.getColor())); if (result) { appleList.add(apple); } } return appleList; }
代碼看起來很簡單,但是感覺卻是不太好。如果不把注釋寫清楚,別人閱讀你代碼時根本就不知道flag是干嘛用的。要是,農民突然又有個想法,需用通過大小、形狀、產地等條件來進行篩選怎么辦?所以,我們需要利用行為參數化來解決這個問題,提高代碼的靈活性。
行為參數化目前,你需要一種比添加很多參數更好的方法來應對變化的需求。讓我們退一步來看看更高層次的抽象。一種可能解決方案是對你的懸著標準建模:你考慮的是蘋果,需要根據Apple的某些屬性(比如它是綠色的嗎?重量超過150克嗎?)來返回一個boolean值。是的,你可能已經想到了第一章中介紹到了的謂詞。
根據謂詞進行篩選
首先,我們應該定義一個接口來對選擇標準建模:
public interface ApplePredicate { /** * 根據給定的參數計算此謂詞。 * * @param apple * @return */ boolean test(Apple apple); }
可以用ApplePredicate的實現類來代表不同的選擇標準:
只篩選綠蘋果
public class AppleGreenColorPredicate implements ApplePredicate { @Override public boolean test(Apple apple) { return "green".equals(apple.getColor()); } }
只篩選重蘋果
public class AppleHeavyWeightPredicate implements ApplePredicate { @Override public boolean test(Apple apple) { return apple.getWeight() > 150; } }
你可以把這些標準看作filter的不同行為。這就像策略設計模式一樣,它讓你定義一組方法,把它們封裝起來,然后在運行時選擇一個方法。這里,方法就是ApplePredicate,不同的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。
你可以將filterApples方法接受一個ApplePredicate對象,對Apple做條件測試。這樣就是行為參數化:讓方法接受多種行為作為參數,并在內部使用,來完成不同的行為。
根據抽象條件篩選
private static ListfilterApples(List apples, ApplePredicate applePredicate) { List appleList = new ArrayList<>(); for (Apple apple : apples) { if (applePredicate.test(apple)) { appleList.add(apple); } } return appleList; }
代碼的傳遞/行為
酷,這段代碼看起來很多了,讀起來、用起來也更容易!現在你可以創建不同的ApplePredicate對象,并將它們傳遞給filterApples方法。這樣就可以根據不同的條件來創建一個類并且實現ApplePredicate就可以了。
現在,農民要求需要篩選紅蘋果。那么,我們就可以根據條件創建一個類并且實現ApplePredicate:
public class AppleRedAndHeavyPredicate implements ApplePredicate { @Override public boolean test(Apple apple) { return "red".equals(apple.getColor()) && apple.getWeight() > 150; } }
ListfilterApples2 = filterApples(apples, new AppleRedAndHeavyPredicate()); System.out.println("通過謂詞篩選紅蘋果并且是重蘋果:" + filterApples2);
酷,現在filterApples方法的行為已經取決于通過ApplePredicate對象來實現了。這就是行為參數化了!
但是,你有沒有發現,我們每次新增一個條件就需要新增一個類。這樣做有點太過于麻煩,或許我們可以通過Lambda,將表達式傳遞給filterApples方法,這樣就無需定義多個ApplePredicate類,從而去掉不必要的代碼,并減輕工作量。
多種行為,一個參數
行為參數化的好處在與你可以把迭代要篩選的集合的邏輯與對集合中每個元素應用的行為區分開來。這樣你就可以重復使用同一個方法,給它不同的行為來達到不同的目的。
為了能夠對參數化行為運用自如,并且簡化代碼,我們來嘗試將參數通過Lambda的方式傳遞給filterApples。
通過Lambda的方式來篩選紅蘋果:
ListfilterApples3 = filterApples(apples, apple -> "red".equals(apple.getColor()));
通過Lambda的方式來篩選紅蘋果并且是重蘋果:
ListfilterApples4 = filterApples(apples, apple -> "red".equals(apple.getColor()) && apple.getWeight() > 150);
是的,使用的已經還是原來的條件,并且不再需要根據不同的條件再去實現一個ApplePredicate類了,這樣極大的簡化了代碼。但是,農民又有一個需求了:“現在,不只是需要對蘋果進行篩選了,還需要對香蕉、橘子、草莓進行篩選了。”
但是,我們目前的代碼只能夠對蘋果進行篩選而已,為了解決這個問題,我們可以將類型定義為泛型,這樣就不只是只能對蘋果進行篩選了。
使用謂詞其實,我們可以不需要去定義謂詞,因為在Java中就一個了Predicate了,我們可以使用它并且實現我們的功能。
定義一個泛型的filter方法:
private staticList filter(List list, Predicate predicate) { List result = new ArrayList<>(); for (T t : list) { if (predicate.test(t)) { result.add(t); } } return result; }
這個方法是一個通用的篩選方法,不只是可以用于篩選蘋果。
篩選重蘋果:
ListheavyApples = filter(apples, (Apple apple) -> apple.getWeight() > 150);
篩選能被2整除的數:
Listnumbers = Arrays.asList(10, 11, 8, 5, 1, 2, 29, 18); List integerList = filter(numbers, number -> number % 2 == 0);
是不是很酷?現在的代碼簡潔性和靈活性都很高,在Java8之前這些都是不可能做到的!
現在,你已經感覺到了行為參數化是一個很有用的模式,它能夠輕松地適應不斷變化的需求。在Java中很多方法都可以用不同的行為來參數化,比如使用Comparator排序,用Runnable執行一個代碼塊等等。
使用Comparator來排序:
apples.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
或者這樣:
apples.sort(Comparator.comparing(Apple::getWeight));
使用Runnable執行某個代碼塊:
Thread t = new Thread(() -> System.out.println("HelloWorld"));總結
行為參數化,就是一個方法接受多個不同的行為作為參數,并在內部使用它們,完成不同行為的能力。
行為參數化可以讓代碼更好的適應不斷變化的要求,減輕工作量。
傳遞代碼,就是將新行為作為參數傳遞給方法。但在Java8之前這實現起來很啰嗦。為接口生命許多只是用一次的實體類而造成的啰嗦代碼,在Java8之前采用匿名類來減少。
JavaAPI包含了很多可以用不同行為進行參數化的方法,包括排序、線程等。
代碼示例:Github:chap2
公眾號文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76644.html
摘要:之前,使用匿名類給蘋果排序的代碼是的,這段代碼看上去并不是那么的清晰明了,使用表達式改進后或者是不得不承認,代碼看起來跟清晰了。這是由泛型接口內部實現方式造成的。 # Lambda表達式在《Java8實戰》中第三章主要講的是Lambda表達式,在上一章節的筆記中我們利用了行為參數化來因對不斷變化的需求,最后我們也使用到了Lambda,通過表達式為我們簡化了很多代碼從而極大地提高了我們的...
摘要:依舊使用剛剛對蘋果排序的代碼。現在,要做的是篩選出所有的綠蘋果,也許你會這一個這樣的方法在之前,基本上都是這樣寫的,看起來也沒什么毛病。但是,現在又要篩選一下重量超過克的蘋果。 《Java8實戰》-讀書筆記第一章(01) 最近一直想寫點什么東西,卻不知該怎么寫,所以就寫寫關于看《Java8實戰》的筆記吧。 第一章內容較多,因此打算分幾篇文章來寫。 為什么要關心Java8 自1996年J...
摘要:實戰讀書筆記第一章從方法傳遞到接著上次的,繼續來了解一下,如果繼續簡化代碼。去掉并且生成的數字是萬,所消耗的時間循序流并行流至于為什么有時候并行流效率比循序流還低,這個以后的文章會解釋。 《Java8實戰》-讀書筆記第一章(02) 從方法傳遞到Lambda 接著上次的Predicate,繼續來了解一下,如果繼續簡化代碼。 把方法作為值來傳遞雖然很有用,但是要是有很多類似與isHeavy...
摘要:上下文比如,接受它傳遞的方法的參數,或者接受它的值得局部變量中表達式需要類型稱為目標類型。但局部變量必須顯示的聲明,或實際上就算。換句話說,表達式只能捕獲指派給它們的局部變量一次。注捕獲實例變量可以被看作捕獲最終局部變量。 由于第三章的內容比較多,而且為了讓大家更好的了解Lambda表達式的使用,也寫了一些相關的實例,可以在Github或者碼云上拉取讀書筆記的代碼進行參考。 類型檢查、...
摘要:內部迭代與使用迭代器顯式迭代的集合不同,流的迭代操作是在背后進行的。流只能遍歷一次請注意,和迭代器類似,流只能遍歷一次。 流(Stream) 流是什么 流是Java API的新成員,它允許你以聲明性方式處理數據集合(通過查詢語句來表達,而不是臨時編寫一個實現)。就現在來說,你可以把它們看成遍歷數據集的高級迭代器。此外,流還可以透明地并行處理,你無需寫任何多線程代碼了!我會在后面的筆記中...
閱讀 1961·2021-11-23 09:51
閱讀 873·2021-11-19 09:40
閱讀 829·2021-10-27 14:20
閱讀 5004·2021-10-09 09:52
閱讀 3297·2021-10-09 09:44
閱讀 1729·2021-10-08 10:05
閱讀 5053·2021-09-09 11:47
閱讀 3481·2019-08-30 12:47