在實習(xí)的這一段時間里,我深刻體會到一個道理,就是用戶的需求不斷的在變化,因而自己的代碼也要進(jìn)行重構(gòu),這說明了一個問題,我設(shè)計的代碼不夠好。所以,我覺得作為程序員來說,很重要的一點(diǎn)就是,讓你的代碼能夠適應(yīng)變化。
行為參數(shù)化
行為參數(shù)化,就是可以幫助我們處理頻繁變化需求的一種軟件開發(fā)模式,通俗的說,就是拿出一個代碼塊,把它準(zhǔn)備好,卻不去執(zhí)行它。這個代碼塊以后可以被程序的其他部分調(diào)用,這就意味著我們可以推遲這塊代碼的執(zhí)行。
背景
農(nóng)場里有很多蘋果,我們要去篩選特定的蘋果,并記錄下來。接下來,我們通過不斷變化的需求來重構(gòu)我們的代碼。以下的場景都是我虛擬出來的,便于故事的發(fā)展,哈哈。
片段一
開始的別人給我們提出的需求是,篩選出來所有顏色是綠色的蘋果。我心里想,你是不是鄙視我,這么簡單,遍歷一下把綠色蘋果放到新的集合不就行了嗎?我可是程序員哎,不是來打雜的,我可是要寫牛逼的代碼的。算了,還是寫吧。代碼如下:
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ("green".equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
我把代碼提交上去了,然后屁顛屁顛的做其他事情了,好像完成了一個很大的項目。。。
片段二
第二天,那個人又找到我,說道,你能不能實現(xiàn)一個篩選紅色蘋果的接口。
我心里自言自語,臥槽,這需求變化那么快,昨天蘋果還是綠的,今天就變紅了,尼瑪,這萬惡的商家,催熟劑就是厲害。以后吃蘋果還是吃青的吧。開始我準(zhǔn)備把上面的代碼copy下來,然后"green"改成"red",但是仔細(xì)一想,萬一明天,讓我寫一個黑蘋果的接口,那我不又要寫一個。于是把color抽象出來,變成一個參數(shù)傳進(jìn)來,這樣你要什么顏色的我都有了。突然,發(fā)現(xiàn)自己好厲害,抽象我都會。。。自我陶醉中。代碼如下:
public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (color.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
寫完之后,我心里在那嘀咕,萬惡的資本主義,我把顏色都抽象出來了,看你需求怎么變。哥,可是--程序員。
片段三
晚上回去睡個安穩(wěn)覺后,第二天,那人又來了,對,還是那個男人。我問他,是不是我寫的接口不行。那人回到到,不是,是這邊又有新的需求。突然之間,整個人都傻了。還有需求,算了,誰讓我是程序員呢。之后,他和我說道,新的需求是區(qū)分,大蘋果和小蘋果,大蘋果是重量大于500g的。二話沒說,扭頭就去擼代碼。代碼如下:
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
聰明的我,早已想到需求會變,這次直接把這個接口抽象好,內(nèi)心不禁有暗暗自喜。想到世界還有自己這種程序員,就說明世界還有希望。
片段四
第二天,他又來了,我知道,又要干活了。新的需求是,過濾那些紅色的且重量大于500g的蘋果。代碼如下:
public static List<Apple> filterApples(List<Apple> inventory, String color, int weight) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (color.equals(apple.getColor()) && apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
不一會我就設(shè)計好了,但是卻沒有之前的開心了,不知道明天又會有什么新的需求,寶寶心里好苦啊。
片段五
之后令我很驚訝的是,沒有新的需求提出了,我一遍暗地自喜,一遍在思考一個問題。為什么,需求變,我的代碼就要變化那么大呢?需求都是和選擇蘋果相關(guān),選擇什么樣的蘋果是通過參數(shù)來傳遞的,那么有沒有一種方法,通過一個借口來實現(xiàn)傳遞不同的參數(shù)呢?
如果熟悉設(shè)計模式的你,應(yīng)該能想到策略模式。接口一致,按需傳遞該接口對應(yīng)的實例。也是面向接口編程的一種體現(xiàn)。先設(shè)計一個接口,用來承載選擇蘋果的邏輯,代碼如下:
public interface ApplePredicate {
boolean test(Apple apple);
}
現(xiàn)在我們可以用不同的實現(xiàn)來傳遞不同的邏輯了,如下:
//綠色蘋果
public class GreenApplePredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getColor().equals("green");
}
}
//大蘋果
public class BigApplePredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 500;
}
}
//代碼主體
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;
}
可以看出,代碼主體以后我們就不會改變了,只要通過傳入不同的行為(過濾邏輯),就行了,而且這里只傳遞一個參數(shù),卻可以傳遞多種行為,多種行為,一個參數(shù),是不是很nice。我又開始沾沾自喜。。感覺自己又變得厲害了,晚上回去睡得很任性。
片段六
第二天,還是沒有新的需求,我們都知道,程序員沒事做,這寶貴的資源就白白的浪費(fèi),別人不心疼,我都心疼。于是,自己又去搗鼓昨天的那塊代碼去了。
剛開始用起來還是蠻舒服的,但是用久了發(fā)現(xiàn),每次一個新的邏輯都要去實現(xiàn)這個接口,太難受了,突然,想到了java還有匿名類,于是,之后直接用匿名類來實現(xiàn)這個邏輯。代碼如下:
List<Apple> greenApples = filterApples(inventory, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return apple.getColor().equals("green");
}
});
這樣看起來,的確比之前簡潔多了,不要顯式的去實現(xiàn)一個接口。但是細(xì)細(xì)的觀察了一下,這里面真正有用的代碼就這一行:
return apple.getColor().equals("green");
那么有沒有什么辦法,只給filterApples方法只傳遞這樣真正邏輯性的代碼呢?幸好,java8提供了行為參數(shù)化的支持。好吧,既然都支持了,還等什么,干吧,下面這一行代碼就解決了。
List<Apple> greenApples = filterApples(inventory, (Apple apple) -> apple.getColor().equals("green"));
什么,我沒有看錯吧,這么神?你并沒有看錯,傳遞的參數(shù)叫做lambda表達(dá)式,如果還嫌不夠簡潔,你可以把類型去掉,像這樣:
List<Apple> greenApples = filterApples(inventory, apple -> apple.getColor().equals("green"));
經(jīng)歷了以上幾個階段,我們重構(gòu)了很多次代碼,讓接口能夠適應(yīng)需求的變化,一方面,我們又不想編寫冗余的代碼,通過lambda表達(dá)式來傳遞核心代碼,程序員不在做一些不必要的工作,真的是太好了。