歡迎光臨
每天分享高質量文章

Java國王:我來告訴你什麼才是真正的封裝!

來自:碼農翻身(微訊號:coderising)


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是不可訪問的, 徹底實現了封裝。

ServiceLoader

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 iter = 
    ServiceLoader.load(FooService.class);

遍歷iter,獲取FooService並且使用。

這樣在執行時,Client的程式碼就可以使用ServiceLoader就可以找到這些具體的實現了, 當然實現可能不僅僅只有一個,Client選擇一個就可以了。


當然,JDK必須得實現這個ServiceLoader,去獲取這些具體的實現。

“這個方案,即不破環封裝性,又提供了足夠的靈活性,相當於在執行時裝配物件,陛下聖明!”  大臣們紛紛拍馬屁。

Python使者見狀,也就不再發言,開始低頭喝酒。

JavaScript 使者半天都沒有開口了,他心裡一直在琢磨,我是不是有點落伍了? Python有模組,Ruby也有模組,這Java的模組化更是搞得如火如荼。模組化極大地提升了封裝性,如果想進行大型專案的開發這模組化是少不了的, 想想自家那凌亂不堪的js檔案, 是時候做出改變了……


(完)


●編號779,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

演演算法與資料結構

更多推薦18個技術類公眾微信

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