前言
雖說使用設計模式可以讓復雜的業務代碼變得清晰且易于維護,但是某些情況下,開發可能會遇到我為了簡單的業務邏輯去適配設計模式的情況,本文筆者就以四種常見的設計模式為例,演示如何基于lambda
來簡化設計模式的實現。
策略模式
我們的項目中會涉及各種各樣的校驗,可能是校驗電話號碼、單雙數、字符串長度等,為此我們希望通過策略模式來封裝這些校驗規則。
第一步自然是通過接口來定義策略,編寫一個名為execute方法,讓用戶傳入字符串,返回校驗結果的布爾值:
/*** 定義策略模式的接口*/
public interface ValidationStrategy {/*** 校驗該字符串是否符合要求,若符合則返回true* @param str* @return*/boolean execute(String str);
}
然后將這個策略接口聚合到我們的校驗器中,后續我們就可以按照需求傳入對應的校驗策略即可:
/*** 校驗工具,將策略接口成員成員屬性,起到依賴抽象的作用*/
public class Validator {private ValidationStrategy strategy;public Validator() {}public Validator(ValidationStrategy strategy) {this.strategy = strategy;}public boolean validate(String str) {return strategy.execute(str);}
}
假如我們需要校驗這個字符串是否全為字符串小寫,那么我們就可以封裝這樣一個類:
/*** 判斷是否全為小寫*/
public class IsAllLowerCase implements ValidationStrategy {@Overridepublic boolean execute(String str) {return str.matches("[a-z]+");}
}
同理,如果我們需要判斷是否全為數字,則可以這樣寫:
/*** 判斷傳入字符是否全為數字*/
public class IsNumeric implements ValidationStrategy {@Overridepublic boolean execute(String str) {return str.matches("\\d+");}
}
使用時,我們只需按需傳入校驗規則即可:
public class Main {public static void main(String[] args) {//校驗是否全為數字Validator v1 = new Validator(new IsNumeric());System.out.println(v1.validate("1234"));//校驗是否全是小寫Validator v2 = new Validator(new IsAllLowerCase());System.out.println(v2.validate("dalhl"));}
}
輸出結果如下:
true
true
不知道讀者是否可以發現問題,規則的校驗往往只是一兩行代碼,為了適配規則校驗所用到的策略模式,開發者往往需要對此額外創建一個類,要知道字符校驗的規則是成百上千的,并且很多校驗規則很可能僅僅是某個業務才會用到的。
所以我們是否有辦法做到既能適配策略模式,又避免為了一段簡單的校驗代碼而去創建一個類呢?
查看我們校驗策略接口ValidationStrategy
的定義,它要求傳入一個String
返回一個boolean
,由此我們想到了java8
提供的函數時接口Function
,其定義如下所示,可以根據泛型要求要指明泛型T
和R
,apply
方法要求傳入一個T
,這里可以直接理解為我們的String
,然后返回一個R
,同理代入我們的boolean
:
@FunctionalInterface
public interface Function<T, R> {/*** Applies this function to the given argument.** @param t the function argument* @return the function result*/R apply(T t);.......}
按照java8
的lambda
語法糖,Function<T, R>
只有一個需要實現的方法R apply(T t)
,我們完全可以表面類的創建,取而代之的是這樣一段表達式:
t->R
查看我們ValidationStrategy
的定義,它也是只有一個方法execute
,我們完全可以將其視為Function<String, Boolean>
,即可得表達式s->boolean
:
由此我們得出下面這段代碼,可以看到根據接口的定義匹配java8
對應的函數時接口,然后基于lambda
表達式即可完成創建,這樣做法避免了類的生命,避免了簡單邏輯復雜化實現的問題:
public static void main(String[] args) {//校驗是否全為數字Validator v1 = new Validator((s) -> s.matches("\\d+"));System.out.println(v1.validate("1234"));//校驗是否全是小寫Validator v2 = new Validator(s -> s.matches("[a-z]+"));System.out.println(v2.validate("dalhl"));}
模板方法
銀行接待VIP
顧客的核心流程為:
- 查詢顧客是否是
VIP
。 - 招待顧客,為顧客辦理業務。
所有銀行的大體流程都是這樣,唯一的區別就是第2步,對此我們可以使用模板方法模式,創建一個抽象類,將第1步抽出來,而第2步按照不同銀行進行不同的實現:
public abstract class Banking {public void processCustomer(int id) {//查詢會員名String customer = getCustomerWithId(id);//招待會員makeCustomerHappy(customer);}private String getCustomerWithId(int id) {return RandomUtil.randomString(5);}protected abstract void makeCustomerHappy(String customer);
}
對應兩個銀行的實現代碼,先來看看BankingA
的招待邏輯:
public class BankingA extends Banking {@Overrideprotected void makeCustomerHappy(String customer) {System.out.println("請"+customer+"吃飯,并為其辦理業務");}
}
BankingB
的招待邏輯:
public class BankingB extends Banking {@Overrideprotected void makeCustomerHappy(String customer) {System.out.println("請" + customer + "喝茶,并為其辦理業務");}
}
測試代碼如下:
public static void main(String[] args) {BankingA bankingA = new BankingA();bankingA.processCustomer(1);BankingB bankingB= new BankingB();bankingB.processCustomer(1);}
對應輸出結果:
請6brkb吃飯,并為其辦理業務
請autjm喝茶,并為其辦理業務
還是一樣的問題,找到會員是一段無返回值的簡單輸出,為了適配模板方法,這一行代碼也還是要創建一個類,所以我們還是需要用lambda
對其進行簡化。
查看抽象方法makeCustomerHappy
的定義,它要求傳入一個傳入而返回一個void,查閱java8對應的函數式接口,我們找到了Consumer
:
@FunctionalInterface
public interface Consumer<T> {/*** Performs this operation on the given argument.** @param t the input argument*/void accept(T t);
于是我們得出公式s->Void
:
對此我們將抽象類Banking 加以改造,將抽象方法makeCustomerHappy
改為Consumer
接口:
public abstract class Banking {public void processCustomer(int id, Consumer<String> makeCustomerHappy) {//查詢會員名String customer = getCustomerWithId(id);//招待會員makeCustomerHappy.accept(customer);}private String getCustomerWithId(int id) {return RandomUtil.randomString(5);}}
這樣一來,后續的調用即可用一段lambda
實現:
public class Main {public static void main(String[] args) {Banking bankingA = new Banking();bankingA.processCustomer(1,customer-> System.out.println("請"+customer+"吃飯,并為其辦理業務"));Banking bankingB = new Banking();bankingB.processCustomer(1,customer-> System.out.println("請"+customer+"喝茶,并為其辦理業務"));}
}
觀察者模式
觀察者模式算是最經典也最好理解的設計模式,觀察者只需將自己注冊到感興趣的主題上,一旦有主題更新就會及時通知觀察者,觀察者按照自己的需要進行響應處理。
對此我們首先定義觀察者的接口:
/*** 觀察者*/
public interface Observer {void inform(String msg);
}
接下來就是主題:
public interface Subject {void registerObserver(Observer observer);void notifyObserver();
}
創建一個觀察者1以及觀察者2以及實現他們對自己感興趣主題時會做出的反饋輸出方法inform
:
public class Observer1 implements Observer {@Overridepublic void inform(String msg) {System.out.println("觀察者1收到通知,內容為:" + msg);}
}
public class Observer2 implements Observer {@Overridepublic void inform(String msg) {System.out.println("觀察者2收到通知,內容為:" + msg);}
}
最后就是主題類的實現,我們將觀察者聚合,如果觀察者對SubJect1
感興趣,則通過registerObserver
完成注冊,一旦主題要發布新消息就可以通過notifyObserver
及時通知每一個訂閱者:
public class SubJect1 implements Subject {private String msg;public SubJect1(String msg) {this.msg = msg;}private List<Observer> observerList = new ArrayList<>();@Overridepublic void registerObserver(Observer observer) {observerList.add(observer);}@Overridepublic void notifyObserver() {observerList.forEach(o -> o.inform(msg));}
}
測試代碼和對應輸出結果如下所示:
public static void main(String[] args) {SubJect1 subJect1 = new SubJect1("請大家學習《基于lambda簡化設計模式》");//注冊訂閱者subJect1.registerObserver(new Observer1());subJect1.registerObserver(new Observer2());//主題發起通知subJect1.notifyObserver();}
輸出結果:
觀察者1收到通知,內容為:請大家學習《基于lambda簡化設計模式》
觀察者2收到通知,內容為:請大家學習《基于lambda簡化設計模式》
很明顯的Observer的inform是典型的Consumer接口,我們直接將其簡化:
public static void main(String[] args) {SubJect1 subJect1 = new SubJect1("請大家學習《基于lambda簡化設計模式》");//注冊訂閱者subJect1.registerObserver(s -> System.out.println("觀察者1收到消息" + s));subJect1.registerObserver(s -> System.out.println("觀察者2收到消息" + s));//主題發起通知subJect1.notifyObserver();}
責任鏈模式
我們希望字符串被對象1處理完成之后要轉交給對象2處理,并且我們后續可能還會交給更多的對象處理,通過對需求的梳理和抽象,這個功能完全可以通過責任鏈模式來實現。
首先聲明公共抽象類,可以看到考慮類的通用性筆者將這個類的入參設置為泛型,并且公共方法handle
的步驟為:
- 調用自己的
handWork
處理輸入數據,handWork
交給實現類自行編寫。 - 若
successor
不為空,則將處理結果交給下一個處理器處理,由此構成一條處理鏈。
public abstract class ProcessingObject<T> {/*** 下一個處理器*/private ProcessingObject<T> successor;public ProcessingObject<T> getSuccessor() {return successor;}public void setSuccessor(ProcessingObject<T> successor) {this.successor = successor;}public T handle(T input) {//先自己處理完,如果有后繼責任鏈,則交給后面的責任鏈處理,遞歸下去T t = handWork(input);if (successor != null) {return successor.handWork(t);}return t;}/*** 自己的處理邏輯** @param intput* @return*/abstract T handWork(T intput);
}
對應的我們基于這個抽象類實現兩個字符處理器,ProcessingStr1
會將收到的中文逗號換位英文逗號:
public class ProcessingStr1 extends ProcessingObject<String> {@OverrideString handWork(String intput) {return intput.replace(",", ",");}
}
而ProcessingStr2
會將中文句號替換為英文句號:
public class ProcessingStr2 extends ProcessingObject<String> {@OverrideString handWork(String intput) {return intput.replace("。", ".");}
}
測試代碼和輸出結果如下:
public static void main(String[] args) {ProcessingObject<String> p1 = new ProcessingStr1();ProcessingObject<String> p2 = new ProcessingStr2();p1.setSuccessor(p2);System.out.println(p1.handle("hello,world。"));}
可以看到所有的中文符號都被替換成英文符號了:
hello,world.
話不多說,不難看出上文這種傳入String
返回String
的方法,我們完全可以使用UnaryOperator
函數式接口實現表達式。
從UnaryOperator
源碼可知,它繼承Function
,我們只需傳入泛型T
即可得到一個Function<T, T>
,從而讓我們得到一個T->T
的Function
表達式:
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {/*** Returns a unary operator that always returns its input argument.** @param <T> the type of the input and output of the operator* @return a unary operator that always returns its input argument*/static <T> UnaryOperator<T> identity() {return t -> t;}
}
而責任連的方式也很簡單,因為UnaryOperator
是Function的子類,這意味著我們可以使用Function
的andThen
將所有的UnaryOperator
完成銜接:
UnaryOperator<String> p1 = i -> i.replace(",", ",");UnaryOperator<String> p2 = i -> i.replace("。", ".");p1.andThen(p2);System.out.println(p1.apply("hello,world。"));
小結
為了適配設計模式常會出現為了一段簡單的邏輯,而去編寫大量實現類的情況,所以我們建議,對于邏輯比較簡單且需要適配設計模式的功能,可以嘗試找到合適的函數式接口簡化功能的實現,避免大量類文件的聲明。
參考
Java 8 in Action