本文是一篇《Java 8實(shí)戰(zhàn)》的閱讀筆記,閱讀大約需要5分鐘羔挡。
有點(diǎn)標(biāo)題黨签财,但是這確實(shí)是我最近使用Lambda表達(dá)式的感受。設(shè)計(jì)模式是過去的一些好的經(jīng)驗(yàn)和套路的總結(jié)淹朋,但是好的語言特性可以讓開發(fā)者不去考慮這些設(shè)計(jì)模式笙各。面向?qū)ο蟪R姷脑O(shè)計(jì)模式有策略模式、模板方法础芍、觀察者模式杈抢、責(zé)任鏈模式以及工廠模式,使用Lambda表達(dá)式(函數(shù)式編程思維)有助于避免面向?qū)ο箝_發(fā)中的那些固定代碼仑性。下面我們挑選了策略模式和職責(zé)鏈模式兩個(gè)案例進(jìn)行分析惶楼。
案例1:策略模式
當(dāng)我們解決一個(gè)問題有不同的解法的時(shí)候,又不希望客戶感知到這些解法的細(xì)節(jié)诊杆,這種情況下適合使用策略模式歼捐。策略模式包括三個(gè)部分:
- 解決問題的算法(上圖中的Strategy);
- 一個(gè)或多個(gè)該類算法的具體實(shí)現(xiàn)(上圖中的ConcreteStrategyA晨汹、ConcreteStrategyB和ConcreteStrategyC)
- 一個(gè)或多個(gè)客戶使用場景(上圖中的ClientContext)
面向?qū)ο笏悸?/h3>
首先定義策略接口豹储,表示排序策略:
public interface ValidationStrategy {
boolean execute(String s);
}
然后定義具體的實(shí)現(xiàn)類(即不同的排序算法):
public class IsAllLowerCase implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("[a-z]+");
}
}
public class IsNumberic implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("\\d+");
}
}
最后定義客戶使用場景,代碼如下圖所示宰缤。Validator是為客戶提供服務(wù)時(shí)使用的上下文環(huán)境颂翼,每個(gè)Valiator對象中都封裝了具體的Strategy對象晃洒,在實(shí)際工作中,我們可以通過更換具體的Strategy對象來進(jìn)行客戶服務(wù)的升級朦乏,而且不需要讓客戶進(jìn)行升級球及。
public class Validator {
private final ValidationStrategy strategy;
public Validator(ValidationStrategy strategy) {
this.strategy = strategy;
}
/**
* 給客戶的接口
*/
public boolean validate(String s) {
return strategy.execute(s);
}
}
public class ClientTestDrive {
public static void main(String[] args) {
Validator numbericValidator = new Validator(new IsNumberic());
boolean res1 = numbericValidator.validate("7780");
System.out.println(res1);
Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
boolean res2 = lowerCaseValidator.validate("aaaddd");
System.out.println(res2);
}
}
函數(shù)式編程思路
如果使用Lambda表達(dá)式考慮,你會(huì)發(fā)現(xiàn)ValidationStrategy就是一個(gè)函數(shù)接口(還與Predicate<String>具有同樣的函數(shù)描述)呻疹,那么就不需要定義上面那些實(shí)現(xiàn)類了吃引,可以直接用下面的代碼替換,原因是Lambda表達(dá)式內(nèi)部已經(jīng)對這些類進(jìn)行了一定的封裝刽锤。
public class ClientTestDrive {
public static void main(String[] args) {
Validator numbericValidator = new Validator((String s) -> s.matches("\\d+"));
boolean res1 = numbericValidator.validate("7789");
System.out.println(res1);
Validator lowerCaseValidator = new Validator((String s) -> s.matches("[a-z]+"));
boolean res2 = lowerCaseValidator.validate("aaaddd");
System.out.println(res2);
}
}
案例2:責(zé)任鏈模式
在某些場景下镊尺,需要對一個(gè)對象做一系列的工作,這些工作分別是由不同的類完成的并思,這時(shí)候就比較適合使用責(zé)任鏈模式庐氮。責(zé)任鏈模式的主要組成部分包括三個(gè):
- 管理操作序列的抽象類,在該抽象類里有會(huì)有一個(gè)對象記錄當(dāng)前對象的后繼操作對象宋彼;
- 一些具體的操作對象弄砍,這些操作對象會(huì)以一個(gè)鏈表的形式組織起來
-
一個(gè)使用該模式的客戶端組件,該組件只需要跟一個(gè)組件打交道就好输涕,不需要跟很多個(gè)操作對象耦合在一起音婶。
面向?qū)ο笏悸?/h3>
首先看下我們這里定義了一個(gè)抽象類ProcessingObject,其中successor字段用于管理該對象的后繼操作對象莱坎;handle接口作為對外提供服務(wù)的接口衣式;handleWork作為實(shí)際處理對象的操作方法。
public abstract class ProcessingObject<T> {
protected ProcessingObject<T> successor;
public void setSuccessor(ProcessingObject<T> successor) {
this.successor = successor;
}
public T handler(T input) {
T r = handleWork(input);
if (successor != null) {
return successor.handler(r);
}
return r;
}
abstract protected T handleWork(T input);
}
接下來可以定義兩個(gè)具體的操作對象檐什,如下面代碼所示碴卧。PS:這里《Java 8實(shí)戰(zhàn)》書中用的是replaceAll方法是不太合適的,這個(gè)點(diǎn)可以參考我們之前的文章——020:舉幾個(gè)String的API以及案例乃正。
public class HeaderTextProcessing extends ProcessingObject<String> {
@Override
protected String handleWork(String input) {
return "From Raoul, Mario and Alan: " + input;
}
}
public class SpellCheckerProcessing extends ProcessingObject<String> {
@Override
protected String handleWork(String input) {
return input.replace("labda", "lambda");
}
}
最后螟深,你就可以在Client中將這上面兩個(gè)具體的操作類對象構(gòu)成一個(gè)操作序列,參見下面的代碼:
public class Client {
public static void main(String[] args) {
ProcessingObject<String> p1 = new HeaderTextProcessing();
ProcessingObject<String> p2 = new SpellCheckerProcessing();
p1.setSuccessor(p2);
String result = p1.handler("Aren't labdas really sexy?!!");
System.out.println(result);
}
}
函數(shù)式編程思路
如果使用函數(shù)式編程思維烫葬,那么職責(zé)鏈模式就直接了——y=f(x)和z=g(x)這兩個(gè)方法都是要對x做處理,那么如果將這兩個(gè)函數(shù)組合在一起凡蜻,就會(huì)形成r=f(g(x))的情況搭综,也就是可以使用Lambda表達(dá)式中的addThen來串聯(lián)起多個(gè)處理過程。
public class ClientWithLambda {
public static void main(String[] args) {
UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
UnaryOperator<String> spellCheckProcessing = (String text) -> text.replace("labda", "lambda");
Function<String, String> function = headerProcessing.andThen(spellCheckProcessing);
String result = function.apply("Aren't labdas really sexy?!!");
System.out.println(result);
UnaryOperator<String> hhhhhProcessing = (String text) -> text.concat("hhhh");
Function<String, String> function1 = function.andThen(hhhhhProcessing);
String result1 = function1.apply("Aren't labdas really sexy?!!");
System.out.println(result1);
}
}
上面是利用Java原生的Lambda表達(dá)式實(shí)現(xiàn)的職責(zé)鏈模式划栓,我們也可以使用前面一篇文章——vavr:讓你像寫Scala一樣寫Java中介紹過的vavr庫來實(shí)現(xiàn)兑巾,代碼如下所示:
public class ClientWithVavr {
public static void main(String[] args) {
Function1<String, String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
Function1<String, String> specllCheckProcessing = (String text) -> text.replace("labda", "lambda");
Function1<String, String> function = headerProcessing.compose(specllCheckProcessing);
String result = function.apply("Aren't labdas really sexy?!!");
System.out.println(result);
}
}
總結(jié)
可以看出,函數(shù)式編程思維跟面向?qū)ο缶幊趟季S的思考方式是不同的忠荞,表達(dá)力更強(qiáng)蒋歌,因此帅掘,作為開發(fā)者是時(shí)候認(rèn)真學(xué)習(xí)下函數(shù)式編程思維了,作為Java開發(fā)者堂油,我準(zhǔn)備先從Lambda表達(dá)式開始學(xué)起修档,然后嘗試學(xué)習(xí)下Scala或Kotlin兩門語言中的函數(shù)式變成特性。
參考資料
- 《Java編程實(shí)戰(zhàn)》
- 《設(shè)計(jì)模式之禪》
本號(hào)專注于后端技術(shù)府框、JVM問題排查和優(yōu)化吱窝、Java面試題、個(gè)人成長和自我管理等主題迫靖,為讀者提供一線開發(fā)者的工作和成長經(jīng)驗(yàn)院峡,期待你能在這里有所收獲。