第一部分 淵源——.net與面向對象
第一章OO大智慧
1.1對象的旅行
1引言
對象與人類世界類比
2出生
new 分配內存
3旅程
CLR託管環境
在一定的約定和規則下,通過方法進行彼此的交互,從而改變本身狀態。
4插曲
訪問修飾符
繼承
多態:接口實現,抽象類實現
5消亡
GC控制
6總結
類比,演化推進
第二章對象創建始末
對象的創建過程,內存分配分析,內存佈局研究
1引言
new 內存分配和初始化
2內存分配
CLR內存區域包括
線程的堆棧:分配值類型實例,主要由操作系統管理,效率高,容量有限。
GC堆:用於分配小對象實例,對象實例小於85000字節
LOH堆:用於分配大對象實例。
IL指令解析
newobj
ldstr
newarr
box
2.1堆棧的內存分配機制
堆棧指針,地址由高到低;堆是由低到高
2.2託管堆的內存分配機制
託管堆是進程可用地址空間中的一塊內存區域
託管堆也分爲多個區域:垃圾回收堆(GC Heap),加載堆(Loader Heap)等。
GCHeap存儲對象實例,受GC管理;
LoaderHeap存儲元數據相關信息,就是Type對象,每個Type體現爲一個MethodTable,MethodTable中記錄元數據信息;不受GC控制。
TypeHandle:類型句柄,指向對應實例的方法表,對象創建時包含,佔4字節。
SyncBlockIndex:用於線程同步,對象創建時包含,佔4字節,指向一塊SynchonizationBlock的內存塊,用於管理同步對象。
NextObjPtr:託管堆維護的一個指針,下一個對象分配位置。
引用類型分配過程:
在堆棧分配引用指針,佔4字節;
計算類型及父類字節總數,還要加上附加成員字節數8字節,內存按4字節的倍數進行分配;
搜索連續空間,GC只是把NextObjPtr向前推進相應字節,並清空當前範圍內字節;
構造類型Type對象,存儲在LoaderHeap;
初始化附件成員TypeHandle,指向LoaderHeap的MethodTable;SyncBlockIndex指向同步內存塊;
通過構造器,進行實例字段的初始化;
實例字段存儲順序,父類在前,子類在後;
把託管堆地址賦值給棧上的引用指針變量;
2.3必要的補充
值類型嵌套引用類型,堆棧保存成員引用,引用類型分配在GC堆上;引用類型嵌套值類型,值類型作爲實例分配在GC堆上。
MethodTable中的方法調用過程?通過TypeHandle找到MethodTable對應方法,再通過JIT Compiler編譯爲本地CPU指令,保存在動態內存中,然後在該內存地址執行。
靜態字段內存分配:內存中只有一份,位於方法表的槽數組後,只能由靜態構造函數初始化。
3結論
第3章繼承
3.1什麼是繼承
繼承,繼承的實現本質,繼承的分類與規則,繼承與聚合,繼承的侷限
1引言
熟悉而又容易產生誤解的話題
2基礎爲上
繼承關係圖
繼承,就是面向對象中類與類之間的一種關係。繼承的類稱爲子類、派生類,而被繼承類稱爲父類、基類或超類。通過繼承,使得子類具有父類的屬性和方法,同時子類也可以通過加入新的屬性和方法或者修改父類的屬性和方法建立新的類層次。
繼承機制體現了面向對象技術中的複用性、擴展性和安全性。
分爲實現繼承和接口繼承。
這種在子類中實現虛函數的方式,稱爲方法的動態綁定,是實現面向對象另一特性:多態的基本機制。
通過繼承我們輕而易舉地實現了代碼的複用和擴展,同時通過重載(overload)、覆寫(override)、接口實現等方式實現了封裝變化,隱藏私有信息等面向對象的基本規則。
3繼承本質論
— 繼承是可傳遞的,子類是對父類的擴展,必須繼承父類方法,同時可以添加新方法。
— 子類可以調用父類方法和字段,而父類不能調用子類方法和字段。
— 虛方法如何實現覆寫操作,使得父類指針可以指向子類對象成員。
— 子類不光繼承父類的公有成員,同時繼承了父類的私有成員,只是在子類中不被訪問。
— new 關鍵字在虛方法繼承中的阻斷作用。
用子類創建父類引用實例,調用方法原則:關注對象原則;執行就近原則。
4密境追蹤
4.1實現繼承與接口繼承
— 抽象類適合於有族層概念的類間關係,而接口最適合爲不同的類提供通用功能。
— 接口着重於 CAN-DO 關係類型,而抽象類則偏重於 IS-A 式的關係。
— 接口多定義對象的行爲;抽象類多定義對象的屬性。
— 如果預計會出現版本問題,可以創建“抽象類”。
— 因爲值類型是密封的,所以只能實現接口,而不能繼承類。
4.2聚合還是繼承?
聚合分爲三種類型,依次爲無、共享和複合,其耦合度逐級遞增。無聚合類型關係,類的雙方彼此不受影響;共享型關係,Class2 不需要對 Class1 負責;而複合型關係,Class1 會受控於 Class2 的更改,因此耦合度更高。
聚合關係是一種 HAS-A 式的關係,耦合度沒有繼承關係高。
依賴關係表明,如果 Class2 被修改,則 Class1 會受到影響。
類與類之間的關係,通常以耦合度來描述,也就是表示類與類之間的依賴關係程度。沒有耦合關係的系統是根本不存在的,因爲類與類、模塊與模塊、系統與系統之間或多或少要發生相互交互,設計應力求將類與類之間的耦合關係降到最低。
而面向對象的基本原則之一就是實現低耦合、高內聚的耦合關係。
繼承之毒瘤主要體現在:
— 繼承可能造成子類的無限膨脹,不利於類體系的維護和安全。
— 繼承的子類對象確定於編譯期,無法滿足需要運行期才確定的情況,而類聚合很好地解決了這一問題。
— 隨着繼承層次的複雜化和子類的多樣化,不可避免地會出現對父類的無效繼承或者有害繼承。子類部分的繼承父類的方法或者屬性,更能適應實際的設計需求。
面向對象的基本原則
多聚合,少繼承。
低耦合,高內聚。
Adapter 模式主要用於將一個類的接口轉換爲另外一個接口,通常情況下在改變原有體系的條件下應對新的需求變化,通過引入新的適配器類來完成對既存體系的擴展和改造。Adapter 模式就其實現方式主要包括:
— 類的 Adapter 模式。通過引入新的類型來繼承原有類型,同時實現新加入的接口方法。其缺點是耦合度高,需要引入過多的新類型。
— 對象的 Adapter 模式。通過聚合而非繼承的方式來實現對原有系統的擴展,鬆散耦合,較少的新類型。(新建一個Adapter對象,實現新接口,對象中加入原類的所有同名方法,根據引用父類參數調用實例子類的具體實現)
5規則制勝
— 密封類不可以被繼承。
— 繼承關係中,我們更多的是關注其共性而不是特性,因爲共性是層次複用的基礎,而特性是系統擴展的基點。
— 實現單繼承,接口多繼承。
— 從宏觀來看,繼承多關注於共通性;而多態多着眼於差異性。
— 繼承的層次應該有所控制,否則類型之間的關係維護會消耗更多的精力。
— 面向對象原則:多組合,少繼承;低耦合,高內聚。
6結論
所有的類型都最終繼承自共同的根 System.Object 類。繼承是.NET 運行機制的基礎技術之一。理解繼承、關注封裝、品味多態、玩轉接口是理解面向對象的起點。
第4章封裝的祕密
1引言
面向對象的封裝特性,字段賞析,屬性賞析
在面向對象三要素中,封裝特性爲程序設計提供了系統與系統、模塊與模塊、類與類之間交互的實現手段。封裝爲軟件設計與開發帶來前所未有的革命,成爲構成面向對象技術最爲重要的基礎之一。
2什麼是封裝?ATM
隱藏內部實現,提供對外接口。
3祕密何處:字段、屬性和方法
— 類的功能是什麼?
— 哪些是字段,哪些是屬性,哪些是方法?
— 對外提供的公有方法有哪些,對內隱藏的私有變量有哪些?
— 類與類之間的關係是繼承還是聚合?
字段(field)通常定義爲 private,表示類的狀態信息。,只讀字段只能在構造函數中被賦值。
對字段公有化的操作,會引起對數據安全性與可靠性的破壞,封裝的第一個原則就是:將字段定義爲 private。
屬性(property)通常定義爲 public,表示類的對外成員。
屬性的實質其實就是在編譯時分別將 get 和 set 訪問器實現爲對外方法。
方法(method)封裝了類的行爲,提供了類的對外表現。在封裝原則中,有效地保護內部數據和有效地暴露外部行爲一樣關鍵。
誰爲公有、誰爲私有,取決於需求和設計雙重因素,在職責單一原則下爲類型設計方法,應該廣泛考慮的是類本身的功能性,從開發者與設計者兩個角度出發,分清訪問權限就會水到渠成。
4封裝的意義
(1)字段通常定義爲 private,屬性通常實現爲 public,而方法在內部實現爲 private,對外部實現爲 public,從而保證對內部數據的可靠性讀寫控制,保護了數據的安全和可靠,同時又提供了與外部接口的有效交互。這是類得以有效封裝的基礎機制。
(2)通常情況下的理解正如我們上面提到的規則,但是具體的操作還要根據實際的設計需求而定,例如有些時候將屬性實現爲 private,也將方法實現爲 private 是更好的選擇。
(3)從內存和數據持久性角度上來看,有一個很重要但常常被忽視的事實是,封裝屬性提供了數據持久化的有效手段。因爲,對象的屬性和對象一樣在內存期間是常駐的。
(4)在面向對象中,封裝的意義還遠不止類設計層面對字段、屬性和方法的控制,更重要的是其廣義層面。我們理解的封裝,應該是以實現 UI 分離爲目的的軟件設計方法,一個系統或者軟件開發之後,從維護和升級的目的考慮,一定要保證對外接口部分的絕對穩定。不管系統內部的功能性實現如何多變,保證接口穩定是保證軟件兼容、穩定、健壯的根本。
— 隱藏系統實現的細節,保證系統的安全性和可靠性。
— 提供穩定不變的對外接口。因此,系統中相對穩定部分常被抽象爲接口。
— 封裝保證了代碼模塊化,提高了軟件的複用和功能分離。
5封裝規則
— 儘可能地調用類的訪問器,而不是成員,即使在類的內部。
— 內部私有部分可以任意更改,但是一定要在保證外部接口穩定的前提下。
— 將對字段的讀寫控制實現爲屬性,而不是方法,否則舍近而求遠,非明智之選。
— 類封裝是由訪問權限來保證的,對內實現爲 private,對外實現爲 public。
— 封裝的精華是封裝變化。開發者應從設計角度和使用角度兩方面來分析封裝。我們將系統中變化頻繁的部分封裝爲獨立的部分,這種隔離選擇有利於充分的軟件複用和系統柔性。
6總結
包裝的內外分爲兩個空間,對內實現數據私有,對外實現方法調用,保證了數據的完整性和安全性。
第5章多態的藝術
多態,動態綁定,面向對象
1引言
多態:可以呈現不同形式的能力或狀態。
而在.NET 中,多態指同一操作作用於不同的實例,產生不同運行結果的機制。
2問題的拋出
萬能加載器
3最初的實現
面向結構和過程的實現
4多態-救命的稻草
對象的開放封閉原則:對擴展開放,對修改關閉。
5隨需而變的業務
只要有更合理的設計與架構,在基於面向對象和.NET 框架的基礎上,完全可以實現類似於插件的可擴展系統,並且無需編譯即可更新擴展。
6多態的類型、本質和規則
6.1分類
多態分爲四類:強制的、重載的、參數的和包含的。
1)基類繼承式多態
IS-A
2)接口實現式多態
CAN-DO
6.2多態的運行機制
是.NET 的動態綁定機制成就了面向對象的多態特性。一般以繼承和虛方法來實現。
6.3多態的規則和意義
例必須顯式地通過 virtual 或者 abstract 標記爲虛方法或者抽象方法
此繼承和重載是多態的實現基礎
第6章接口
接口,接口映射本質,面向接口編程,.net的接口
1引言
接口,是面向對象設計中的重要元素,也是打開設計模式精要之門的鑰匙。
2什麼是接口
所謂接口,就是契約,用於規定一種規則由大家遵守。
接口是一組行爲規範。
實現接口還意味着,同樣的方法對不同的對象表現爲不同的行爲。
3.Net中的接口
1)接口多繼承
2)接口的本質
接口本質上仍然被標記爲.class,同時提供了 abstract virtual 方法 MyMethod,因此接口其實本質上可以看作是一個定義了抽象方法的類,該類僅提供了方法的定義,而沒有方法的實現,其功能由接口的實現類來完成。
接口在本質上,仍舊是一個不能實例化的類,但是又區別於一般意義上的類,例如不能實例化、允許多繼承、可以作用於值類型等。
3)由string想到的,框架類庫的典型接口
4面向接口的編程
表示對接口編程而不要對實現編程,更通俗的說法是對抽象編程而不要對具體編程。
按照接口隔離原則,接口應該被實現爲具有單一功能的多個小接口,而不是具有多個功能的大接口。
5接口之規則
— 接口隔離原則強調接口應該被實現爲具有單一功能的小接口,而不要實現爲具有多個功能的胖接口,類對於類的依賴應建立在最小的接口之上。
— 接口支持多繼承,既可以作用於值類型,也可以作用於引用類型。
— 禁止爲已經發布的接口,添加新的成員,這意味着你必須重新修改所有實現了該接口的類型,在實際的應用中,這往往是不可能完成的事情。
— 接口不能被實例化,沒有構造函數,接口成員被隱式聲明爲 public。
— 接口可以作用於值類型和引用類型,並且支持多繼承。