Java 帝國第一代國王正式登基,百官前來朝賀。
大臣甲說道:「恭喜陛下登基,為了吸引更多程式設計師加入我國,臣建議儘快完善我們Java語言的OOP特性,封裝、繼承、多態。」
國王說:「一個個來,先說說封裝吧, 我們現在已經可以把數據和方法放到一個類中,接下來想辦法隱藏信息,限制對他們的訪問了, 我聽說現在有不少人在使用C++, 能不能從它借鑑一下啊? 」
大臣乙對C++很有好感,他說:「陛下聖明,C++那裡有pubic, private,protected 等關鍵字,可以用於修飾屬性和方法,我們直接拿過來用得了。」
public class Person{
private String name;
private int age;
public String getName(){
return name;
}
public int getAge(){
return age;
}
}
「如此甚好!」 國王表示讚許,不過他眼珠一轉,突然想到了早些年出現的Python, 他問道:「Python是怎麼處理封裝這個問題的?」
大臣甲從Python王國「倒戈」而來,懷著對故國的歉意,十分想把Python的語法帶來一點給Java,聽到國王這麼問起,趕緊說到:「Python的處理比較簡單,用兩個下劃線來表示私有的屬性和方法。」
class Person:
def __init__(self, name):
self.name = name
# 私有屬性
self.__age = 10
# 私有方法
def __secret(self):
return self.__age
p = Person("andy")
#可以訪問
print(p.name)
#私有屬性,無法訪問
print(p.__age)
#私有方法,無法訪問
print(p.__secret())
可是國王卻說:「嗯,這種方式挺簡單的嘛,用下劃線就實現了,很簡潔,我們能不能也這樣啊?」
大臣乙有點瞧不起這個腳本語言,他趕緊說:「萬萬不可,陛下有所不知,這個Python啊,即使加了下劃線,也只是『偽私有』的屬性和方法。」
「什麼是偽私有?」
「就是說外界依然有方法訪問他們!」
#用這種方法,依然可以訪問偽私有屬性和方法
print(p._Person__age) # 10
print(p._Person__secret()) # 10
「這算哪門子私有的屬性和方法? 一點都不純粹。」 大臣乙繼續補刀。
國王說:「好吧,那不學它了, 那JavaScript呢?他是怎麼實現封裝的?」
朝中的大臣們面面相覷,JavaScript? 這是什麼鬼?怎麼沒有聽說過?
(碼農翻身註:JavaScript的出現時間比Java要晚, 這個Java國王估計是穿越了。)
大臣甲看到自己的想法沒有「得逞」,又另闢蹊徑:「陛下,Python有module的機制,可以把多個類組織到一起,形成一個高內聚的單元,我們Java要不也這麼幹?」
國王瞪了大臣甲一眼,訓斥道:「不要什麼都學Python! 我們也得有點獨特的東西啊。對於如何組織class, 我們可以用package,一個package對應文件系統的一個目錄,下面可以有多個class文件。 如果一個類沒有被public 修飾,那他只能被同一個package下面的類訪問,其他package的類是訪問不到的。這個設計不錯吧?!」
國王甚為得意。
同一個package下有三個類A,B,C, 只有class A能被外邊的包訪問到,可以充當這個包對外的「接口」 (註:不是java 的interface ), B,C只是包級可見的類, 相當於包內部的實現了,外界是無法new 出來, 防止了被外界誤用。
只要保證A不發生變化,就不會影響外界使用, B和C想怎麼改就怎麼改!
大臣甲小心地問道:「如果我只想把foo.core中的class B暴露給foo.cmd訪問,同時阻止別的包訪問class B,該怎麼辦呢? 」
「怎麼會有這麼『變態』的需求? 」 朝中各位大臣都表示不可思議。
國王沉吟道:「程式設計師的要求是無窮無盡的,例外總是會發生的, 這種需求是存在的, 容朕想想。」
熟悉C++的大臣乙趕緊上奏:「陛下,C++ 有個什麼friend class的概念。 例如在class Node 中聲明了 friend class LinkedList , 那LinkedList 就可以訪問Node 類的屬性和方法了。 」
大臣甲強烈反對這種做法:「不好不好,雖然看起來給程式設計師提供了方便,但是給封裝性撕開了一個大口子,如果被濫用,後果不堪設想。」
國王表示同意:「對,還是放棄這種想法吧,保持簡單性最重要。 如果他實在想訪問class B,可以採用兩種辦法:(1) 把 class B 變成 public (2) 通過接口class A來進行代理。」
鬥轉星移,轉眼間Java國王已經傳到了第9世。
這一天,鄰國的Python, JavaScript派使者來訪,受到了國王的熱情招待,席間談到了java package的存在的問題。
Java package的方式雖然不錯,可是還是有很大的弊端,其中最大的弊端就是很多包中的類都是public的, 這就造成了這樣一種情況。
本來是想讓org.foo.api對外提供接口,讓Client去調用的,但實際上,只要foo.jar放到classpath中,另外兩個package , org.foo.impl, org.foo.core中的類也就暴漏了。
JavaScript使者說道:「奧, 我原來以為貴國的一個jar文件就是一個可復用的模塊, 現在看來還是遠遠不夠啊!」
「怪不得大家都說,你的一個jar文件就是class的壓縮包, classpath就是把這些類給平鋪而已。」 Python使者笑道。
Java國王心中有點生氣,但是臉上沒有表露出來:「貴國是怎麼實現的啊?」
Python使者想了想, 自家的module好像也差不多,並且只能靠約定(給變量和方法的前面添加下劃線)的方式來實現private , 由於是約定的,外界依然可以訪問。
JavaScript想到自己連module, package都沒有,趕緊噤聲。
Java 國王說:「簡單的jar文件缺乏一個重要的特性:隱藏內部實現, 寡人打算做一個重要的改變,定義真正的模塊!」
「看到沒有? 我打算用一個文件module-info.java來定義一個模塊中有哪些包是可以被export的, 只有那些export的包才能被Client所調用, 其他的包對Client都是不可見的。 」
看到這個設計方案,各個大臣都覺得不錯。 有了模塊, 就真正地定義了對外可以訪問的接口,除了接口的那個package之外,其他的package是不可訪問的, 徹底實現了封裝。
Python使者盯著這個圖看了一會兒,說道:「不對吧,假設有這樣的代碼:」
FooService service = new FooServiceImpl();
「其中FooService 是org.foo.api包下面的類, FooServiceImpl是org.foo.impl下面的類, 按照你模塊化的要求,這個FooServiceImpl是不能被Client所訪問的, 那怎麼才能創建FooService呢?」
Java國王心想這Python使者對我Java語言挺熟悉的啊,搞得我下不來臺。
「陛下,臣以為可以用工廠模式解決!」 終於有大臣前來救駕。「創建一個新的類FooServiceFactory,把它放到org.foo.api包下,可以公開調用,這樣不就行了?」
public class FooServiceFactory{
public static FooService getFooService(){
return new FooServiceImpl();
}
}
Python使者卻繼續施壓: 「不過讓人不爽的是, 這個FooServiceFactory雖然屬於api包,但是需要知道impl包的具體實現。 如果想添加一個FooService的實現,還得修改它。還是不妥啊!」
突然,Java國王拍了一下腦袋,對了,我怎麼把ServiceLoader給忘記了呢?
我可以把原來的模塊分成兩個模塊,org.foo.api表示接口, org.foo.provider表示實現。
在org.foo.provider中特別聲明,本模塊可以提供FooService的實現!
provides org.foo.api.FooService with org.foo.provider.FooServiceImpl
Python使者還是不太明白: 「那客戶端怎麼使用呢?」
「簡單,Client 代碼可以這麼寫:」
Iterable<FooService> iter =
ServiceLoader.load(FooService.class);
遍歷iter,獲取FooService並且使用。
這樣在運行時,Client的代碼就可以使用ServiceLoader就可以找到這些具體的實現了, 當然實現可能不僅僅只有一個,Client選擇一個就可以了。
當然,JDK必須得實現這個ServiceLoader,去獲取這些具體的實現。
「這個方案,即不破環封裝性,又提供了足夠的靈活性,相當於在運行時裝配對象,陛下聖明!」 大臣們紛紛拍馬屁。
Python使者見狀,也就不再發言,開始低頭喝酒。
JavaScript 使者半天都沒有開口了,他心裡一直在琢磨,我是不是有點落伍了? Python有模塊,Ruby也有模塊,這Java的模塊化更是搞得如火如荼。模塊化極大地提升了封裝性,如果想進行大型項目的開發這模塊化是少不了的, 想想自家那凌亂不堪的js文件, 是時候做出改變了.
本文轉自公眾號:碼農翻身