OO makes code understandable by encapsulating moving parting, but FP makes code understandable by minimizing moving parts. -Michael Feathersjava
Logger
if (logger.isLoggable(Level.INFO)) { logger.info("problem:" + getDiagnostic()); }
這個實現存在以下一些壞味道:數據庫
重複的樣板代碼,而且散亂到用戶的各個角落;安全
在logger.debug
以前,首先要logger.isLoggable
,logger
暴露了太多的狀態邏輯,違反了LoD(Law of Demeter)
app
Eliminate Effects Between Unrelated Things.函數
logger.info("problem:" + getDiagnostic());
public void info(String msg) { if (isLoggable(Level.INFO)) { log(msg) } }
這樣的設計雖然將狀態的查詢進行了封裝,遵循了LoD
原則,但依然存在一個嚴重的性能問題。不管如何,getDiagnostic
都將獲得調用,若是它是一個耗時、昂貴的操做,可能成爲系統的瓶頸。性能
靈活地應用Lambda
惰性求值的特性,能夠很漂亮地解決這個問題。scala
public void log(Level level, Supplier<String> supplier) { if (isLoggable(level)) { log(supplier.get()); } } public void debug(Supplier<String> supplier) { log(Level.DEBUG, supplier); } public void info(Supplier<String> supplier) { log(Level.INFO, supplier); } ...
用戶的代碼也更加簡潔,省略了那些重複的樣板代碼。debug
logger.info(() -> "problem:" + getDiagnostic());
在使用lambda
時多餘的()
顯得有點冗餘,可使用by-name
參數進一步提升表達力。設計
def log(level: Level, msg: => String) { if (isLoggable(level)) { log(msg) } } def debug(msg: => String) { log(DEBUG, msg) } def info(msg: => String) { log(INFO, msg) }
logger.info("problem:" + getDiagnostic());
"problem:" + getDiagnostic()
語句並不是在logger.info
展開計算,它被延遲計算直至被apply
的時候才真正地被評估和計算。日誌
咱們常常會遇到一個場景,在執行操做以前,先準備環境,以後再拆除環境。例如XUnit
中的setUp/tearDown
;操做數據庫時,先取得數據庫的鏈接,操做數據後確保釋放鏈接;當操做文件時,先打開文件流,操做文件後確保關閉文件流。
try-finally
爲了保證異常安全性,在Java7
以前,經常使用try-finally
的實現模式解決這樣的問題。
public static String process(File file) throws IOException { BufferedReader bf = new BufferedReader(new FileReader(file)); try { return bf.readLine(); } finally { if (bf != null) bf.close(); } }
這樣的設計和實現存在幾個問題:
if (bf != null)
是必須的,但經常被人遺忘;
try-finally
的樣板代碼遍及在用戶程序中,形成大量的重複設計;
try-with-resources
自Java7
,只要實現了AutoCloseable
的資源類,可使用try-with-resources
的實現模式,進一步簡化上例的樣板代碼。
public String process(File file) throws IOException { try(BufferedReader bf = new BufferedReader(new FileReader(file))) { return bf.readLine(); } }
可是,在某些場景下很難最大化地複用代碼,這使得實現中存在大量的重複代碼。例如遍歷文件中全部行,並替換制定模式爲其餘的字符串。
public String replace(File file, String regex, String i) throws IOException { try(BufferedReader bf = new BufferedReader(new FileReader(file))) { return bf.readLine().replaceAll(regex, replace); } }
爲了最大化地複用代碼,最小化用戶樣板代碼,將資源操做先後的代碼保持封閉,使用lambda
定製與具體問題相關的處理邏輯。
process
使用BufferedProcessor
實現行爲的參數化。
public static String process(File file, BufferedProcessor p) throws IOException { try(BufferedReader bf = new BufferedReader(new FileReader(file))) { return p.process(bf); } }
其中,BufferedProcessor
是一個函數式接口,用於描述lambda
的原型信息。
@FunctionalInterface public interface BufferedProcessor { String process(BufferedReader bf) throws IOException; }
用戶使用lambda
表達式,使得代碼更加簡單、漂亮。
process(file, bf -> bf.readLine());
若是使用Method Reference
,可加強表達力。
process(file, BufferedReader::readLine);
爲了最大化地複用資源釋放的實現,使用Scala
能夠神奇地構造一個簡單的DSL
,讓用戶更好地實現複用。
Make it Easy to Reuse.
import scala.language.reflectiveCalls object using { def apply[R <: { def close(): Unit }, T](resource: => R)(f: R => T) = { var res: Option[R] = None try { res = Some(resource) f(res.get) } finally { if (res != None) res.get.close } } }
R <: { def close(): Unit }
中泛型參數R
是一個擁有close
方法的類型;resource: => R
將resource
聲明爲Call by Name
,可延遲計算;apply
使用了兩個參數,並進行了Currying
化。
受益於Currying
,用戶的定製的函數可使用大括號來加強表達力,using
猶如內置的語言特性,獲得抽象了的控制結構。
using(Source.fromFile(file)) { source => source.getLines }
由於參數source
僅僅使用了一次,能夠經過佔位符進一步加強表達力。
using(Source.fromFile(file)) { _.getLines }