1. 程式人生 > >Java程式設計思想:第一章:物件導論

Java程式設計思想:第一章:物件導論

第一章:物件導論

    我們之所以將自然界分解,組織成各種概念,並按其含義分類,主要是因為我們是整個口語交流社會共同遵守的協議的參與者,這個協定以語言的形式固定下來...除非贊成這個協定中規定的有關語言資訊的組織和分類,否則我們根本無法交流。


抽象過程

    所為的程式語言都提供抽象機制。可以認為:人們所能夠解決的問題的複雜性直接取決於抽象的型別和質量。所謂的“型別”指“所抽象的是什麼?”,比如組合語言是對底層機器的輕微的抽象。接著出現的命令式語言就是組合語言的抽象。

    面向物件方式通過向程式設計師提供標示問題空間中的元素的工具而進了一步。這種標示方式非常通用,使得程式設計師不會受限於任何特定型別的問題。我們將問題空間中的元素及其在解空間中的標示稱為

物件。這種思想的實質是:程式可以通過新增新型別的物件使自身適應於某種特定的問題,因此,當你在閱讀描述解決方案的程式碼的同時,也是在閱讀問題的描述。所以,OOP允許根據問題來描述問題,而不是根據執行解決方案計算機來描述問題。但是仍然與計算機有關聯:每個物件都看起來有點像一臺微型計算機:具有狀態,還有操作,使用者可以要求物件執行這些操作。

    面向物件的5個基本特性:

    • 萬物皆為物件,將物件視為奇特的變數,它可以儲存資料,還可以要求它自身上執行一些操作。理論上講,你可以抽取待求解問題的任何概念化結構(狗)。將其表示為程式中的物件。

    • 程式是物件的集合,它們通過傳送訊息來告知彼此所要做的。

    • 每個物件都有自己的由其他物件所構成的儲存。可以通過建立包含現有物件的包的方式來建立新型別的物件。

    • 每個物件都擁有其型別。每個物件都是某個類的例項,類就是型別的同義詞。

    • 某一個特定型別的所有物件都可以接收相同的訊息。比如子類能處理的訊息,父類也能處理,面向介面程式設計的概念。

    物件具有狀態,行為和標識。意味著每一個物件都可以擁有內部資料,方法,並且每一個物件都可以唯一的與其他物件區分開來,具體來說,每一個物件在記憶體中有唯一的的地址。

每一個物件都有一個介面

    在程式執行期間具有不同的狀態而其他方面都相似的物件會被分組到物件的類中,這就是關鍵字Class的由來。建立抽象資料型別(類)是面向物件程式設計的基本概念之一。抽象資料型別的執行方式與內建(built-in)型別幾乎完全一致。你可以建立某一型別的變數,然後操作這些變數。每個類的成員或元素都具備某種共性。同時,每一個成員都有自己的狀態,每一個物件都屬於定義了特性和行為的某個特定的類。

    面向物件用class來表示資料型別。因為類描述了具有相同特性(資料元素)和行為(功能)的物件集合,所以一個類實際上就是一個數據型別。

    一擔類被建立,就可以隨心所欲的建立類的任意個物件,然後去操作它們。事實上,面嚮物件語言中的挑戰之一,就是在問題空間元素和解空間的物件之間一對一的對映。每個物件只能滿足某些請求,這些請求由物件的介面(interface)所定義,決定介面的便是型別。介面確定了對某一特定物件所能發出的請求。但是,在程式中必須滿足這些請求的程式碼,這些程式碼月隱藏的資料在一起構成了實現。通常為概括為,向某個物件傳送請求,此物件便知道次請求的目的,然後執行對應的程式程式碼。


每個物件都提供服務

    物件就是服務提供者。提供的服務儘量單一職責哦。


被隱藏的具體實現

    將程式開發人員按角色分:

    • 類建立者

    • 客戶端程式設計師

    類中某些部分需要隱藏,這樣的隱藏部分是程式中脆弱的部分,這樣類建立者可以隨意的修改這些隱藏的部分。也避免客戶端程式設計師直接呼叫這類隱藏部分,這對客戶端的bug也大大的減少。

    在任何的互動環境中,就有關係所涉及的各方多遵守的邊界是十分重要的。當建立一個類庫時,就建立了與客戶端程式之間的關係,他們同樣也是程式設計師,但是他們是使用你的類庫來構建應用,或者構建更大的類庫的程式設計師。如果所有的類成員都任何人都可用的,那麼客戶端程式設計師對類庫做任何事情,而不受任何約束。

    因此,訪問控制存在原因是:

    • 讓客戶端程式設計師無法接觸及他們不應該接觸的部分:這些部分對資料型別的內部操作來說必須的,但並不是使用者解決特定問題所需要的介面的一部分。這對客戶端程式設計師來說是一種服務,因為他們很容易的看出那些東西對他們來說很重要,而那些東西可以忽略。

    • 允許庫設計者可以改變類內部的工作方式而不用擔心影響到客戶端程式設計師。


複用具體實現

    產生一個可複用的物件需要豐富的經驗和觀察力的,但是你一旦有了這樣的設計,它就可以複用。程式碼複用時面向物件提供的最了不起的優點之一。

    最簡單的複用某個類的方式就是直接使用該類的一個物件,因此也而已將那個類的一個物件置於某個新的類中。比如:建立一個成員物件。使用現有的類合成新的類稱為:組合(composition)。如果組合是動態發生的,那麼它通常稱為聚合(aggregation)。組合通常被視為擁有(has-is)關係,比如:汽車擁有引擎。

    組合帶來極大的靈活性。新類的成員物件通常都被宣告為private,使得使用新類的客戶端程式設計師不能訪問他們。開發時優先考慮組合,而不是繼承。


繼承

    現有的類為基礎,複製它,然後通過新增和修改這個副本來建立新類就稱為:繼承。當源類發生改變時,被修改的副本(子類)也會反映出這些變動。

    型別不僅僅與描述了作用於一個物件集合上的約束條件,同時還有與其他型別之間的關係。兩個型別可以有相同特性和行為,但是其中一個特性可能比另一個含有更多的特性,而且可以處理更多的訊息。繼承使用基型別和子型別的概念表示了這種型別之間的相似性。一個基型別包含其所有子型別所共享的特性和行為。可以建立基型別來表示系統中某些物件的核心概念,子型別來表示實現的各種不同方式。

    當繼承現有的型別時,也就是創造了新的型別。這個新的型別不僅包含現有型別的所有成員,而且更重要的就是它複製了基類的介面。也就是說,所以可以發給基類物件的訊息同時也可以傳送給子類物件。由於通過傳送給類的訊息的型別可知類的型別,意味著子類與基類具有相同的型別。改變基類的方法的行為稱:覆蓋。


是“一個”和“像一個”的關係

    子類物件來替代基類物件,稱為:純碎替代。通常稱為替代原則。在某種意義上,這是處理繼承的理想方法。我們經常將這種情況下的基類與子類的關係稱為:is-a是一個關係。判斷是否繼承,就是要確定是否可以使用is-a來描述類之間的關係。

    有些子類需要擴充套件基類,這個新的型別也可以替代基類,但是這種替代並不完美,因為基類無法訪問子類的新的方法。這種稱為像一個(is-liek-a)關係。子類有新的方法,所以說子類和基類不是完全相同。

    當你看到替代原則時,很容易會認為純碎替代是唯一可行的方式,而且事實上,用這種方式設計是很好的。但是你會時常發現,同樣顯然的是你必須在子類的介面中新增新方法,兩種方法的使用場景應該是相當明顯的。


