讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 1.15 設計 >

1.15 設計

現在你已經知道了何為對象,何為實例。不過,程序需要什麼對像類型呢,這些對像類型應該包含哪些方法與屬性呢,何時以及如何實例化它們呢,該如何使用這些實例呢?遺憾的是,我無法告訴你這些,這是一門藝術,基於對像編程的藝術。我能告訴你的是,在設計與實現基於對象的程序時,你的首要關注點應該是什麼,程序正是通過這個過程不斷發展起來的。

基於對象的程序設計必須要基於對對像本質的深刻理解之上。你設計出的對象類型需要能夠封裝好正確的功能(方法)以及正確的數據(屬性)。接下來,在實例化這些對像類型時,你要確保實例擁有正確的生命週期,恰到好處地公開給其他對象,並且要能實現對像之間的通信。

1.15.1 對像類型與API

程序文件只會包含極少的頂層函數與變量。對像類型的方法與屬性(特別是實例方法與實例屬性)實現了大多數動作。對像類型為每個具體實例賦予了專門的能力。它們還有助於更有意義地組織程序代碼,並使其更易維護。

可以用兩個短語來總結對象的本質:功能封裝與狀態維護(我最早是在REALbasic:The Definitive Guide一書中給出的這個總結)。

功能封裝

每個對象都有自己的職責,並且在自身與外界對像(從某種意義上說是程序員)之間架起一道不透明的牆,外界只能通過這道牆訪問方法與動作,而這是通過向其發送相應的消息實現的。在背後,這些動作的實現細節是隱藏起來的,其他對像無須知曉。

狀態維護

每個實例都有自己所維護的一套數據。通常來說,這些數據是私有的,因此是被封裝的;其他對像不知道這些數據是什麼,也不清楚其形式是怎樣的。對於外界來說,探求對像所維護的私有數據的唯一方式就是通過公有方法或是屬性。

比如,假設有一個對象,它實現了棧的功能——也許是Stack類的實例。棧是一種數據結構,以LIFO(後進先出)的順序維護著一組數據,它只會響應兩個消息:push與pop。push表示將給定數據添加到集合中,pop表示從集合中刪除最近剛加進去的數據。棧像是一摞盤子:盤子會一個接著一個地放到棧上面或從棧上移除,因此只有將後面添加的盤子都移除後才能移除第一個添加的盤子(如圖1-3所示)。

圖1-3:棧

棧對像很好地說明了功能的封裝,因為外部對棧的實現方式一無所知。它可能是個數組,也可能是個鏈表,還有可能是其他實現。不過客戶端對像(向棧對像發送push或pop消息的對象)對此卻並不在意,只要棧對像堅持其行為要像一個棧這樣的契約即可。這對於程序員來說也非常棒,在開發程序時,它們可以將一個實現替換為另外的實現,同時又不會破壞程序的機制。恰恰相反,棧對像不知道,也不關心到底是誰向其發送push或pop消息以及為何發送。它只不過以可靠的方式完成自己的工作而已。

棧對象也很好地說明了狀態維護,因為它不僅僅是棧數據的網關,它就是棧數據本身。其他對象可以訪問到棧的數據,但只能通過訪問棧對像本身的方式才行,棧對象也只允許通過這種方式來訪問。棧數據位於棧對像內部;其他對象是看不到的。其他對像能做的只是push或pop。如果某個對象位於棧對象的頂部,那麼無論哪個對象向其發送pop消息,棧對象都會收到這個消息並將頂部對像返回。如果沒有對象向這個棧對像發送pop消息,那麼棧頂部的對象就會一直待在那裡。

每個對象類型可以接收的消息總數(即API,應用編程接口)類似於你可以讓這個對象類型所做的事項列表。對像類型將你的代碼劃分開來;其API構成了各部分之間通信的基礎。

在實際情況下,當編寫iOS程序時,你所使用的大多數對像類型都不是你自己的,而是蘋果公司提供的。Swift本身自帶了一些頗具價值的對象類型,如String和Int;你可能還會使用import UIKit,它包含了為數眾多的對象類型,這些對像類型會湧入你的程序中。你無須創建這些對像類型,只要學習如何使用它們就可以了,你可以查閱公開的API,即文檔。蘋果公司自己的Cocoa文檔包含了大量頁面,每一頁都列出並介紹了一種對像類型的屬性與方法。比如,要想瞭解可以向NSString實例發送什麼消息,你可以先研究一下NSString類的文檔。其頁面包含屬性與方法的長長的列表,告訴你NSString對像能做什麼。文檔上並不會列出關於NSString的一切,不過大部分內容都可以在上面找到。

