天天看點

Java 反射最終篇 - Mock 對象和樁(上)接口和類型

Mock 對象和 **樁(Stub)**在邏輯上都是 Optional 的變體。他們都是最終程式中所使用的“實際”對象的代理。

不過,Mock 對象和樁都是假扮成那些可以傳遞實際資訊的實際對象,而不是像 Optional 那樣把包含潛在 null 值的對象隐藏。

Mock 對象和樁之間的的差别在于程度不同。

Mock 對象往往是輕量級的,且用于自測試。通常,為了處理各種不同的測試場景,我們會建立出很多 Mock 對象。

樁隻是傳回樁資料,通常是重量級的,在多個測試中被複用。可以根據它們被調用的方式,通過配置進行修改。是以,樁是一種複雜對象,可以做很多事情。

至于 Mock 對象,如果你要做很多事,通常會建立大量又小又簡單的 Mock 對象。

接口和類型

interface

關鍵字的一個重要目标就是允許程式員隔離元件,進而降低耦合度。使用接口可以實作這一目标,但是通過類型資訊,這種耦合性還是會傳播出去——接口并不是對解耦的一種無懈可擊的保障。

比如我們先寫一個接口:

Java 反射最終篇 - Mock 對象和樁(上)接口和類型

實作這個接口

Java 反射最終篇 - Mock 對象和樁(上)接口和類型

通過使用 RTTI,我們發現 a 是用 B 實作的。通過将其轉型為 B,我們可以調用不在 A 中的方法。

這樣的操作完全是合情合理的,但是你也許并不想讓用戶端開發者這麼做,因為這給了他們一個機會,使得他們的代碼與你的代碼的耦合度超過了你的預期。

你可能認為 interface 關鍵字正在保護你,但其實并沒有。另外,在本例中使用 B 來實作 A 這種情況是有公開案例可查的。

一種解決方案是直接聲明,如果開發者決定使用實際的類而不是接口,他們需要自己對自己負責。這在很多情況下都是可行的,但“可能”還不夠,你或許希望能有一些更嚴格的控制方式。

最簡單的方式是讓實作類隻具有包通路權限,這樣在包外部的用戶端就看不到它了:

Java 反射最終篇 - Mock 對象和樁(上)接口和類型

在包中唯一 public 的部分就是 HiddenC,在被調用時将産生 A接口類型的對象

即使你從 makeA() 傳回的是 C 類型,你在包的外部仍舊不能使用 A 之外的任何方法,因為你不能在包的外部命名 C。

現在如果你試着将其向下轉型為 C,則将被禁止,因為在包的外部沒有任何 C 類型可用:

Java 反射最終篇 - Mock 對象和樁(上)接口和類型

通過使用反射,仍然可以調用所有方法,甚至是 private 方法!如果知道方法名,你就可以在其 0Method 對象上調用 setAccessible(true),就像在 callHiddenMethod() 中看到的那樣。

你可能覺得,可以通過隻釋出編譯後的代碼來阻止這種情況,但其實這并不能解決問題。因為隻需要運作 javap(一個随 JDK 釋出的反編譯器)即可突破這一限制。

使用 javap

javap -private C      

-private

 标志表示所有的成員都應該顯示,甚至包括私有成員。下面是輸出:

class C extends
java.lang.Object implements typeinfo.interfacea.A {
  typeinfo.packageaccess.C();
  public void f();
  public void g();
  void u();
  protected void v();
  private void w();
}      

是以,任何人都可以擷取你最私有的方法的名字和簽名,然後調用它們。