馬鞍山制作網站網絡營銷方式有哪幾種
項目源碼:https://github.com/java8/
1 應對不斷變化的需求
在我們進行開發(fā)中,經常需要面臨需求的不斷變更,我們可以將行為參數化以適應不斷變更的需求。
行為參數化就是可以幫助我們處理頻繁變更的需求的一種軟件開發(fā)模式
我們可以將代碼塊作為參數傳遞給方法。
例如,現有一個倉庫,我們想定義從倉庫中查詢綠蘋果的功能。后來我們又想查詢重蘋果(>150g)…
面對這種不斷變更的需求,我們就可以使用行為參數化來維護我們的代碼。
1.1 初試牛刀:篩選出綠蘋果
Apple類:
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;@Getter
@Setter
@AllArgsConstructor
@ToString
public class Apple {private int weight = 0;private String color = "";
}
// 初試牛刀:篩選綠蘋果
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;
}
? 如果說我們現在改變主意,想要查詢紅色的蘋果,最簡單的辦法就是copy上面這個方法,然后將方法改名為 fliterRedApples,改變if 判斷條件。談若我們需要查詢各種演示的蘋果:淺綠色、暗紅色、黃色等,那么再按照前面的這種方法來做代碼將變得非常的冗余。
? 一個好的原則是嘗試將我們上的的這個方法進行抽象化,以適應不同顏色的蘋果的查詢。
下面我們進行嘗試:
1.2 再展身手:把顏色作為參數
我們立馬能想到的方法是將上面的 filterGreenApples 加上一個顏色參數,就可以了:
// 再展身手:把顏色作為參數
public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {if (apple.getColor().equals(color)) {result.add(apple);}}return result;
}
這樣我們就可以查詢各種各樣的蘋果了,如下:
List<Apple> greenApple = filterApplesByColor(inventory,"green");
List<Apple> redApple = filterApplesByColor(inventory,"red");
...
假設我們現在又要查詢重蘋果(>150g)或輕蘋果,那么我們只需要根據
filterApplesByColor 進行稍稍修改即可,如下:
// 再展身手:根據蘋果的重量進行查詢
public static List<Apple> filterApplesWeight(List<Apple> inventory, int weight) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {if (apple.getWeight() > weight) {result.add(apple);}}return result;
}
現在我們完成了根據顏色查詢蘋果、根據重量查詢蘋果的功能,但是我們這兩個方法極為相似,復制了大部分的代碼來實現遍歷庫存,并對每個蘋果應用篩選條件稍稍做了修改。
如果后續(xù)我們想改變查詢的遍歷方式來提升性能,那么需要修改所有的方法,這樣顯然是不合適的。
我們可以考慮將顏色和重量結合為一個方法,稱為filterApples。不過這樣需要加上一個標記來區(qū)分是對什么屬性(顏色或重量)的查詢(但是我們不推薦這種方式)
1.3 第三次嘗試:對你能想到的每個屬性做篩選
下面是一個比較笨拙的方法:
// 生產環(huán)境別這么用
public static List<Apple> filterApples(List<Apple> inventory, String color,int weight, boolean flag) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {// 閱讀性不好,也很笨拙if (flag && apple.getColor().equals(color) || !flag && apple.getWeight() > weight) {result.add(apple);}}return result;
}
我們可以這樣調用這個方法,以使用不同屬性的查詢:
// 根據顏色查詢:查詢綠蘋果
List<Apple> greenApples = filterApples(inventory, "green",0,true);
// 根據重量查詢:查詢>150g的重蘋果
List<Apple> heavyApples = filterApples(inventory,"",150,false);
這個方法能解決根據不同屬性查詢蘋果的功能,但是這樣寫代碼很爛,并且我們也不知道 boolean flag 參數傳入 true、flase是什么意思,可讀性很差。
并且這樣也不能很好的使用根據不同屬性進行查詢,如果我們需要根據多個屬性進行查詢(比如:查詢綠色的重蘋果),那更是天方夜譚了。
下面我們來解決這個問題:
2 行為參數化
我們需要我們適應各種各樣的屬性來查詢。
我們可以考慮根據Apple的屬性來返回一個boolean值。我們把它稱為謂詞(即一個返回boolean值的函數)。下面我們定義一個接口來實現:
// 判斷型接口
@FunctionalInterface
public interface ApplePredicate {boolean test(Apple apple);
}
現在我們就可以用 ApplePredicate 的多個實現代表不同的查詢標準了,例如:
// 查詢出重蘋果
public class AppleHeavyWeightPredicate implements ApplePredicate {@Overridepublic boolean test(Apple apple) {return apple.getWeight() > 150;}
}// 查詢出綠蘋果
public class AppleGreenColorPredicate implements ApplePredicate {@Overridepublic boolean test(Apple apple) {return "green".equals(apple.getColor());}
}
我們可以把這些標準看作filter方法的不同行為。剛做的這些和“策略設計模式”相關,它讓你定義一族算法,把它們封裝起來(稱為“策略”),然后在運行時選擇一個算法。在這里,算法族就是ApplePredicate,不同的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。
但是,該怎么利用ApplePredicate的不同實現呢?
我們需要 filterApples方法接受 ApplePredicate對象,對Apple做條件測試。這就是行為參數化:讓方法接受多種行為(或戰(zhàn)略)作為參數,并在內部使用,來完成不同的行為。
個人對于行為參數化的理解:
? 所謂行為參數化,就是將行為(一般封裝成方法的形式)以參數的形式傳遞到其他方法中執(zhí)行。
如何實現行為參數化:
我們要給filterApples方法添加一個參數,讓它接受 ApplePredicate對象。
行為參數化的好處:
我們把filterApples方法迭代集合的邏輯與我們應用到集合中每個元素的行為(這里是一個謂詞,即我們根據Apple的屬性查詢的行為)區(qū)分開了。
2.1 第四次嘗試:根據抽象條件篩選
利用 ApplePredicate 修改后的 filter方法如下:
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate predicate) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {if (predicate.test(apple)){ // 謂詞對象封裝了測試蘋果的條件result.add(apple);}}return result;
}
2.2 傳遞代碼/行為
現在,這段代碼以及比我們前面寫的方法靈活多了,代碼的可讀性也高了。比如,我們要查詢紅的重蘋果,只需要創(chuàng)建一個類實現現ApplePredicate接口即可(甚至可以使用Lambda表達式):
public class AppleRedAndHeavyPredicate implements ApplePredicate{@Overridepublic boolean test(Apple apple) {return "red".equals(apple.getColor()) && apple.getWeight() > 150;}
}// 調用
List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
現在,我們的filterApples方法完成了行為參數化。
但是我們現在發(fā)現這樣還是很麻煩,我們要完成一個查詢功能,為此我們定義了一個類,里面有一個方法,完成對查詢結果的判斷,這樣有很多的無用代碼,代碼可讀性還是不高。
我們可以使用lmabda表達式簡化不必要的代碼,直接把表達式"red".equals(apple.getColor())
&&apple.getWeight() > 150傳遞給filterApples方法,無需定義一個類
// 使用Lambda表達式實現傳遞代碼
List<Apple> redAndHeavyApples = filterApples(inventory, (apple) ->"red".equals(apple.getColor()) && apple.getWeight() > 150);
2.3 多種行為,一個參數
行為參數化的好處在于你可以把迭代要篩選的集合的邏輯與對集合中每個元素應用的行為區(qū)分開來。這樣你可以重復使用同一個方法,給它不同的行為來達到不同的目的。
演示:編寫靈活的prettyPrintApple方法
編寫一個prettyPrintApple方法,它接受一個Apple的List,并可以對它參數化,以多種方式根據蘋果生成一個String輸出(有點兒像多個可定制的toString方法)。
例如,你可以告訴 prettyPrintApple 方法,只打印每個蘋果的重量。此外,你可以讓 prettyPrintApple方法分別打印每個蘋果,然后說明它是重的還是輕的。
@FunctionalInterface
public interface AppleFormatter{ String accept(Apple a);
}public class AppleFancyFormatter implements AppleFormatter { // 后續(xù)我們可以通過Lambda簡化,以省略這樣的類public String accept(Apple apple) {String characteristic = apple.getWeight() > 150 ? "heavy" :"light";return "A " + characteristic +" " + apple.getColor() + " apple";}
}public class AppleSimpleFormatter implements AppleFormatter {public String accept(Apple apple) {return "An apple of " + apple.getWeight() + "g";}
}public static void prettyPrintApple(List<Apple> inventory, AppleFormatter formatter) {for (Apple apple : inventory) {String output = formatter.accept(apple);System.out.println(output);}
}
使用
// 你首先要實例化AppleFormatter的實現,然后把它們作為參數傳給prettyPrintApple方法
prettyPrintApple(inventory, new AppleFancyFormatter());
現在,我們已經將行為抽象出來了,這樣使我們的代碼適應不同的需求,但是這樣很繁瑣
因為我們需要聲明很多個只使用一次的類,下面通過匿名內部類、Lambda表達式等方式進行簡化
3 簡化代碼
在前面,當要把新的行為傳遞給 filterApples方法的時候,我們不得不聲明好幾個實現ApplePredicate接口的類,然后實例化好幾個只會提到一次的ApplePredicate對象。這真是很啰嗦,很費時間!
3.1 匿名類
匿名類和我們熟悉的 Java局部類(塊中定義的類)差不多,但匿名類沒有名字。它允許我們同時聲明并實例化一個類。換句話說,它允許你隨用隨建
3.2 第五次嘗試:使用匿名類
下面我們將通過創(chuàng)建匿名類的方式實現ApplePredicate的對象,重寫篩選的例子(以查詢綠色蘋果為例):
List<Apple> greenApples = filterApples(inventory, new ApplePredicate() { // 直接內聯(lián)參數化filterapples方法的行為@Overridepublic boolean test(Apple apple) {return "red".equals(apple.getColor());}
});
但是匿名類的方式還是不夠好:
- 它往往很笨重,因為它占用了很多空間
- 代碼閱讀性較差
總的來說,使用匿名類的方式,不僅代碼編寫、維護比較費時間,可讀性也不太好。
接下來,我們使用Java 8中引人的Lambda表達式——一種更簡潔的傳遞代碼的方式。
3.3 第六次嘗試:使用 Lambda 表達式
使用Lambda表達式重寫上面的代碼:
List<Apple> greenApples = filterApples(inventory, (apple) ->"red".equals(apple.getColor()));
到目前為止,區(qū)別于以往的值參數傳遞,我們已經實現了將類、匿名類、Lambda表達式等行為參數化傳遞到了方法中
3.4 第七次嘗試:將 List 類型抽象化
在通往抽象的路上,我們還可以更進一步。目前,filterApples方法還只適用于Apple。
我們還可以將List類型抽象化,從而超越你眼前要處理的問題:
@FunctionalInterface
public interface Predicate<T>{ boolean test(T t);
}public static <T> List<T> filter(List<T> list, Predicate<T> p){ // 引入類型參數T,即泛型List<T> result = new ArrayList<>(); for(T e: list){ if(p.test(e)){ result.add(e); } } return result;
}
現在,我們的 filter 方法能更好的適應不同的查詢了,可以用在香蕉、桔子、Integer或是String的列表等等上了。
例如:
List<Apple> redApples = filter(inventory, (Apple apple) -> "red".equals(apple.getColor())); // 從numbers中篩選出偶數
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
4 真實的例子
到現在,我們已經清除的知道了行為參數化是一個很有用的模式,它能夠輕松地適應不斷變化的需求。這種模式可以把一個行為(一段代碼)封裝起來,并通過傳遞和使用創(chuàng)建的行為(例如對Apple的不同謂詞)將方法的行為參數化。前面提到過,這種做法類似于策略設計模式。你可能已經在實踐中用過這個模式了。Java API中的很多方法都可以用不同的行為來參數化。這些方法往往與匿名類一起使用。
我們會展示兩個例子,這應該能幫助你鞏固傳遞代碼的思想了:用一個Comparator排序,用Runnable執(zhí)行一個代碼塊
4.1 用 Comparator 來排序
例如,根據蘋果的重量對庫存進行排序,或者希望你根據顏色對蘋果進行排序。聽起來有點兒耳熟?是的,
你需要一種方法來表示和使用不同的排序行為,來輕松地適應變化的需求。
在Java 8中,List自帶了一個sort方法(你也可以使用Collections.sort)。sort的行為可以用java.util.Comparator
對象來參數化,如下:
package java.util;@FunctionalInterface
public interface Comparator<T> {int compare(T o1, T o2);...
}
因此,我們可以隨時創(chuàng)建Comparator的實現,用sort方法表現出不同的行為。
比如,你可以 使用匿名類,按照蘋果的重量升序對庫存排序:
inventory.sort(new Comparator<Apple>() {@Overridepublic int compare(Apple o1, Apple o2) {return o1.getWeight() - o2.getWeight();}
});
后續(xù),我們可以隨時創(chuàng)建一個Comparator來滿足新要求,并把它傳遞給 sort方法。而如何進行排序這一內部細節(jié)都被抽象掉了。用Lambda表達式的話,看起來就是這樣:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight() - a2.getWeight());
4.2 用 Runnable 執(zhí)行代碼塊
在 Java里,你可以使用Runnable接口表示一個要執(zhí)行的代碼塊
package java.lang;@FunctionalInterface
public interface Runnable {public abstract void run();
}
我們可以像下面這樣,使用這個接口創(chuàng)建執(zhí)行不同行為的線程:
Thread t = new Thread(new Runnable() { @Overridepublic void run(){ System.out.println("Hello world"); }
});
使用Lambda表達式簡化:
Thread t = new Thread(() -> System.out.println("Hello world"));
5 小結
-
行為參數化,就是一個方法接受多個不同的行為作為參數,并在內部使用它們,完成不同行為的能力。
-
行為參數化可讓代碼更好地適應不斷變化的要求,減輕未來的工作量。
-
傳遞代碼,就是將新行為作為參數傳遞給方法。但在Java 8之前這實現起來很啰嗦。為接口聲明許多只用一次的實體類而造成的啰嗦代碼,在Java 8之前可以用匿名類來減少。
-
Java API包含很多可以用不同行為進行參數化的方法,包括排序、線程和GUI處理。