首頁  >  文章  >  Java  >  Java如何利用IOC控制反轉的三種設計模式詳解

Java如何利用IOC控制反轉的三種設計模式詳解

黄舟
黄舟原創
2017-10-18 10:16:561531瀏覽

這篇文章主要為大家詳細介紹了Java使用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<User> 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<SubjectObserver> 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方法变更其状态事,都会触发调用绑定到它的观察者。

使用主题观察者和主题,以下是实例给出了观察方式:


public static void main(String[] args) {
  User user = new User("John");
  user.addObserver(() -> System.out.println(
      "Observable subject " + user + " has changed its state."));
  user.setName("Jack");
}

每当User对象的状态通过setter方法进行修改时,观察者将被通知并向控制台打印出一条消息。到目前为止,给出了观察者模式的一个简单用例。不过,通过这个看似简单的用例,我们了解到在这种情况下控制是如何实现反转的。

观察者模式下,主题就是起到”框架层“的作用,它完全主导何时何地去触发谁的调用。观察者的主动权被外放,因为观察者无法主导自己何时被调用(只要它们已经被注册到某个主题中的话)。这意味着,实际上我们可以发现控制被反转的”事发地“ – - – 当观察者绑定到主题时:


user.addObserver(() -> System.out.println(
      "Observable subject " + user + " has changed its state."));

上述用例,简要说明了为什么,观察者模式(或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 的典型例子,透過分層結構來實現。在這種情況下,模板方法只是可調的擴展點的一個漂亮的名字,被開發者用來管理自己的一系列實現。

總結

儘管控制反轉普遍存在於Java 的生態系統中,特別是許多框架普遍採用了依賴注入,但對於多數開發者來說,這個模式仍然很模糊,對其應用也受限於依賴注入。在這篇文章中,我展示了幾種實際可用的實作 IoC 的方法,闡明了這個概念。

依賴注入:從客戶端獲得依賴關係的控制不再存在於這些類別中。它存由底層的注入器 / DI 框架來處理。
觀察者模式:當主體改變時,控制權從觀察者傳遞到主體。
模板方法模式:控制發生在定義模板方法的基底類別中,而不是實現演算法步驟的子類別中。
像往常一樣,怎麼樣以及何時使用 IoC 是透過對每個用例的分析來決定的,不要為了 IoC 而 IoC。

以上是Java如何利用IOC控制反轉的三種設計模式詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn