面向對象
- 在軟件開發中,通常認爲在編程之前要完成設計。這種說法並不準確;在實際中,分析、編程和設計趨向於重疊、結合、交織。
對象是一個數據以及相關行爲的集合
面向簡單的意思就是指向;面向對象:把功能指向模型(對象)
對複雜系統的建模方法之一:通過數據和行爲來描述交互對象的合集。
分析、設計、編程是軟件開發的流程,面向對象是一種開發的風格。
軟件開發並不是一蹴而就,採用迭代式開發模型:先模塊化、設計和編程實際任務中的一小部分,然後評估這個程序並通過擴展來改善每一個特性,包括一系列短週期裏增加新的特性。
對象和類
對象:行爲和數據的集合
- 一個蘋果是“蘋果”對象,另一個蘋果是“另一個蘋果”對象。
類:創建對象的模板
- 很多個“蘋果”對象通用一個“蘋果”類。
描述類、對象之間的關係:統一建模語言(UML)
- 圖中展示出了:一名學生和一間教室因爲某種關係關聯在一起。關聯是把兩個類聯繫起來的基本方式。
類之間的關係通常不需要進一步解釋,但是進一步澄清的場合也是存在的。
- 圖中解釋了學生和教室的關係:一間教室可以容納多名學生。
- 多樣性:類之間的關係可以雙向解讀。
指定屬性和行爲
對象是類的實例,可以彼此相互關聯。一個對象實例是一個帶有他自己數據集合和行爲的特定對象。例如:小明同學就是小明,是一個特定的學生對象。
數據描述對象
一個類的對象可以定義這個類的一些公共特徵,這些特徵可以賦予不同的值。例如有3名學生,都有一個名字,所以“學生”類就有公共屬性“姓名”,而“姓名”可以相同也可以不同。所以,屬性的值不唯一。
屬性類型一般是編程語言常見的標準說法,如整型、浮點型、字符串字節等,也有可能是數據結構,例如列表、樹、圖等。
- 在設計階段不用考慮這些屬性的值具體,因爲不同的語言有不同的實現方法。
完善上面的例子,給屬性賦值:
行爲是動作
行爲是可以發生在對象身上的動作。一個特定的類的對象裏可以執行的行爲叫做方法
- 在編程層面,方法就像結構化編程裏的函數一樣。
方法可以像函數那樣接受參數並返回值。
回到例子中,和學生聯繫到一起的行爲有上學,上學就需要到一間教室,成爲班級的一員,即加入花名冊裏。所以上學這個動作就要有一個教室參數作爲操作依據;學生上學要交作業,交作業方法會返回一個值叫作業成績。對於教室,放學之後就要清空,學生各自回家;教室還有一個行爲是授課,授課就需要科目,所以科目就是授課這個方法的參數。
給單個對象添加模塊或者方法允許我們創建一個交互對象的系統。系統裏的每一個對象都是特定的類的成員。這些類指明瞭這個對象可以容納什麼樣的數據類型以及什麼方法可以調用它。每一個對象的數據相比於同一類的其他對象可以有不同的狀態,並且因爲這個不同,每一個對象對於方法調用的反應也是不一樣的。
隱藏細節並且創建公共接口
模塊化一個對象的主要目的是決定該對象的公共接口。接口是其他對象可以和該對象交互的屬性和方法的集合。它們不需要通常也不允許去訪問對象內部空間。一個常見的例子就是手機,我們在使用手機撥打電話的時候,不必考慮電話通訊是如何建立的,僅需要通過UI就可以完成操作。作爲普通用戶,我們沒能力也沒權限打開手機去查看內部的硬件工作狀態,通常這會讓你失去保修。
隱藏一個對象實現或者功能細節的過程,通常稱爲信息隱藏,有時也會成爲封裝。封裝數據並不一定是隱藏。,你有一封情書,爲了不讓父母發現,你藏在了一個上鎖的收納盒中,收納盒放在收納箱裏,這就是封裝並隱藏。然而你把情書放在一個全包相框中,掛在了牆上,這時候就是封裝而沒有隱藏了。
- 封裝和隱藏在設計時不是那麼重要,實際上這兩個屬於是可以相互替換的。作爲Python工程師,我們不需要和不必要真正的隱藏信息。
程序對象代表了對象,但它們不是真正的對象,它們是模型。模型的好處是可以理想化,忽略不重要的細節。一個手機模型可以理想化爲可以實際使用的手機,具有一切你想要的功能,雖然它本身只是一個擺設。模型是真是概念的抽象
抽象是另一個和封裝一級信息隱藏相關的面向對象詞彙。抽象意味着要對一個給定的任務以最合適的水平來處理細節的描述程度。它是一個把公共接口從內部細節抽取出來的過程。還是那個手機模型,對於普通用戶來說,用戶操作的是UI, 可以打電話,發短信,上網等,其後臺的進程管理、電源管理、信號處理等功能是無關緊要的,對於開發工程師來說,這些又是所關注的重點。抽象就是通過把公共和私有接口分開而封裝信息的過程。私有接口用於信息隱藏。(只有特定的人知道)
組合和繼承
組合
到目前爲止,我們已經學會了把系統作爲一組交互式對象來設計,每一個交互都是在適當的抽象層次上查看對象,創建這些抽象對象的方法一般有兩個基本原則:組合和繼承。
組合:把一些對象收集在一起組成一個新的對象。
- 當一個對象是另一個對象的一部分時,這是一個好選擇。
在手機的例子裏,一部手機是由主機、電源適配器、電源線、耳機、包裝盒組成。接下來,主機是由屏幕、後蓋、電池、主板、中框等組成。在這個例子中,組合是提供抽象很好的方式。手機對象提供給用戶所需要的接口,同時提供了訪問後臺的能力,根據工程師的需要可以不同層面的抽象劃分。
然鵝,程序系統中的對象並沒有實際對象那麼事無鉅細,程序設計中的對象往往更加具有針對性,要什麼,不要什麼,清晰明瞭。
象棋遊戲
- 象棋遊戲是由兩個棋手之間玩得。在一個8X8的網格里包含64個位置的棋盤上使用一副棋子。棋盤有可以移動的16個一組的棋子兩組,兩個選手交替用不同的方式移動。每一個棋子可以吃掉其他的棋子。每一次移動,棋盤都要在屏幕上刷新自己。
在描述中,斜體確定了一些可能的對象。黑體確定了一些可能的關鍵方法。在一個面向對象分析變成設計的過程中,這是常見的第一步。在這一點上,爲了強調組合,我們將關注於棋盤,而不會關心太多有關玩家或者不同類型棋子的問題。
爲了方便後續表述,UML將採用英文方式展現。
- 這個圖確切的表示了2個玩家可以和一套棋子做交互,同時也說明,任何玩家一次動作只能玩一副棋子。
現在,讓我們拋開UML,想象一下象棋這個對象由什麼組成:一個棋盤和32個棋子。棋盤由64個位置組成。此時要引入一個概念聚合,聚合是組合的一種一般式,在上面例子中,棋盤的組成位置是不能隨意剝除的,棋盤A的A1位不可能由棋盤B的A1位來替代,這是組合;然鵝,原棋盤A配套的棋子King,卻可以用棋盤B的配套棋子King來替代。
讓我們完善象棋遊戲的UML:
- 在UML中,實心菱形代表組合關係,空心菱形代表聚合關係。推薦使用Visio製作UML
繼承
繼承,在面向對象編程中衆所周知的一種關係。繼承,顧名思義,從父輩那裏獲得的相關的屬性。一個類可以從其他類繼承屬性和方法。
例如我們在象棋中有32個棋子,其實只有6中不同的棋子類型(Rook/Bishop/King/Queen/Knight/Pawn),每種類型棋子在移動的時候遵從不同的規則。所有這些棋子都有如顏色、屬於那副棋、形狀、移動等屬性。用UML表示這幾種棋子從一個Piece類繼承的關係:
所有的棋子都會通過Piece繼承到chess_set和color屬性。每一個棋子提供一個不同的shape屬性和move方法。
考慮到象棋棋子的特點,給它們提供一個默認移動方法沒有什麼意義,只需要在每個子類裏面指明它的移動方法就行。這可以通過使Piece成爲一個抽象類,並且聲明一個抽象的移動方法實現。抽象方法好像在說:“我們在子類裏需要這個方法,但是我們不允許這個類指明一個現實方法。”這樣一個沒有實現任何方法的類,會簡單的告訴我們這個類該做什麼,但是絕對沒有提供任何建議告訴你如何去做。
在面向對象編程中,這種類成爲接口。
繼承提供了抽象
多態:依據一個類的不同子類的實現方式而區別對待這個類的能力。在上面的例子中,棋盤對象接收到一個移動請求,然後調用這個棋子的move方法函數。棋盤不關心是哪個棋子的move函數,它要做的只是調用,然後相應的子類會去處理是哪個棋子、該怎麼移動。
多重繼承
當我們說遺傳的時候,我們會說“這個孩子眼睛像他爸,鼻子像他媽”。同樣在編程中,允許一個子類從多個父類那裏繼承功能。
只要兩個類有不同的接口,對於一個從這兩個類繼承過來的子類通常是無害的。例如,如果我們有一個汽車類,它有一個move方法,並且還有一個船類,它也有一個move方法。然後我們想創建一個水陸兩棲的交通工具,需要繼承這兩個類的一些屬性,當我們調用move方法的時候,這個子類到底該怎麼操作?在設計層面,這需要解釋,在實現層面,每種編程語言有各自不同的方式決定該調用哪一個父類的方法,或者基於什麼樣的順序去調用。
一個比較好的處理方法就是避開它,想想有沒有其它不衝突的方法來實現。
總結
學習瞭如何把兩個不同的對象分爲一個不同的類的分類,通過類的接口描述了這些類的屬性和行爲。
- 類和對象
- 抽象、封裝、信息隱藏
- 設計一個公共接口
- 對象關係:關聯、組合、繼承
- UML語法