Mock 對象和 **樁(Stub)**在邏輯上都是 Optional
的變體。他們都是最終程序中所使用的“實際”對象的代理。
不過,Mock 對象和樁都是假扮成那些可以傳遞實際信息的實際對象,而不是像 Optional
那樣把包含潛在 null
值的對象隱藏。
Mock 對象和樁之間的的差別在於程度不同。
- Mock 對象往往是輕量級的,且用於自測試。通常,爲了處理各種不同的測試場景,我們會創建出很多 Mock 對象。
- 樁只是返回樁數據,通常是重量級的,在多個測試中被複用。可以根據它們被調用的方式,通過配置進行修改。因此,樁是一種複雜對象,可以做很多事情。
至於 Mock 對象,如果你要做很多事,通常會創建大量又小又簡單的 Mock 對象。
接口和類型
interface
關鍵字的一個重要目標就是允許程序員隔離組件,進而降低耦合度。使用接口可以實現這一目標,但是通過類型信息,這種耦合性還是會傳播出去——接口並不是對解耦的一種無懈可擊的保障。
比如我們先寫一個接口:
實現這個接口
通過使用 RTTI,我們發現 a
是用 B
實現的。通過將其轉型爲 B
,我們可以調用不在 A
中的方法。
這樣的操作完全是合情合理的,但是你也許並不想讓客戶端開發者這麼做,因爲這給了他們一個機會,使得他們的代碼與你的代碼的耦合度超過了你的預期。
你可能認爲 interface
關鍵字正在保護你,但其實並沒有。另外,在本例中使用 B
來實現 A
這種情況是有公開案例可查的。
一種解決方案是直接聲明,如果開發者決定使用實際的類而不是接口,他們需要自己對自己負責。這在很多情況下都是可行的,但“可能”還不夠,你或許希望能有一些更嚴格的控制方式。
最簡單的方式是讓實現類只具有包訪問權限,這樣在包外部的客戶端就看不到它了:
在包中唯一 public
的部分就是 HiddenC
,在被調用時將產生 A
接口類型的對象
即使你從 makeA()
返回的是 C
類型,你在包的外部仍舊不能使用 A
之外的任何方法,因爲你不能在包的外部命名 C
。
現在如果你試着將其向下轉型爲 C
,則將被禁止,因爲在包的外部沒有任何 C
類型可用:
通過使用反射,仍然可以調用所有方法,甚至是 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();
}
因此,任何人都可以獲取你最私有的方法的名字和簽名,然後調用它們。
那如果把接口實現爲一個私有內部類,又會怎麼樣呢?下面展示了這種情況:
public C.f()
InnerA$C
public C.g()
package C.u()
protected C.v()
private C.w()
這裏對反射仍然沒有任何東西可以隱藏。那麼如果是匿名類呢?
輸出結果:
public C.f()
AnonymousA$1
public C.g()
package C.u()
protected C.v()
private C.w()
看起來任何方式都沒法阻止反射調用那些非公共訪問權限的方法。對於字段來說也是這樣,即便是 private
字段:
輸出結果:
i = 1, I'm totally safe, Am I safe?
f.getInt(pf): 1
i = 47, I'm totally safe, Am I safe?
f.get(pf): I'm totally safe
i = 47, I'm totally safe, Am I safe?
f.get(pf): Am I safe?
i = 47, I'm totally safe, No, you're not!
但實際上 final
字段在被修改時是安全的。運行時系統會在不拋出異常的情況下接受任何修改的嘗試,但是實際上不會發生任何修改。
通常,所有這些違反訪問權限的操作並不是什麼十惡不赦的。如果有人使用這樣的技術去調用標誌爲 private
或包訪問權限的方法(很明顯這些訪問權限表示這些人不應該調用它們),那麼對他們來說,如果你修改了這些方法的某些地方,他們不應該抱怨。
另一方面,總是在類中留下後門,也許會幫助你解決某些特定類型的問題(這些問題往往除此之外,別無它法)。
總之,不可否認,反射給我們帶來了很多好處。
程序員往往對編程語言提供的訪問控制過於自信,甚至認爲 Java 在安全性上比其它提供了(明顯)更寬鬆的訪問控制的語言要優越。然而,正如你所看到的,事實並不是這樣。