伴隨多型的可互換物件

    在處理型別的層次結構時,經常想把一個物件不當做它所屬的特定型別來對待,而是將其當做其基類的物件來對待。使得可以寫出不依賴於特定型別的程式碼。這樣的程式碼是不會受新增新型別影響的,而且新增新型別是擴充套件一個面向物件程式一遍處理新情況的最常用方式。

    但是,子類物件看待基類物件時,仍然存在一些問題。編譯器在編譯時不可能知道執行那一個程式碼的。這就是關鍵所在,當傳送這樣的訊息時,程式設計師並不想知道那一段程式碼被執行,如果不需要知道那一段程式碼被執行,那麼新增新型別時,不需要改變呼叫它的方法,它就能執行不同的程式碼。

    編譯器不可能產生傳統意義上的函式呼叫,在OOP中,程式執行時才能夠確定程式碼的地址。所以,當訊息傳送到一個泛化的物件時,必須採用其他的機制。這既是後期繫結。Java使用一小段特殊的程式碼來替代絕對地址呼叫。這段程式碼使用在物件中儲存資訊來計算方法體的地址。這樣,根據這一段程式碼的內容,每一個物件都可以具有不同的行為表現。當向一個物件傳送訊息時,該物件就能夠知道這條訊息應該做些什麼。

    將子類看做是它的基類的過程稱為:向上轉型。


單根繼承結構

    Java中所有的類最終都繼承自單一的基類Object,如下好處:

    • 所有的物件都具有一個公共的介面,所以它們歸根到底都是相同的基類型別。

    • 單根繼承結構保證所有物件都具有某些功能。

    • 單根繼承讓垃圾回收期的實現容易的多了。因為所有的物件都保證具有其型別資訊,因此不會因無法確定物件的型別而陷入僵局。


容器

    通常來說,如果不知道解決某個問題需要多少個物件時,或者它們將存活多久,那麼就不可能知道如何儲存這些物件。如何才能知道多少個空間來建立這些物件?答案是你不可能知道,因為這些資訊執行時才能確定的。

    容器,比如List,Map,Set等。

    從設計的角度來看呢,真正需要的只是一個可以被操作,從而解決問題的序列。需要選擇容器,原因如下:

    • 不同的容器提供了不同型別的介面,與外部行為。

    • 不同的容器,對某些不用型別的操作具有不同的效率。


引數化型別

    以前用Object來表示通用型別,未知型別,但是向上轉換時失去型別資訊,需要獲取時強制型別轉換。泛型,就是向下轉換,一般向上轉換是安全的。除非明確知道所要處理的物件的型別,否則向下轉換是不安全的。比如某個list放入一個元素,取出來時必須知道型別,做個強制型別轉換。否則丟擲異常。

    向下轉型和執行期檢查需要額外的執行時間,也需要程式設計師提供更多的心血,那麼建立這樣的容器,它知道自己所儲存的物件的型別,從而不需要向下轉型以及消除犯錯誤的可能,這就是泛型機制。


物件的建立和生命期

    在使用物件時,最關鍵的問題之一便是它們的生成與銷燬方式。每個物件需要生存都需要資源,尤其是記憶體。當我們不在需要一個物件時,它必須被清理掉,便其佔用的資源被釋放和重用。

    怎樣才能知道何時銷燬這些物件?當處理完某個物件後,系統某個其他部分可能還在處理它:

    • C++一樣,編寫程式時確定。給程式設計師提供了選擇權。

    • 堆在記憶體池中動態的建立,直到執行時才確定需要多少個物件,它們宣告週期如何,以及它們的具體型別是什麼。

    動態方式有這樣一般性的邏輯假設:物件趨向於變的複雜,所以查詢和釋放儲存空間的開銷不會對物件的建立造成重大沖擊。Java採用的就是動態記憶體分配方式。當需要建立物件時,就要用new關鍵字來構建此物件的動態例項。

    物件的生命週期,垃圾回收期的工作了。Java的垃圾回收器用來解決記憶體的釋放問題。它知道物件何時不再被使用,並自動釋放物件佔用的記憶體。結合瞭如下兩種特性:

    • 繼承與單一基類Object。

    • 只能只用方式建立物件(在堆上建立)。


異常處理,處理錯誤

    異常處理將錯誤處理直接置於程式語言中,有時甚至置於作業系統中。Java引入了異常處理,而且強制你必須使用它。它是唯一可接受的錯誤報告方式。如果編寫錯誤的異常處理程式碼,就編譯時就有出錯資訊。

    異常處理不是面向物件的特徵。


併發程式設計

    Java的併發內置於語言中。


Java與Internet

  • 客戶端,服務端程式設計

  • 瀏覽器,服務端程式設計