在編寫代碼前,蘋果公司已經替你做了很多思考與規劃。因此,你會大量使用蘋果公司提供的對象類型。你也可以創建全新的對象類型,但相對於使用現有的對象類型來說,自己創建的比例不是很大。

1.15.2 實例創建、作用域與生命週期

Swift中重要的實體基本上就是實例了。對像類型定義了實例的種類以及每一種實例的行為方式。不過這些類型的具體實例都是持有狀態的「實體」,這也是程序最為關注的內容,實例方法與屬性就是可以發送給實例的消息。因此,程序能夠發揮作用是離不開實例的幫助的。

不過在默認情況下,實例是不存在的!回頭看看示例1-1,我們定義了一些對像類型,但卻並沒有創建實例。如果運行程序,那麼對像類型從一開始就會存在,但僅僅只是存在而已。我們實現了一種可能性,能夠創建一些可能存在的對象的類型,但在這種情況下,其實什麼都不會發生。

實例並不會自動產生。你需要對類型進行實例化才能得到實例。因此,程序的很多動作都會包含實例化類型。當然,你希望保留這些實例,因此還會將每個新創建的實例賦給一個變量,讓這個變量來持有它、為其命名,並保持其生命週期。實例的生命週期取決於引用它的變量的生命週期。根據引用實例的變量的作用域,一個實例可能會對其他實例可見。

基於對像編程的藝術的要點就在於此,賦予實例足夠的生命週期並讓它對其他實例可見。你常常會將實例放到特定的盒子中(將其賦給某個變量、在某個作用域中聲明),這多虧了變量生命週期與作用域規則,如果需要,實例會留存足夠長的時間供程序使用,其他代碼也可以獲得它的引用並與之通信。

規劃好如何創建實例、確定好實例的生命週期以及實例之間的通信這件事讓人望而生畏。幸好,在實際情況下,當編寫iOS程序時,Cocoa框架本身會再一次為你提供初始框架。

比如,你知道對於iOS應用來說,你需要一個應用委託類型和一個視圖控制器類型;實際上,在創建iOS應用項目時,Xcode會幫你完成這些事情。此外,當應用啟動時,運行時會幫你實例化這些對像類型,並且構建好它們之間的關係。運行時會創建出應用委託實例,並且讓其在應用的生命週期中保持存活狀態;它會創建一個窗口實例,並將其賦給應用委託的一個屬性;它還會創建一個視圖控制器實例,並將其賦給窗口的一個屬性。最後,視圖控制器實例有一個視圖,它會自動出現在窗口中。

這樣,你無須做任何事情就擁有了在應用生命週期中一直存在的一些對象,包括構成可視化界面的基礎對象。重要的是,你已經擁有了定義良好的全局變量,可以引用所有這些對象。這意味著,無須編寫任何代碼,你就已經可以訪問某些重要對象了,並且有了一個初始位置用於放置生命週期較長的其他對象,以及應用所需的其他可視化界面元素。

1.15.3 小結

在構建基於對象的程序來執行特定的任務時,我們要理解對象的本質。它們是類型與實例。類型指的是一組方法,用於說明該類型的所有實例可以做什麼(功能封裝)。相同類型的實例只有屬性值是不同的(狀態維護)。我們要做好規劃。對象是一種組織好的工具,一組盒子,用於封裝完成特定任務的代碼。它們還是概念工具。程序員要能以離散對象的思維進行思考,他們要能將程序的目標與行為分解為離散的任務,每個任務都對應一個恰當的對象。

與此同時,對象並不是孤立的。對像之間可以合作,這叫作通信,方式則是發送消息。通信途徑是多種多樣的。對此做出妥善的安排(即架構),從而實現對像之間的協作、有序的關係是基於對像編程最具挑戰性的一個方面。在iOS編程中,你可以得到Cocoa框架的幫助,它提供了初始的對象類型集合以及基礎的架構框架。

使用基於對象的編程能夠很好地讓程序實現你的需求,同時又會保持程序的整潔性和可維護性;你的能力會隨著經驗的增加而增強。最後,你可能想要進一步閱讀關於高效規劃及基於對像編程架構方面的讀物。我推薦兩本經典、值得收藏的好書。Martin Fowler所寫的Refactoring(Addison-Wesley,1999)介紹了如何根據哪些方法應該屬於哪些類而重新調整代碼的原則(如何戰勝恐懼以實現這一點)。Erich Gamma、Richard Helm、Ralph Johnson及John Vlissides(又叫作「四人組」)所寫的Design Patterns(本書中文版《設計模式:可復用面向對像軟件的基礎》已由機械工業出版社引進出版,書號:978-7-111-07575-2。)是架構基於對像程序的寶典,書中列舉了如何根據正確的原則以及對像之間的關係來安排對象的所有方法(Addison-Wesley,1994)。