Lambda 表達(dá)式修改設(shè)計(jì)模式
本節(jié)內(nèi)容并不是討論設(shè)計(jì)模式,而是討論如何使用 Lambda 表達(dá)式讓現(xiàn)有的設(shè)計(jì)模式變得更簡潔,或者在某些情況是有一些不同的實(shí)現(xiàn)方式。我們可以從另一個(gè)角度來學(xué)習(xí)使用和理解 Lambda 表達(dá)式。
Tips: 要更好地理解本節(jié)內(nèi)容需要對(duì)涉及的四個(gè)設(shè)計(jì)模式有一定的了解,具體可以查閱相關(guān)資料。
1. 命令者模式
命令者模式是將操作、方法調(diào)用、命令封裝成一個(gè)對(duì)象,在合適的時(shí)候讓該對(duì)象進(jìn)行執(zhí)行。
它包五個(gè)角色:
- 客戶端(Client):發(fā)出命令;
- 調(diào)用者(Invoker):調(diào)用抽象命令,還可以記錄執(zhí)行的命令;
- 接受者(Receiver):命令的實(shí)際執(zhí)行者,一個(gè)命令會(huì)存在一個(gè)或多個(gè)接收者;
- 抽象命令(Command):定義命令執(zhí)行方法;
- 具體命令(Concrete Command):調(diào)用接收者,執(zhí)行命令的具體方法。
命令者模式被大量運(yùn)用在組件化的圖形界面系統(tǒng)、撤銷功能、線城市、事務(wù)和向?qū)е小N覀儊砜匆粋€(gè)例子,我們實(shí)現(xiàn)一個(gè)將一系列命令錄制下來的功能,有點(diǎn)類似于 Word 中的撤銷功能那樣記錄每一步的操作。
//定義一個(gè)命令接收者,包含打開、關(guān)閉和保存三個(gè)操作
public class Editor{
public void save(){
System.out.println("do save")
}
public void open();
public void close();
}
?
// 定名命令對(duì)象,所有操作都要實(shí)現(xiàn)這個(gè)接口
public interface Action{
public void perform();
}
?
//實(shí)現(xiàn)保存命令操作
public Save implements Action{
private final Editor editor;
public Save(Editor editor){
this.editor = editor;
}
public void perform(){
editor.save();
}
}
?
//實(shí)現(xiàn)打開命令操作
public class Open implements Action{
private final Editor editor;
?
public Open(Editor editor){
this.editor = editor;
}
public void perform(){
editor.open();
}
}
?
//實(shí)現(xiàn)關(guān)閉命令操作
public class Close implements Action{
private final Editor editor;
?
public Close(Editor editor){
this.editor = editor;
}
public void perform(){
editor.close();
}
}
?
//定義命令發(fā)起者來記錄和順序執(zhí)行命令
public class Invoker{
private final List<Action> actions = new ArrayList<>();
?
public void record(Action action){
actions.add(action);
}
public void run(){
for (Action action : actions) {
action.perform();
}
}
}
?
//定義客戶端,用來記錄和執(zhí)行命令
public class Client{
public static void main(String...s){
Invoker invoker = new Invoker();
Editor editor = new Editor();
//記錄保存操作
invoker.record(new Save(editor));
//記錄打開操作
invoker.record(new Open(editor));
//記錄關(guān)閉操作
invoker.record(new Close(editor));
invoker.run();
}
}
輸出結(jié)果:
do save
do open
do close
以上是一個(gè)完整的命令者模式的例子,我們使用 Lambda 表達(dá)式來修改客戶端:
public class Client{
public static void main(String...s){
Invoker invoker = new Invoker();
Editor editor = new Editor();
//記錄保存操作
invoker.record(()->editor.open());
//記錄打開操作
invoker.record(()->editor.save());
//記錄關(guān)閉操作
invoker.record(()->editor.close());
invoker.run();
}
}
我們使用引用方法來修改客戶端:
public class Client{
public static void main(String...s){
Invoker invoker = new Invoker();
Editor editor = new Editor();
//記錄保存操作
invoker.record(editor::open);
//記錄打開操作
invoker.record(editor::save);
//記錄關(guān)閉操作
invoker.record(editor::close);
invoker.run();
}
}
通過這樣的改造,我們的代碼意圖更加明顯了呢,一看就明白具體記錄的是哪個(gè)操作。
2. 策略模式
策略模式是軟件運(yùn)行時(shí),根據(jù)實(shí)際情況改變軟件的算法行為。
常見的策略模式就是文件壓縮軟件,通常一個(gè)壓縮軟件可以支持多種壓縮算法如 zip 、gzip、rar 等,通過策略模式可以讓壓縮軟件根據(jù)我們具體的操作來實(shí)現(xiàn)不同的壓縮算法。我們來看一個(gè)壓縮數(shù)據(jù)的策略模式的例子:
//定義壓縮策略接口
public interface CompressionStrategy{
public OutputStream compress(OutputStream data) throws IOException;
}
//gzip壓縮策略
public class GzipStrategy implements CompressionStrategy{
@Override
public OutputStream compress(OutputStream data) throws IOException {
return new GZIPOutputStream(data);
}
}
//zip壓縮策略
public class ZipStrategy implements CompressionStrategy{
@Override
public OutputStream compress(OutputStream data) throws IOException {
return new ZipOutputStream(data);
}
}
?
//在構(gòu)造類時(shí)提供壓縮策略
public class Compressor{
private final CompressionStrategy strategy;
public Compressor(CompressionStrategy strategy){
this.strategy = strategy;
}
public void compress(Path inFiles, File outputFile) throws IOException{
try(OutputStream outputStream = new FileOutputStream(outputFile)){
Files.copy(inFiles,strategy.compress(outputStream));
}
}
}
//使用具體的策略初始化壓縮策略
//gzip策略
Compressor gzipCompressor = new Compressor(new GzipStrategy());
//zip策略
Compressor zipCompressor = new Compressor(new ZipStrategy());
以上就是一個(gè)完整的 zip 和 gzip 的壓縮策略?,F(xiàn)在我們用 Lambda 表達(dá)式來優(yōu)化初始化壓縮策略
//使用構(gòu)造器引用優(yōu)化初始化壓縮策略
//gzip策略
Compressor gzipCompressor = new Compressor(GzipStrategy::new);
//zip策略
Compressor zipCompressor = new Compressor(ZipStrategy::new);
3. 觀察者模式
觀察者模式是定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。
觀察者模式被適用于消息通知、觸發(fā)器之類的應(yīng)用場景中
觀察者模式包含三個(gè)類 主題(Subject)、觀察者(Observer)和客戶端(Client)。Subject 對(duì)象帶有綁定觀察者到 Client 對(duì)象和從 Client 對(duì)象解綁觀察者的方法。
我們來看一個(gè)例子:現(xiàn)在我們用觀察者模式實(shí)現(xiàn)根據(jù)輸入的數(shù)字,自動(dòng)將輸入的數(shù)字其轉(zhuǎn)變成對(duì)應(yīng)十六進(jìn)制和二進(jìn)制。
//定義一個(gè)觀察者
public interface Observer {
public void update(int num);
}
?
//創(chuàng)建主題
public static class Subject{
private List<Observer> observers = new ArrayList<>();
private int num;
public int getNum(){
return num;
}
private void setNum(int num){
this.num = num;
this.notifyAllObservers();
}
private void addObserver(Observer observer) {
observers.add(observer);
}
?
private void notifyAllObservers(){
for(Observer observer:observers){
observer.update(num);
}
}
}
?
//創(chuàng)建二進(jìn)制觀察者
public static class BinaryObserver implements Observer{
?
private Subject subject;
?
?
@Override
public void update(int num) {
System.out.println( "Binary String: "
+ Integer.toBinaryString( num ) );
}
}
?
//創(chuàng)建十六進(jìn)制觀察者
public static class HexObserver implements Observer{
?
@Override
public void update(int num) {
System.out.println( "Hex String: "
+ Integer.toHexString( num ) );
}
}
?
//使用 Subject 和實(shí)體觀察者對(duì)象
public class Demo{
public static void main(String... s){
Subject subject = new Subject();
subject.addObserver(new BinaryObserver());
subject.addObserver(new HexObserver());
System.out.println("first input is:11");
subject.setNum(11);
System.out.println("second input is:15");
subject.setNum(15);
}
}
輸出結(jié)果:
first input is:11
Binary String: 1011
Hex String: b
second input is:15
Binary String: 1111
Hex String: f
同樣我們使用 Lambda 表達(dá)式來修改 Demo
類:
public class Demo{
public static void main(String...s){
Subject subject = new Subject();
subject.addObserver( num -> System.out.println( "Binary String: " + Integer.toBinaryString( num )));
subject.addObserver( num -> System.out.println( "Hex String: " + Integer.toHexString(num )));
System.out.println("first input is:11");
subject.setNum(11);
System.out.println("second input is:15");
subject.setNum(15);
}
}
在這個(gè)例子中,我們實(shí)際上是省去了 BinaryObserver
和 HexObserver
兩個(gè)類的定義,直接使用 Lambda 表達(dá)式來描述二進(jìn)制和十六進(jìn)制轉(zhuǎn)化的邏輯。
4. 模板方法模式
模板方法模式是定義一個(gè)操作中的算法的骨架,從而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
通常對(duì)于一些重要的復(fù)雜方法和多個(gè)子類共有的方法且邏輯相同的情況下會(huì)使用模板方法模式。比如用戶第三方用戶認(rèn)證的時(shí)候就比較適合使用模板方法。
我們來看一個(gè)例子:假設(shè)我們現(xiàn)在需要用到微信、微博的第三方用戶授權(quán)來獲取用戶的信息。
//使用模板方法模式描述獲取第三方用戶信息的過程
public abstract class Authentication{
public void checkUserAuthentication(){
checkIdentity();
fetchInfo();
}
?
protected abstract void checkIdentity();
protected abstract void fetchInfo();
}
?
//微信用戶
public class WechatAuthenication extends Authentication{
@Override
protected void checkIdentity() {
System.out.println("獲得微信用戶授權(quán)");
}
?
@Override
protected void fetchInfo() {
System.out.println("獲取微信用信息");
}
}
?
//微信用戶
public class WeiboAuthenication extends Authentication{
@Override
protected void checkIdentity() {
System.out.println("獲得微博用戶授權(quán)");
}
?
@Override
protected void fetchInfo() {
System.out.println("獲取微博用信息");
}
}
?
//調(diào)用模板方法
public class Demo{
public static void main(String...s){
Authentication auth = new WechatAuthenication();
auth.checkUserAuthentication();
auth = new WeiboAuthenication();
auth.checkUserAuthentication();
}
}
輸出結(jié)果:
獲得微信用戶授權(quán)
獲取微信用信信息
獲得微博用戶授權(quán)
獲取微博用信信息
現(xiàn)在我們使用 Lambda 表達(dá)式換個(gè)角度來思考模板方法模式。如果我們用函數(shù)式接口來組織模板方法中的調(diào)用過程,相比使用繼承來構(gòu)建要顯得靈活的多。
//定義一個(gè)處理接口,用來處理一項(xiàng)事務(wù),如授權(quán)或者獲取信息。
public interface Processer{
public void process();
}
?
//封裝調(diào)用過程
public class Authentication{
private final Processer identity;
private final Processer userinfo;
public Authentication(Criteria identity,Criteria userinfo){
this.identity = identity;
this.userinfo = userinfo;
}
?
public void checkUserAuthentication(){
identity.process();
userinfo.process();
}
}
?
//使用模板方法
public class Demo{
Authentication auth = new Authentication(()->System.out.println("獲得微信用戶授權(quán)"),
()->System.out.println("獲取微信用戶信息"));
auth.checkUserAuthentication();
auth = new Authentication(()->System.out.println("獲得微博用戶授權(quán)"),
()->System.out.println("獲取微博用戶信息"));
auth.checkUserAuthentication();
}
輸出結(jié)果:
獲得微信用戶授權(quán)
獲取微信用信信息
獲得微博用戶授權(quán)
獲取微博用信信息
此時(shí),我們的模板方法得到了大幅的簡化,同時(shí)通過函數(shù)接口讓模板方法獲得了極大的靈活性。
5. 小結(jié)
本節(jié)我們討論如何使用 Lambda 表達(dá)式讓我們的設(shè)計(jì)模式變得更簡單、更好用。這里我們使用了四個(gè)例子從不同的角度來。
- 命令者模式:我們使用 Lamabda 表達(dá)式的方法引用來進(jìn)行改造;
- 策略模式:我們使用了 Lambda 表達(dá)式的構(gòu)造器引用來進(jìn)行改造;
- 觀察者模式:我們使用了標(biāo)準(zhǔn)的 Lambda 表達(dá)式來進(jìn)行改造;
- 模板方法模式:我們使用了函數(shù)式接口來進(jìn)行改造。
目的是希望給大家一點(diǎn)啟發(fā),在平常的編碼過程中去思考如何使用 Lambda 表達(dá)式來設(shè)計(jì)我們的程序。對(duì)于其他的設(shè)計(jì)模式如果感興趣的話可以自己嘗試下去修改它們。