得益於函數接口,我們可以改造設計模式(不限於此):
- 策略模式
- 模板模式
- 觀察者模式
- 責任鏈模式
- 工廠模式
策略模式
優點: 1、算法可以自由切換。 2、避免使用多重條件判斷。 3、擴展性良好。
缺點: 1、策略類會增多。 2、所有策略類都需要對外暴露。
使用場景: 1、如果在一個系統里面有許多類,它們之間的區別僅在於它們的行為,那么使用策略模式可以動態地讓一個對象在許多行為中選擇一種行為。 2、一個系統需要動態地在幾種算法中選擇一種。 3、如果一個對象有很多的行為,如果不用恰當的模式,這些行為就只好使用多重的條件選擇語句來實現。
例:
public interface Strategy { public int doOperation(int num1, int num2); } public class OperationAdd implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 + num2; } } public class OperationSubstract implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 - num2; } } public class Context { private Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public int executeStrategy(int num1, int num2){ return strategy.doOperation(num1, num2); } } public class StrategyPatternDemo { public static void main(String[] args) { Context context = new Context(new OperationAdd()); System.out.println("10 + 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationSubstract()); System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); } }
在“了不起”系列的第一課就學過謂詞Predicate就可以用Lambda形式來簡寫,因為Predicate就是方法接口。
其實不僅僅是Predicate這種返回Boolean的方法可以使用Lambda,所有的參數和返回形式都可以使用Lambda。完整的例子:
public class StrategyMain { public static void main(String[] args) { // 老派策略1,參數為String,返回Boolean Validator v1 = new Validator(new IsNumeric()); System.out.println(v1.validate("aaaa")); Validator v2 = new Validator(new IsAllLowerCase()); System.out.println(v2.validate("bbbb")); // 老派策略2,參數為兩個int,返回int Context context = new Context(new OperationAdd()); System.out.println("10 + 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationSubstract()); System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); // lambda改造的形式1,參數為String,返回Boolean Validator v3 = new Validator((String s) -> s.matches("\\d+")); System.out.println(v3.validate("aaaa")); Validator v4 = new Validator((String s) -> s.matches("[a-z]+")); System.out.println(v4.validate("bbbb")); // lambda改造的形式2,參數為兩個int,返回int Context operationContext1 = new Context((int a, int b) -> a + b); System.out.println(operationContext1.executeStrategy(10, 5)); Context operationContext2 = new Context((int a, int b) -> a - b); System.out.println(operationContext2.executeStrategy(10, 5)); } interface ValidationStrategy { public boolean execute(String s); } static private class IsAllLowerCase implements ValidationStrategy { public boolean execute(String s) { return s.matches("[a-z]+"); } } static private class IsNumeric implements ValidationStrategy { public boolean execute(String s) { return s.matches("\\d+"); } } static private class Validator { private final ValidationStrategy strategy; public Validator(ValidationStrategy v) { this.strategy = v; } public boolean validate(String s) { return strategy.execute(s); } } interface Strategy { public int doOperation(int num1, int num2); } static private class OperationAdd implements Strategy { public int doOperation(int num1, int num2) { return num1 + num2; } } static private class OperationSubstract implements Strategy { public int doOperation(int num1, int num2) { return num1 - num2; } } static private class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public int executeStrategy(int num1, int num2) { return strategy.doOperation(num1, num2); } } }
結果:
false true 10 + 5 = 15 10 - 5 = 5 false true 15 5
模板模式
優點: 1、封裝不變部分,擴展可變部分。 2、提取公共代碼,便於維護。 3、行為由父類控制,子類實現。
缺點:每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。
使用場景: 1、有多個子類共有的方法,且邏輯相同。 2、重要的、復雜的方法,可以考慮作為模板方法。
模板模式的關鍵在於子類靈活處理業務代碼,比如StrartPlay等方法,還比如一個銀行,需要獲取客戶的資料,根據情況,然后靈活的使用客戶滿意的操作,每個分行的操作都不同,如果是傳統方式,就是每個分行建立模板子類。用Lambda就省去了一堆的分行子類。
import java.util.function.Consumer; public class OnlineBankingLambda { public static void main(String[] args) { new OnlineBankingLambda().processCustomer(1337, (Customer c) -> System.out.println("Hello!")); } public void processCustomer(int id, Consumer<Customer> makeCustomerHappy){ Customer c = Database.getCustomerWithId(id); makeCustomerHappy.accept(c); } // dummy Customer class static private class Customer {} // dummy Database class static private class Database{ static Customer getCustomerWithId(int id){ return new Customer();} } }
注意,(Customer c) -> System.out.println("Hello!") 就是Consumer<Customer>的一個實例,如果看過“了不起”系列的第一課,我們知道Consumer也是方法接口,他和謂詞的區別是謂詞返回Boolean,消費者無返回值:void accept(T t);
如此一來,System.out.println("Hello!")就可以使用了(沒有返回),不僅僅是Consumer,任何方法接口都可以用在Lambda,所以用一行代碼就省了一個子類。
觀察者模式
優點: 1、觀察者和被觀察者是抽象耦合的。 2、建立一套觸發機制。
缺點: 1、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。 2、如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。 3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅只是知道觀察目標發生了變化。
使用場景:
- 一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的對象中使它們可以各自獨立地改變和復用。
- 一個對象的改變將導致其他一個或多個對象也發生改變,而不知道具體有多少對象將發生改變,可以降低對象之間的耦合度。
- 一個對象必須通知其他對象,而並不知道這些對象是誰。
- 需要在系統中創建一個觸發鏈,A對象的行為將影響B對象,B對象的行為將影響C對象……,可以使用觀察者模式創建一種鏈式觸發機制。
觀察者模式相當常用,比如發布-訂閱就是觀察者模式。
需要觀察的資源類,最重要的2個方法是,注冊觀察者和提醒觀察者。
interface Observer{ void inform(String tweet); } interface Subject{ void registerObserver(Observer o); void notifyObservers(String tweet); } static private class NYTimes implements Observer{ @Override public void inform(String tweet) { if(tweet != null && tweet.contains("money")){ System.out.println("Breaking news in NY!" + tweet); } } } static private class Guardian implements Observer{ @Override public void inform(String tweet) { if(tweet != null && tweet.contains("queen")){ System.out.println("Yet another news in London... " + tweet); } } } static private class LeMonde implements Observer{ @Override public void inform(String tweet) { if(tweet != null && tweet.contains("wine")){ System.out.println("Today cheese, wine and news! " + tweet); } } } static private class Feed implements Subject{ private final List<Observer> observers = new ArrayList<>(); public void registerObserver(Observer o) { this.observers.add(o); } public void notifyObservers(String tweet) { observers.forEach(o -> o.inform(tweet)); } }
使用
Feed f = new Feed(); f.registerObserver(new NYTimes()); f.registerObserver(new Guardian()); f.registerObserver(new LeMonde()); f.notifyObservers("The queen said her favourite book is Java 8 in Action!");
用Lambda替換
Feed feedLambda = new Feed(); feedLambda.registerObserver((String tweet) -> { if(tweet != null && tweet.contains("money")){ System.out.println("Breaking news in NY! " + tweet); } }); feedLambda.registerObserver((String tweet) -> { if(tweet != null && tweet.contains("queen")){ System.out.println("Yet another news in London... " + tweet); } }); feedLambda.notifyObservers("Money money money, give me money!");
需要靈活對待Lambda,並不是什么情況都要用其替代,因為如果觀察者的邏輯比較復雜,用{}括起來比較大,或者還依賴狀態,或者定義了很多依賴方法,這種情況下,還是傳統的觀察者模式更加清晰易維護。
責任鏈模式
優點: 1、降低耦合度。它將請求的發送者和接收者解耦。 2、簡化了對象。使得對象不需要知道鏈的結構。 3、增強給對象指派職責的靈活性。通過改變鏈內的成員或者調動它們的次序,允許動態地新增或者刪除責任。 4、增加新的請求處理類很方便。
缺點: 1、不能保證請求一定被接收。 2、系統性能將受到一定影響,而且在進行代碼調試時不太方便,可能會造成循環調用。 3、可能不容易觀察運行時的特征,有礙於除錯。
使用場景: 1、有多個對象可以處理同一個請求,具體哪個對象處理該請求由運行時刻自動確定。 2、在不明確指定接收者的情況下,向多個對象中的一個提交一個請求。 3、可動態指定一組對象處理請求。
責任鏈的核心是一個對象處理完工作后,還要傳遞給下一個(Next)對象接着處理,並且依次類推,傳遞下去。
責任鏈也是需要定義子類,而Lambda使用Function + andThen 把順序串聯起來,完成同樣的功能,例子:
import java.util.function.Function; import java.util.function.UnaryOperator; public class ChainOfResponsibilityMain { public static void main(String[] args) { ProcessingObject<String> p1 = new HeaderTextProcessing(); ProcessingObject<String> p2 = new SpellCheckerProcessing(); p1.setNextProcessor(p2); String result1 = p1.handle("Aren't labdas really sexy?!!"); System.out.println(result1); UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text; UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda"); Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing); String result2 = pipeline.apply("Aren't labdas really sexy?!!"); System.out.println(result2); } static private abstract class ProcessingObject<T> { protected ProcessingObject<T> nextProcessor; public void setNextProcessor(ProcessingObject<T> nextProcessor) { this.nextProcessor = nextProcessor; } public T handle(T input) { T r = handleWork(input); if (nextProcessor != null) { return nextProcessor.handle(r); } return r; } abstract protected T handleWork(T input); } static private class HeaderTextProcessing extends ProcessingObject<String> { public String handleWork(String text) { return "From Raoul, Mario and Alan: " + text; } } static private class SpellCheckerProcessing extends ProcessingObject<String> { public String handleWork(String text) { return text.replaceAll("labda", "lambda"); } } }
工廠模式
優點: 1、一個調用者想創建一個對象,只要知道其名稱就可以了。 2、擴展性高,如果想增加一個產品,只要擴展一個工廠類就可以。 3、屏蔽產品的具體實現,調用者只關心產品的接口。
缺點:每次增加一個產品時,都需要增加一個具體類和對象實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的復雜度,同時也增加了系統具體類的依賴。這並不是什么好事。
使用場景: 1、日志記錄器:記錄可能記錄到本地硬盤、系統事件、遠程服務器等,用戶可以選擇記錄日志到什么地方。 2、數據庫訪問,當用戶不知道最后系統采用哪一類數據庫,以及數據庫可能有變化時。 3、設計一個連接服務器的框架,需要三個協議,"POP3"、"IMAP"、"HTTP",可以把這三個作為產品類,共同實現一個接口。
工廠模式也是非常常用的模式,比如我們在銀行工作,我們需要使用一個接口創建不同的金融產品:貸款、期權、基金、股票等。
static private class ProductFactory { public static Product createProduct(String name){ switch(name){ case "loan": return new Loan(); case "stock": return new Stock(); case "bond": return new Bond(); default: throw new RuntimeException("No such product " + name); } }
創建方法:
Product p1 = ProductFactory.createProduct("loan");
還是使用方法接口Supplier,來實現Lambda的方式:
@FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; public class FactoryMain { public static void main(String[] args) { Product p1 = ProductFactory.createProduct("loan"); Supplier<Product> loanSupplier = Loan::new; Product p2 = loanSupplier.get(); Product p3 = ProductFactory.createProductLambda("loan"); } static private class ProductFactory { public static Product createProduct(String name){ switch(name){ case "loan": return new Loan(); case "stock": return new Stock(); case "bond": return new Bond(); default: throw new RuntimeException("No such product " + name); } } public static Product createProductLambda(String name){ Supplier<Product> p = map.get(name); if(p != null) return p.get(); throw new RuntimeException("No such product " + name); } } static private interface Product {} static private class Loan implements Product { public Loan() { super(); System.out.println("Loan...Created " ); }} static private class Stock implements Product { public Stock() { super(); System.out.println("Stock...Created " ); }} static private class Bond implements Product { public Bond() { super(); System.out.println("Bond...Created " ); }} final static private Map<String, Supplier<Product>> map = new HashMap<>(); static { map.put("loan", Loan::new); map.put("stock", Stock::new); map.put("bond", Bond::new); } }
這種工廠方法有點局限性,如果有多個參數時,就不好用了,這時不能使用Supplier,需要使用一個特殊的函數接口TriFunction,來進行柯里化:
@PublicEvolving @FunctionalInterface public interface TriFunction<S, T, U, R> { /** * Applies this function to the given arguments. * * @param s the first function argument * @param t the second function argument * @param u the third function argument * @return the function result */ R apply(S s, T t, U u); }
比如:
TriFunction<Integer,Integer,Integer, Integer> triFunction = (x,y,z) -> (x+y)*z; System.out.println(triFunction.apply(4,5,6)); //54
隨着函數在Java中變成一等公民,自然而然會產生柯里化。柯里化的鏈式調用的確用起來很爽。柯里化也可以延遲加載一個函數。
除此以外,柯里化在很多時候簡化了函數式編程的復雜性,使編程更加優雅。當然,在團隊中使用的話,也需要充分考慮到團隊中其他成員是否接受。
另外,還可以用BiFunction,好處是可以使用andThen,進行串聯操作,這就是有些責任鏈+工廠的感覺了。
@FunctionalInterface public interface BiFunction<T, U, R> { R apply(T t, U u); default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t, U u) -> after.apply(apply(t, u)); } }
例如:
public static int compute4(int a, int b, BiFunction<Integer, Integer, Integer> biFunction,Function<Integer, Integer> function) { return biFunction.andThen(function).apply(a, b); } //25 System.out.println(FactoryMain.compute4(2, 3, (v1, v2) -> v1 + v2, v1 -> v1 * v1));