使用 IoC 反轉控制的三種設計模式
對於許多開發人員來說,控制反演(IoC)都是一個模糊的概念,因為他們在現實世界中很少或沒有被應用過。在最好的情況下,控制反演(IoC)可以加單的認為是等效於依賴注入(DI)。實際上,只有在翻轉控制與依賴注入雙方都只是反映翻轉依賴管理控制的時候,才認為兩者是等效的。雖然,依賴注入實際上是IoC的一種眾所周知的形式。但是,事實上IoC卻是一個相對更為廣泛的軟體設計範例,可以通過多種模式來進行實現。在本文中,我們將介紹依賴注入,觀察者模式和模板方法模式如何實現控制反轉的。
正如許多其他設計模式,是從各種各樣的使用場景中總結出來的,IoC的實現方式,也是類似的一種適合開發者使用的折中方式:
一方面,高度解耦組件的設計,以及將應用邏輯封裝在一個單一的地方,是實現IoC的直接而又自然的一種方式。
另一方面,上述實現需要至少需要構建一個間接層,然而在某些用例中,這可能又是一種過度設計了。
接下來,不妨看幾個具體的實現,這將有助於您了解,如何在這些屬性之間進行權衡折中。
IOC範式揭秘
控制反轉是一種帶有某些特徵的模式。下面,給出了由Martin Fowler給出的一個IOC經典範例,該範例實現的功能是從控制台中收集用戶數據。
public static void main(String[] args) { while (true) { BufferedReader userInputReader = new BufferedReader( new InputStreamReader(System.in)); System.out.println("Please enter some text: "); try { System.out.println(userInputReader.readLine()); } catch (IOException e) { e.printStackTrace(); } } }
這個用例中,在main方法中進行流程控制:在無限循環調用中,讀取用戶輸入,並將讀取的內容輸出到控制台上。完全又main方法控制何時去讀取用戶輸入,何時去輸出。
考慮下,上述程序的一個新版本,該版本中需要通過圖形界面中的文本框來收件用戶輸入,另外還有個按鈕,該按鈕上綁定有一個action監聽器。這樣的話,用戶每次點擊按鈕,輸入的文本由監聽器收集並列印到面板。
在這個版本的程序中,它實際上是由事件監聽器模型(在這種情況下,這是框架)的控制下,調用開發者編寫的用於讀取和列印用戶輸入的代碼。簡單地說,框架將調用開發人員的代碼,而不是其他方式。該框架實際上是一個可擴展的結構,它為開發人員提供了一組注入自定義代碼段的切入點。
這種情況下,控制已經被有效的反轉了。
從更通用的角度來看,由框架定義的每個可調用擴展點(以介面實現,實現繼承(也稱為子類)的形式)是IoC的一種明確定義的形式。
看下,下述這個簡單的Servlet例子:
public class MyServlet extends HttpServlet { protected void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // developer implementation here } protected void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // developer implementation here } }
此處,HttpServlet類(屬於框架)是完全控制程序的元素,而不是MyServlet這個子類。在由servlet容器創建之後,當收到servlet的GET和POST的HTTP請求,doGet()和doPost()方法中的代碼會分別自動調用。
與典型的繼承方式相比,即子類是控制的元素,而不是基類,該例中,控制項已經被反轉了。
事實上,servlet的方法是模板方法模式的實現,稍後我們再深入討論。
使用那些通過提供可擴展API,秉承開閉原則的框架時,使用框架的開發人員的角色,最終被歸結為定義自己的一組自定義類,即開發人員要麼通過實現框架提供的一個或多個介面方式,要麼通過繼承現有基類的方式。反過來,類的實例卻是直接框架進行實例化,並且這些事例是被框架調用的。
此處引用Fowler的話:
該框架調用開發人員,而不是開發人員調用該框架。
因此,IoC通常被稱為好萊塢原則:
不要打電話給我們,我們會打電話給你。
IOC的實現方式
該問題上,顯而易見的是,實現控制反轉是有幾種不同方法的。我們不妨來總結一下,那些常見的實現方式。
注入依賴實現IOC
如前所述,注入依賴是IOC的一種實現方式,而且是最常見的一種面向對象設計方式。但是,思考一下:注入依賴究竟是如何達到控制反轉效果的呢?
為了回答這個問題,我們給出如下一個原始的例子:
public interface UserQueue { void add(User user); void remove(User user); User get(); } public abstract class AbstractUserQueue implements UserQueue { protected LinkedList queue = new LinkedList(); @Override public void add(User user) { queue.addFirst(user); } @Override public void remove(User user) { queue.remove(user); } @Override public abstract User get(); } public class UserFifoQueue extends AbstractUserQueue { public User get() { return queue.getLast(); } } public class UserLifoQueue extends AbstractUserQueue { public User get() { return queue.getFirst(); } }
UserQueue 介面定義了公共的API,用於在一個隊列中去存放User對象(為了簡單明了,此處忽略User的具體實現)。AbstractUserQueue則是為後續的繼承類,提供了一些公用的方法實現。最後的UserFifoQueue 和 UserLifoQueue,則是分別實現了FIFO 和 LIFO 隊列。
這是,實現子類多態性的一種有效方式。但是這具體用什麼來買我們好處呢?實際上,好處還是蠻多的。
通過創建一個依賴於UserQueue抽象類型(也稱為DI術語中的服務)的客戶端類,可以在運行時注入不同的實現,無需會重構使用客戶端類的代碼:
public class UserProcessor { private UserQueue userQueue; public UserProcessor(UserQueue userQueue) { this.userQueue = userQueue; } public void process() { // process queued users here } }
UserProcessor展示了,注入依賴確實是IOC的一種方式。
我們可以通過一些硬編碼方式 如 new 操作,直接在構造函數中實例化在UserProcessor中獲取對隊列的依賴關係。但是,這是典型的代碼硬編程,它引入了客戶端類與其依賴關係之間的強耦合,並大大降低了可測性。耳邊警鐘聲聲想起啦!不是嗎?是的,這樣設計真的很挫。
該類在構造函數中聲明對抽象類 UserQueue 的依賴。也就是說,依賴關係不再通過 在構造函數中使用 new 操作, 相反,通過外部注入的方式,要麼使用依賴注入框架(如CDI和谷歌的Guice),要麼使用factory或builders模式。
簡而言之,使用DI,客戶端類的依賴關係的控制,不再位於這些類中;而是在注入器中進行:
public static void main(String[] args) { UserFifoQueue fifoQueue = new UserFifoQueue(); fifoQueue.add(new User("user1")); fifoQueue.add(new User("user2")); fifoQueue.add(new User("user3")); UserProcessor userProcessor = new UserProcessor(fifoQueue); userProcessor.process(); }
上述方式達到了預期效果,而且對UserLifoQueue的注入也簡單明了。顯而易見,DI確實是實現IOC的一種方式(該例中,DI是實現IOC的一個中間層)。
觀察者模式實現IOC
直接通過觀察者模式實現IOC,也是一種常見的直觀方式。廣義上講,通過觀察者實現IOC,與前文提到的通過GUI界面中的action監聽器方式類似。但是在使用action監聽器情況下,只有在特定的用戶事件發生時(點擊滑鼠,鍵盤或窗口事件等),才會發生調用。觀察者模式通常用於在模型視圖的上下文中,跟蹤模型對象的狀態的變遷。
在一個典型的實現中,一到多個觀察者綁定到可觀察對象(也稱為模式術語中的主題),例如通過調用addObserver方法進行綁定。一旦定義了被觀察者和觀察者之間的綁定,則被觀察者狀態的變遷都會觸發調用觀察者的操作。
為了深入了解這個概念,給出如下例子:
@FunctionalInterface public interface SubjectObserver { void update(); }
值發生改變時,會觸發調用上述這個很簡單的觀察者。真實情況下,通常會提供功能更豐富的API,如需要保存變化的實例,或者新舊值,但是這些都不需要觀察action(行為)模式,所以這裡舉例盡量簡單。
下面,給出一個被觀察者類:
public class User { private String name; private List observers = new ArrayList(); public User(String name) { this.name = name; } public void setName(String name) { this.name = name; notifyObservers(); } public String getName() { return name; } public void addObserver(SubjectObserver observer) { observers.add(observer); } public void deleteObserver(SubjectObserver observer) { observers.remove(observer); } private void notifyObservers(){ observers.stream().forEach(observer -> observer.update()); } }
User類中,當通過setter方法變更其狀態事,都會觸發調用綁定到它的觀察者。
使用主題觀察者和主題,以下是實例給出了觀察方式:
每當User對象的狀態通過setter方法進行修改時,觀察者將被通知並向控制台列印出一條消息。到目前為止,給出了觀察者模式的一個簡單用例。不過,通過這個看似簡單的用例,我們了解到在這種情況下控制是如何實現反轉的。
第 15 段(可獲 1.19 積分)
觀察者模式下,主題就是起到」框架層「的作用,它完全主導何時何地去觸發誰的調用。觀察者的主動權被外放,因為觀察者無法主導自己何時被調用(只要它們已經被註冊到某個主題中的話)。這意味著,實際上我們可以發現控制被反轉的」事發地「 – - – 當觀察者綁定到主題時:
上述用例,簡要說明了為什麼,觀察者模式(或GUI驅動環境中的action監聽器)是實現IoC的一種非常簡單的方式。正是以這種分散式設計軟體組件的形式,使得控制得以發生反轉。
通過模板方法模式實現IoC
模板方法模式實現的思想是在一個基類中通過幾個抽象方法(也稱演算法步驟)來定義一個通用的演算法,然後讓子類提供具體的實現,這樣保證演算法結構不變。
我們可以應用這個思想,定義一個通用的演算法來處理領域實體:
public abstract class EntityProcessor { public final void processEntity() { getEntityData(); createEntity(); validateEntity(); persistEntity(); } protected abstract void getEntityData(); protected abstract void createEntity(); protected abstract void validateEntity(); protected abstract void persistEntity(); }
processEntity() 方法是個模板方法,它定義了處理實體的演算法,而抽象方法代表了演算法的步驟,它們必須在子類中實現。通過多次繼承 EntityProcessor 並實現不同的抽象方法,可以實現若干演算法版本。
雖然這說清楚了模板方法模式背後的動機,但人們可能想知道為什麼這是 IoC 的模式。
典型的繼承中,子類調用基類中定義的方法。而這種模式下,相對真實的情況是:子類實現的方法(演算法步驟)被基類的模板方法調用。因此,控制實際是在基類中進行的,而不是在子類中。
這也是 IoC 的典型例子,通過分層結構實現。這種情況下,模板方法只是可調的擴展點的一個漂亮的名字,被開發者用來管理自己的一系列實現。
來自作者更多的文章
Validate Object Graphs with Java Bean Validation』s @Valid Annotation
Java Bean Validation』s validateProperty() and validateValue() in Three Minutes
總結
儘管控制反轉普遍存在於 Java 的生態系統中,特別是很多框架普遍採用了依賴注入,但對於多數開發者來說,這個模式仍然很模糊,對其應用也受限於依賴注入。在這篇文章中,我展示了幾種實際可用的實現 IoC 的方法,闡明了這一概念。
依賴注入:從客戶端獲得依賴關係的控制不再存在於這些類中。它存由底層的注入器 / DI 框架來處理。
觀察者模式:當主體發生變化時,控制從觀察者傳遞到主體。
模板方法模式:控制發生在定義模板方法的基類中,而不是實現演算法步驟的子類中。
像往常一樣,怎樣以及何時使用 IoC 是通過對每個用例的分析來決定的,不要為了 IoC 而 IoC。
※定時任務發展史
※為了擴大產品的影響力,這些手機廠商的營銷方式讓節操碎了一地
※小組討論谷歌機器翻譯Attention is All You Need
※自動駕駛技術創業公司 Drive.ai獲5000 萬美元 B 輪融資
※高手來了!8個簡單有效的方法幫你提升設計檔次
TAG:推酷 |
※大玩反轉設計的Air Max 1 尺碼標印在鞋舌的解構設計
※劇情再被反轉,NVIDIA第三季度量產Turing架構顯卡
※KICKSVISION 反轉Logo 四色短袖 正在熱賣中……
※外匯輕入門:運用RSI指標,把握住行情反轉的機會!
※中端機體驗反轉旗艦機,新iPhone Xl或搭載屏幕指紋
※反轉再反轉,戴森Airwrap美髮造型器上手玩
※HENRY,感性氛圍下的反轉魅力……「會用新專輯展現』原原本本的HENRY』」(GETITK)
※「電極反轉」的LED:為未來計算機提供新冷卻方案!
※反轉!AMS決定重新評估收購歐司朗的潛在交易
※兵團六師SCP違約後「神反轉」
※韓網友票選防彈JIMIN入坑瞬間,這種反轉魅力真的很難抵擋啊
※新iPhone手機造型大反轉:這回神似一加5T
※CPU打漏洞補丁性能下滑 Intel竟然禁止跑分!結局反轉
※BlackPink金智妮被嚇哭,別樣的反轉魅力
※反轉!抖音國際版「TikTok」印度禁令解除,承諾將加大內容審核力度
※我的V形反轉
※一個優雅的反轉數組的演算法
※兩個缺口的M.2固態硬碟性能不行?實測結局反轉
※扣碎配色反轉!Air Jordan 1 Mid 全新配色曝光
※OPPO FindZ命名敲定!真機實拍圖曝光:瀑布屏遭反轉