面向對象程序設計簡介(1/2)

在使用 Cocoa 和 Objective-C 進行編程的時候,最讓人感到困惑的就是面向對象編程。幾乎全部的現代編程語言都是面向對象的,學習面向對象的概念和模式對你讀寫代碼都會帶來很大的幫助。
UITableView 和 UIScrollView 或者 NSString 和NSMutableString 之間的關係體現的是面向對象設計的基本理念。經過對這些理念的理解,您將更好的領會到Cocoa 和 Cocoa Touch內部爲何要像它如今這樣組織,而且在您之後編寫您本身的應用或者框架的時候,將會更有想法。
在本系列教程中,您將學習面向對象程序設計,內容涵蓋如下幾個概念:html

                                                     

  • 對象基礎
  • 繼承
  • MVC模型
  • 多態
  • 常見的面向對象模式

總的來講本系列課程是爲那些編程初學者而設計的-就像我剛開始學習編程時那樣。你極可能尚未接觸過不少編程語言,而且你也不明白爲何全部事都要以一種特定的方式去完成。
本教程將會側重介紹面向對象設計原則,而不會介紹具體語法,因此在繼續閱讀以前您應該對Objective-C 和 Xcode 的基本概念有所理解。 若是您須要補充這方面的基礎知識,請借鑑這篇教程 Beginning Objective-Cios

準備開始

爲了以更具體的方式去理解一些概念, 你將建立一個叫作Vehicles的程序。 它將用到一個能將現實世界的物件轉換成虛擬對象的最多見的隱喻詞「車」, 它能夠是自行車,汽車或者任何其它帶輪子的的東西。
好比, 這是一輛車:
Kleinwagen_16
這也是一輛車:
Motorcycle on a white background
這也是一輛車:
B
這仍是一輛車:
European 18-wheeler with canvas trailer
在這部分教程中, 你將用面嚮對象的技術創建一個數據模型以表明全部的這些「車」,還將建立一個簡單的應用以實現這些數據模型並將這些「車」的數據顯示給用戶。
下載 初始工程, 它將包含一個你將用於學習面向對象編程的程序的基礎框架。程序員

對象基礎

在面向對象編程中,主要的目的是分解一個「東西」的特色,並將其用於建立一個或多個對象,這個對象能夠描述出這個「東西」是什麼以及它能作哪些事。
有時候,就像車同樣,你的「東西」在現實世界會有一個等同物。但有時候也並不必定會有這種等同物, 就像不少不一樣類型的 UIViewController 對象同樣。 爲了簡單, 你先建立一些具備現實世界等同物的對象吧。
爲了回答某個「東西」是什麼,你首先要弄清楚這個「東西」有哪些特色。
有些語言會把這些特色做爲一個「字段」,一個「成員」,甚至只是一個「變量」。然而在Objective-C 中,一個對象的特色是由它的特性(properties)所體現的.編程

想想「車」的這個概念——一個可以描述涵蓋全部以上圖片的東西。你的腦中會浮現出一些關於「車」的什麼特色呢?canvas

  • 它的輪子數量老是大於零
  • 它總有某種能量來源,以使它能動起來,能夠是人力,汽油,電能或者是混合動力
  • 它是有品牌的,像福特,雪佛蘭,哈利—戴維森,施文
  • 它有類型名稱,像越野車,跑車或者小汽車
  • 它有出廠日期

*- 針對汽車或者卡車咱們有時候也說「製造商」,可是爲了清晰咱們這裏統一都說「品牌」。
如今你已經知道了一些車的基本特色,你已經能根據這些特色構建一個對象了。
初始工程裏有兩個文件:Vehicle.h 和 Vehicle.m, 它們一塊兒組成了一個NSObject的子類 。過會兒你將會進一步瞭解什麼子類。
將下面這部分代碼加入到 Vehicle.h 文件中,位於 @interface 的下一行:設計模式

@property (nonatomic, assign) NSInteger numberOfWheels;
@property (nonatomic, copy) NSString *powerSource;
@property (nonatomic, copy) NSString *brandName;
@property (nonatomic, copy) NSString *modelName;
@property (nonatomic, assign) NSInteger modelYear;

這這些特性(property)的聲明描述了全部你想要記錄的有關於這個對象的特色。數組

小小的題外話:特性(property)的背後

當你在Xcode 4.4或者以上的環境下聲明一個@property, Xcode 將自動爲這個特性(property)合成一個後臺實例變量,一個getter方法, 一個setter 方法。 這爲咱們省去了大量沒必要要的代碼。 若是沒有這種自動合成功能,你就須要爲每個特性(property)寫如下代碼:app

@interface Vehicle() {
    NSString *_brandName;
}
@end
@implementation Vehicle
//Setter method
-(void)setBrandName:(NSString *)brandName
{
    _brandName = [brandName copy];
}
//Getter method
-(NSString *)brandName
{
    return _brandName;
}
@end

爲每個特性(property)省去這些代碼之後,你的代碼看起來會更加乾淨,可讀性更高。而且你還能夠以幾種不一樣的方式存取一個@property :框架

  • someVariableName = self.brandName; 這句話在背後實際上調用了 [self brandName];這個事先爲你合成好了的 getter 方法,它將返回 _brandName 實例變量中的數據, 並將它賦值給 someVariableName.
  • self.brandName = @"Some Brand Name"; 這句話在背後實際上調用了 [self setBrandName:@"Some Brand Name"]; setter 方法,它將實例變量 _brandName 的值設置成@"Some Brand Name".

 

描述對象

另外一個有關與對象的很重要的問題— 這個對象究竟能作什麼?
從程序層面來說,一個對象能作的事都一般被稱爲方法. 想想上面圖片中全部的車廣泛都能作的事:iview

  • 都能前進
  • 都能後退
  • 都能停下
  • 都能轉彎
  • 都能換擋
  • 都能發出某種噪音(好比喇叭或者鈴鐺)

大多狀況下,你會使用返回值類型爲void的方法:好比, -(void)nameOfMethod. 這在當你僅僅想要執行某個函數體而不須要從該函數體獲取任何返回信息時特別有用。然而,爲了能更容易的顯示你的程序正在發生着什麼,你將用到一些返回值類型爲NSString對象的方法。

小小的題外話:類方法與實例方法

你極可能已經注意到了,在你書寫代碼的時候,有些方法的前面是+號,而有的方法前面是-號。這兩個不一樣的符號偏偏區分了這個方法是一個類方法仍是一個實例方法。
最簡單的區分法是將它們想象成是現實世界中的原理圖:原理圖永遠只有一張,可是有了原理圖,你就能複製任意多的拷貝。
類方法用+號來表示,它表明了這張原理圖不須要進行復制就能作的操做。好比NSString 的stringWithFormat:就是一個類方法,它能建立一個新的字符串對象。
實例方法用-號來表示,它是須要這張原理圖先進行拷貝之後,它的拷貝所能執行的方法。好比, NSString 的一個實例 @"Hello There" 就有一個實例方法 lowercaseString 它將全部字符轉換爲小寫,返回 @"hello there"。若是將lowercaseString做爲類方法是沒有意義的,由於一個類根本沒有用於轉爲爲小寫的字符串實例!

爲你的類添加基本的方法

將如下方法添加到 Vehicle.h 頭文件中,位於你早些時候添加的特性(property)如下,但位於 @end之上:

//Basic operation methods
-(NSString *)goForward;
-(NSString *)goBackward;
-(NSString *)stopMoving;
-(NSString *)changeGears:(NSString *)newGearName;
-(NSString *)turn:(NSInteger)degrees;
-(NSString *)makeNoise;

頭文件中聲明 的方法是公開的 – 就像告訴別的對象,「這些是我能作的事」 ,可是別的對象並不知道,這些事是如何被完成的。爲了完成這些事,咱們將如下方法的實現添加到Vehicle.m文件中:

-(NSString *)goForward
{
    return nil;
}
-(NSString *)goBackward
{
    return nil;
}
-(NSString *)stopMoving
{
    return nil;
}
-(NSString *)turn:(NSInteger)degrees
{
    //Since there are only 360 degrees in a circle, calculate what a single turn would be.
    NSInteger degreesInACircle = 360;
 
    if (degrees > degreesInACircle || degrees < -degreesInACircle) {
        //The % operator returns the remainder after dividing. 
        degrees = degrees % degreesInACircle;
    }
 
    return [NSString stringWithFormat:@"Turn %d degrees.", degrees];
}
-(NSString *)changeGears:(NSString *)newGearName
{
    return [NSString stringWithFormat:@"Put %@ into %@ gear.", self.modelName, newGearName];
}
-(NSString *)makeNoise
{
    return nil;
}

這些代碼大部分都只是框架而已,待會兒你將實現這些方法的細節。 turn: 和 changeGears: 有一些日誌輸出,這些輸出將幫助你理解你的函數是否正常工做。
打開 AppDelegate.m文件, 將這行導入語句添加到文件頂部:

#import "Vehicle.h"

這樣你就能在你的代碼中引用Vehicle類了。
接下來,將application:didFinishLaunchingWithOptions: 這個函數的實現替換成以下代碼:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    Vehicle *vehicle = [[Vehicle alloc] init];
    //Test methods with implementations
    NSLog(@"Vehicle turn: %@", [vehicle turn:700]);
    NSLog(@"Vehicle change gears: %@", [vehicle changeGears:@"Test"]);
 
    //Test methods without implementations
    NSLog(@"Vehicle make noise: %@", [vehicle makeNoise]);
    NSLog(@"Vehicle go forward: %@", [vehicle goForward]);
    NSLog(@"Vehicle go backward: %@", [vehicle goBackward]);
    NSLog(@"Vehicle stop moving: %@", [vehicle stopMoving]);
 
    return YES;
}

當vehicle實例被初始化之後,你能夠調用它的每個實例方法,看看它的日誌輸出。
編譯並運行你的程序,你就能看到全部咱們填充了數據的字符串都正常的返回了日誌。可是對於那些沒有設置過的特性(property),或者返回值爲nil的方法,你會看到返回的日誌是(null),就像下面這樣:
Log output
你將使用繼承來爲這些方法提供更特定的實現。

繼承

繼承這個概念和遺傳很是像:孩子老是繼承他們父母的特色。
然而,在像Objective-C這樣的單一繼承編程語言中,繼承的概念要遠比現實世界中遺傳的概念要嚴格的多。」子「類老是繼承自一個」父「類,或者說超類,而不是像現實中,你的特色其實是你父母的特色的混合。
Vehicle 類繼承自NSObject類,而NSObject類位於最底層,它幾乎是Objective-C中全部類的父類。

注意: 有一些 C 語言結構體,像 CGRect 和 CGSize 它們並非 NSObject 的子類,由於結構體並不遵循面向對象編程的原則。然而,大部分以NS 或 UI 開頭的類都是 NSObject 的子類。 有關NSObject的更詳細介紹,請看 Apple’s documentation 。

爲了更實際的看到繼承,建立一個Vehicle的子類 「Car」。點擊 FileNewFile…Cocoa TouchObjective-C Class。像圖示同樣建立一個名爲Car的 Vehicle子類:
Add Car
打開 Car.m 文件,將下面的初始化函數添加到 @implementation 行如下:

- (id)init
{
  if (self = [super init]) {
    // Since all cars have four wheels, we can safely set this for every initialized instance
    // of a car. 
      self.numberOfWheels = 4;
  }
  return self;
}

這個init初始化方法僅僅是把輪子的數量設置爲 4。
你有沒有發現,你不須要作任何額外的操做來調用 numberOfWheels 這個Vehicle 變量 。那是由於繼承了 Vehicle 類之後,Car 已經可以調用全部Vehicle的公共變量和方法.
若是你須要更多的變量來描述汽車(car)該怎麼辦呢?除了輪子的數量之外, 汽車還有不少特殊的特色,它有幾扇門呢?它的頂棚能夠開關嗎?它有遮陽棚嗎?
固然,你能夠很容易的添加這些新的特性!打開 Car.h , 在 @interface 行下添加以下代碼:

@property (nonatomic, assign) BOOL isConvertible;
@property (nonatomic, assign) BOOL isHatchback;
@property (nonatomic, assign) BOOL hasSunroof;
@property (nonatomic, assign) NSInteger numberOfDoors;

 

重寫方法

在你添加了這些新的特性以後,你還能夠添加新的方法或者從父類中繼承一些方法,並在子類中實現它們。
繼承的意思是「拿一個父類中已經聲明的方法,併爲它建立你本身的實現」。 好比,當你建立一個 UIViewController 對象時,系統已經自動爲你繼承了這些方法  initWithNibName:bundle:,viewDidLoad, 和 didReceiveMemoryWarning。
當你繼承一個方法時,你能夠作兩件事:

  1. 調用 [super method] 方法以執行父類中的全部內容,或者
  2. 從零開始,爲子類提供新的實現

你會發如今全部的UIViewController 方法中,蘋果都要求你調用 [super method] 方法 – 由於在UIViewController 類中有一些很重要的方法,以致於你的子類在執行它本身的任務前必須先執行父類中的方法。
然而,由於那些你將要繼承的Car 類中的方法都返回nil,因此你僅僅只須要提供你的實現就好了,由於父類中的實現是空的,因此也就沒有必要必要再調用父類方法了。
打開 Car.m 文件並添加以下私有方法以簡化你的父類繼承:

#pragma mark - Private method implementations
- (NSString *)start
{
    return [NSString stringWithFormat:@"Start power source %@.", self.powerSource];
}

有些車好比自行車是不須要啓動的,可是汽車是須要啓動的!在這種狀況下,你就不須要將start 定義爲公開方法,由於它只須要本類的實現中被調用。

注意: 即使一個方法是「似有」的,並對其它類和對象不可見,那也不能阻止他被某個子類繼承。你實在沒法阻止在這種狀況下將要發生的錯誤,可是你應該在你應用的文檔中進行註明, 就像蘋果所作的這樣

接着,將剩下的繼承方法添加到文件中:

#pragma mark - Superclass Overrides
- (NSString *)goForward
{
    return [NSString stringWithFormat:@"%@ %@ Then depress gas pedal.", [self start], [self changeGears:@"Forward"]];
}
- (NSString *)goBackward
{
    return [NSString stringWithFormat:@"%@ %@ Check your rear view mirror. Then depress gas pedal.", [self start], [self changeGears:@"Reverse"]];
}
- (NSString *)stopMoving
{
    return [NSString stringWithFormat:@"Depress brake pedal. %@", [self changeGears:@"Park"]];
}
- (NSString *)makeNoise
{
    return @"Beep beep!";
}

如今你有了具體的,實現徹底的車的子類,你能夠開始構建你的 Table View controller了。

構建用戶界面

在 VehicleListTableViewController.m文件中,將以下導入語句添加到文件頭部,位於Vehicle導入語句之下:

#import "Car.h"

接下來,將下面的方法添加到didReceiveMemoryWarning 和 #pragma mark - Table View之間:

#pragma mark - Data setup
-(void)setupVehicleArray
{
    //Create a car.
    Car *mustang = [[Car alloc] init];
    mustang.brandName = @"Ford";
    mustang.modelName = @"Mustang";
    mustang.modelYear = 1968;
    mustang.isConvertible = YES;
    mustang.isHatchback = NO;
    mustang.hasSunroof = NO;
    mustang.numberOfDoors = 2;
    mustang.powerSource = @"gas engine";
 
    //Add it to the array
    [self.vehicles addObject:mustang];
 
    //Create another car.
    Car *outback = [[Car alloc] init];
    outback.brandName = @"Subaru";
    outback.modelName = @"Outback";
    outback.modelYear = 1999;
    outback.isConvertible = NO;
    outback.isHatchback = YES;
    outback.hasSunroof = NO;
    outback.numberOfDoors = 5;
    outback.powerSource = @"gas engine";
 
    //Add it to the array.
    [self.vehicles addObject:outback];
 
    //Create another car
    Car *prius = [[Car alloc] init];
    prius.brandName = @"Toyota";
    prius.modelName = @"Prius";
    prius.modelYear = 2002;
    prius.hasSunroof = YES;
    prius.isConvertible = NO;
    prius.isHatchback = YES;
    prius.numberOfDoors = 4;
    prius.powerSource = @"hybrid engine";
 
    //Add it to the array.
    [self.vehicles addObject:prius];
    //Sort the array by the model year
    NSSortDescriptor *modelYear = [NSSortDescriptor sortDescriptorWithKey:@"modelYear" ascending:YES];
    [self.vehicles sortUsingDescriptors:@[modelYear]];
}

這個函數的做用就是簡單的將數據初始化的工做分離出來以構建你的vehicle數組。
找到 awakeFromNib 並將如下代碼添加到這個函數的末尾處:

  // Initialize the vehicle array
  self.vehicles = [NSMutableArray array];
  // Call the setup method
  [self setupVehicleArray];
  // Set the title of the View Controller, which will display in the Navigation bar.
  self.title = @"Vehicles";

上面這個方法會在你的Storyboard 完成構建一個UIViewController 後被執行。它調用了 你剛剛建立的 setupVehicleArray 方法,並設置了 VehicleListTableViewController的標題,以顯示它的內容。
編譯並運行你的程序,你會看到的將會和下圖顯示的同樣:
Three Cars
你看到的這些數字可能會不同,由於它們表明着內存地址,可是除了這之外,其它內容都應該是同樣的。
好消息是這些對象已經被識別爲Car 對象了。壞消息是當前顯示的內容並非很是的有用。看一看UITableViewDataSource 的代理方法 tableView:cellForRowAtIndexPath:中都作了什麼事:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    Vehicle *rowVehicle = self.vehicles[indexPath.row];
    cell.textLabel.text = [rowVehicle description];
    return cell;
}

這裏,你獲取了一個 UITableViewCell 對象,並以當前cell所在的行數做爲 self.vehicles 數組的索引獲取了一個Vehicle 對象。 緊接着你將這個Vehicle 對象的description 字符串賦值給當前cell的 textLabel變量。
description 方法(繼承自 NSObject 對象)輸出的字符並非很是的友好。你會但願在Vehicle 中定義一個可以以友好方式描述 Vehicle 對象完整內容的方法。
回到 Vehicle.h 文件中並添加以下方法聲明,位於全部其它方法的聲明之下,但位於 @end 之上:

//Convenience method for UITableViewCells and UINavigationBar titles.
-(NSString *)vehicleTitleString;

接着, 在 Vehicle.m 文件中添加以下實現,仍是位於其它方法的實現之下:

#pragma mark - Convenience Methods
-(NSString *)vehicleTitleString
{
    return [NSString stringWithFormat:@"%d %@ %@", self.modelYear, self.brandName, self.modelName];
}

上面這個方法用每一個Vehicle 對象中都會有的三個參數來完整的描述了vehicle 對象。
如今,更新VehicleListTableViewController的tableView:cellForRowAtIndexPath: 方法以使用新的描述方法,就像下面這樣:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    Vehicle *rowVehicle = self.vehicles[indexPath.row];
    cell.textLabel.text = [rowVehicle vehicleTitleString];
    return cell;
}

編譯並運行你的應用程序,如今它應該看上去更漂亮了:
Three Vehicles With Readable Titles
然而,當你從列表中選擇了一個 Vehicle ,你看到的將是和storyboard中所顯示的如出一轍的內容,而不是和你所選擇的Vehicle 對象所對應的內容:
Before Data Hookup Detail爲何會這樣呢?
打開 VehicleDetailViewController.m文件, 你會看到當全部的UI在Storyboard 中被建立完成的時候,全部的IBOutlets 也都爲你鏈接好了以節省你手動操做UI的時間,可是全部的數據都尚未鏈接好。

注意: 你會發現有些 IBOutlets 是在 VehicleDetailViewController.m 文件中設置的, 而不是像正常同樣位於 .h 文件中。

若是你有一些參數不但願向其它的類公開,你老是能夠將它們做爲似有的實現寫在.m 文件中。這就是以 @interface 聲明的位於 .m 文件頭部,緊接着類名和一對括號。好比,UIViewController() 就是 UIViewController的私有實現。

任何在此接口中聲明的 @property 都仍將能夠像正常  IBOutlet (若是合理的被標註過的話) 同樣從Storyboard 中和 你當前 .m 實現文件中使用,可是任何不相關的類,或者它的子類都沒法使用它。

 

鏈接你的數據和視圖

爲了鏈接數據,更新 VehicleDetailViewController.m 中的  configureView 方法,利用事先設置好的 vehicle 對象,就像下面這樣:

- (void)configureView
{
    // Update the user interface for the detail vehicle, if it exists.
    if (self.detailVehicle) {
        //Set the View Controller title, which will display in the Navigation bar.
        self.title = [self.detailVehicle vehicleTitleString];
 
        //Setup the basic details string based on the properties in the base Vehicle class.
        NSMutableString *basicDetailsString = [NSMutableString string];
        [basicDetailsString appendString:@"Basic vehicle details:nn"];
        [basicDetailsString appendFormat:@"Brand name: %@n", self.detailVehicle.brandName];
        [basicDetailsString appendFormat:@"Model name: %@n", self.detailVehicle.modelName];
        [basicDetailsString appendFormat:@"Model year: %dn", self.detailVehicle.modelYear];
        [basicDetailsString appendFormat:@"Power source: %@n", self.detailVehicle.powerSource];
        [basicDetailsString appendFormat:@"# of wheels: %d", self.detailVehicle.numberOfWheels];
 
        self.vehicleDetailsLabel.text = basicDetailsString;
    }
}

編譯並運行你的程序;從TableView 中單擊一個對象,你將看到以下的詳細視圖:
Basic vehicle details

模型-視圖-控制器(MVC)封裝邏輯

iOS 和不少其它現代編程語言都有一個設計模式叫作 模型-視圖-控制器 ,簡稱 MVC 。
MVC 背後的理念主要是,視圖永遠只關心如何呈現,模型永遠只關心數據,控制器應該能在不須要了解兩者太多的內部結構的前提下,很好的將兩者嫁接起來。
使用MVC最大的好處就是,當你的數據模型變了,你只須要修改一次就夠了。
新人最容易犯的錯誤就是在 UIViewController 類裏密密麻麻的的寫了過多的邏輯。這就使得視圖和 UIViewControllers 的鏈接太過於緊密,以致於這個視圖很難再被重用於顯示其它不一樣的內容。
爲何要在你的應用中實現MVC模型呢?設想若是你想往 VehicleDetailViewController 中添加更多有關汽車的詳細內容 。你能夠回到 configureView 方法中,並添加更多有關於汽車的具體內容,就像這樣:

//Car-specific details
[basicDetailsString appendString:@"nnCar-Specific Details:nn"];
[basicDetailsString appendFormat:@"Number of doors: %d", self.detailVehicle.numberOfDoors];

可是你要注意,這樣會有一個小問題:
Error with Car properties
VehicleDetailsViewController 只知道 在 Vehicle 父類中定義的參數;它並不知道任何關於Car 子類的內容。
有不少方法能夠解決這個問題。
一種最直觀的方法就是導入Car.h文件, 那麼 VehicleDetailViewController 就知道Car子類的全部參數了。可是那就意味着要爲每個子類添加大量的邏輯來處理這些參數。
每次你發現你本身在作這種事的時候,你都應該問問你本身:「個人視圖控制器是否是作了太多了呢?」
這種狀況下,你的答案是確定的。你能夠利用繼承的特性,用同一個方法來爲不一樣的子類提供對應的字符串來顯示相應的內容。

經過繼承建立子類

首先,將下面的新方法添加到Vehicle.h:

//Convenience method to get the vehicle's details.
-(NSString *)vehicleDetailsString;

這是公開聲明的方法,它能夠被像 VehicleDetailsViewController的其它類調用。它們不須要知道每個參數,相反,它們僅僅經過調用vehicleDetailsString一個方法就能夠獲取徹底格式化的字符串,而後使用它。
打開 Vehicle.m 文件並添加以下實現:

-(NSString *)vehicleDetailsString
{
    //Setup the basic details string based on the properties in the base Vehicle class.
    NSMutableString *basicDetailsString = [NSMutableString string];
    [basicDetailsString appendString:@"Basic vehicle details:nn"];
    [basicDetailsString appendFormat:@"Brand name: %@n", self.brandName];
    [basicDetailsString appendFormat:@"Model name: %@n", self.modelName];
    [basicDetailsString appendFormat:@"Model year: %dn", self.modelYear];
    [basicDetailsString appendFormat:@"Power source: %@n", self.powerSource];
    [basicDetailsString appendFormat:@"# of wheels: %d", self.numberOfWheels];
 
    return [basicDetailsString copy];
}

這個方法和你添加到 VehicleDetailViewController.m中的方法很是相似,只是它返回的是一個字符串,而不是直接將它在某個地方顯示出來。
如今你能夠繼承父類vehicle 的基礎字符串併爲 Car 類添加特殊的內容。打開 Car.m 並覆蓋vehicleDetailsString:方法的實現:

- (NSString *)vehicleDetailsString
{
    //Get basic details from superclass
    NSString *basicDetails = [super vehicleDetailsString];
 
    //Initialize mutable string
    NSMutableString *carDetailsBuilder = [NSMutableString string];
    [carDetailsBuilder appendString:@"nnCar-Specific Details:nn"];
 
    //String helpers for booleans
    NSString *yes = @"Yesn";
    NSString *no = @"Non";
 
    //Add info about car-specific features.
    [carDetailsBuilder appendString:@"Has sunroof: "];
    if (self.hasSunroof) {
        [carDetailsBuilder appendString:yes];
    } else {
        [carDetailsBuilder appendString:no];
    }
 
    [carDetailsBuilder appendString:@"Is Hatchback: "];
    if (self.isHatchback) {
        [carDetailsBuilder appendString:yes];
    } else {
        [carDetailsBuilder appendString:no];
    }
 
    [carDetailsBuilder appendString:@"Is Convertible: "];
    if (self.isConvertible) {
        [carDetailsBuilder appendString:yes];
    } else {
        [carDetailsBuilder appendString:no];
    }
 
    [carDetailsBuilder appendFormat:@"Number of doors: %d", self.numberOfDoors];
 
    //Create the final string by combining basic and car-specific details.
    NSString *carDetails = [basicDetails stringByAppendingString:carDetailsBuilder];
 
    return carDetails;
}

汽車版本的這個函數首先調用了父類的相應方法以獲取有關車的詳細內容。接着它將和帶有汽車特色的詳細內容存入 carDetailsBuilder 字符串,最後再將它們兩者結合起來。
如今將VehicleDetailViewController.m 文件中的  configureView  函數替換爲以下的實現,以顯示咱們剛剛建立完成的字符串:

- (void)configureView
{
    // Update the user interface for the detail vehicle, if it exists.
    if (self.detailVehicle) {
        //Set the View Controller title, which will display in the Navigation bar.
        self.title = [self.detailVehicle vehicleTitleString];
        self.vehicleDetailsLabel.text = [self.detailVehicle vehicleDetailsString];
    }
}

編譯並運行你的程序;選擇一輛車,除了看到通常信息之外,你還應該能看到帶有汽車特色的信息,就像下面這樣:
Basic and car-specific details
你的 VehicleDetailViewController 類如今已經能讓  Vehicle 和 Car 類來判斷所要顯示的數據了。  ViewController 所作的惟一的事情就是將信息和視圖鏈接起來。
這種方法的優點在你繼續爲 Vehicle 建立其它子類的時候被顯現出來。就拿一個最簡單的摩托車來講。
打開 FileNewFileCocoaTouchObjective-C Class, 建立一個 Vehicle 的新的子類 Motorcycle。
由於有的摩托車會發出深沉的引擎噪音,而有的摩托車的引擎聲音是高亮的,因此你建立的每個 Motorcycle 對象,你都應該爲它指定它能發出的噪音種類。
在 Motorcycle.h 中,添加一個表明噪音種類的參數,位於 @interface 行後面:

@property (nonatomic, strong) NSString *engineNoise;

接着,打開 Motorcycle.m. 添加以下 init 方法:

#pragma mark - Initialization
- (id)init
{
    if (self = [super init]) {
        self.numberOfWheels = 2;
        self.powerSource = @"gas engine";
    }
 
    return self;
}

由於全部的摩托車都有兩個輪子,而且都是汽油驅動的(在這個例子中,全部用電驅動的都被看做電動車,而不叫摩托車),你能夠在初始化對象的時候設置它輪子的個數已經動力源。
接下來,添加下面的方法以覆蓋父類中那些返回是 nil 的方法:

#pragma mark - Superclass Overrides
-(NSString *)goForward
{
    return [NSString stringWithFormat:@"%@ Open throttle.", [self changeGears:@"Forward"]];
}
-(NSString *)goBackward
{
    return [NSString stringWithFormat:@"%@ Walk %@ backwards using feet.", [self changeGears:@"Neutral"], self.modelName];
}
-(NSString *)stopMoving
{
    return @"Squeeze brakes.";
}
-(NSString *)makeNoise
{
    return self.engineNoise;
}

最後,覆蓋 vehicleDetailsString 方法以添加有 Motorcycle-特色的內容,就像下面這樣:

- (NSString *)vehicleDetailsString
{
    //Get basic details from superclass
    NSString *basicDetails = [super vehicleDetailsString];
 
    //Initialize mutable string
    NSMutableString *motorcycleDetailsBuilder = [NSMutableString string];
    [motorcycleDetailsBuilder appendString:@"nnMotorcycle-Specific Details:nn"];
 
    //Add info about motorcycle-specific features.
    [motorcycleDetailsBuilder appendFormat:@"Engine Noise: %@", self.engineNoise];
 
    //Create the final string by combining basic and motorcycle-specific details.
    NSString *motorcycleDetails = [basicDetails stringByAppendingString:motorcycleDetailsBuilder];
 
    return motorcycleDetails;
}

如今,是時候建立一些 Motorcycle 的實例了。
打開 VehicleListTableViewController.m 確保它導入了 Motorcycle 類,不然加入下面這句話:

#import "Motorcycle.h"

接下來,找到 setupVehicleArray 方法,並添加以下代碼,位於你以前添加的 Car 對象的下面,可是位於數組排序代碼的上面:

    //Create a motorcycle
    Motorcycle *harley = [[Motorcycle alloc] init];
    harley.brandName = @"Harley-Davidson";
    harley.modelName = @"Softail";
    harley.modelYear = 1979;
    harley.engineNoise = @"Vrrrrrrrroooooooooom!";
 
    //Add it to the array.
    [self.vehicles addObject:harley];
 
    //Create another motorcycle
    Motorcycle *kawasaki = [[Motorcycle alloc] init];
    kawasaki.brandName = @"Kawasaki";
    kawasaki.modelName = @"Ninja";
    kawasaki.modelYear = 2005;
    kawasaki.engineNoise = @"Neeeeeeeeeeeeeeeeow!";
 
    //Add it to the array
    [self.vehicles addObject:kawasaki];

上面的代碼簡單的初始化了兩個摩托車對象,並將它們添加到車的數組中。
編譯並運行你的應用程序;你將會在列表中看到你剛剛添加的 摩托車 對象 :
Added Motorcycles
點擊其中的一個,你將會被帶到這個摩托車 的詳情頁面,就像下面這樣:
Motorcycle Details
不管是汽車仍是摩托車(甚至是一個普通的老爺車),你均可以調用 vehicleDetailsString 並得到響應的詳情。
適當的分離模型,視圖和控制器,並運用繼承,你就可以爲一個父類的不一樣子類顯示數據,而避免了爲不一樣的子類撰寫大量額外的代碼。代碼越少==程序員越開心:]

提供模型類中的邏輯

運用這種方法,你還能夠將更多的更復雜的邏輯包裝在模型類裏面。想一想 卡車 對象:不少不一樣類型的車都被稱爲「卡車」,從小貨車到半掛車。你的卡車類須要一些邏輯,以基於這輛開車能拉多少貨物而改變它的行爲。
進入 FileNewFileCocoaTouchObjective-C Class, 建立一個名爲Truck 的Vehicle 的子類。
添加以下整型變量到Truck.h 文件中,用於存儲卡車的載重數據:

@property (nonatomic, assign) NSInteger cargoCapacityCubicFeet;

由於卡車的類型太多了,因此你也不須要建立初始化方法以自動提供全部的詳情。你能夠只是簡單的重寫父類中那些對於任何類型的卡車都適用的方法。
打開 Truck.m 文件並添加以下方法:

#pragma mark - Superclass overrides
- (NSString *)goForward
{
    return [NSString stringWithFormat:@"%@ Depress gas pedal.", [self changeGears:@"Drive"]];
}
- (NSString *)stopMoving
{
    return [NSString stringWithFormat:@"Depress brake pedal. %@", [self changeGears:@"Park"]];
}

接着,你須要重寫一些方法,以便它能根據貨車拉貨量的多少返回不一樣的字符串。大的卡車在倒車的時候須要發出警報聲,因此你能夠爲此建立一個私有函數(一個不聲明在 .h 文件中的函數,所以對於其它的類是不可見的)。
將以下幫助代碼添加到 Truck.m 文件中:

#pragma mark - Private methods
- (NSString *)soundBackupAlarm
{
    return @"Beep! Beep! Beep! Beep!";
}

而後回到剛剛重寫的那個方法中,如今你能夠在 goBackward 方法中調用  soundBackupAlarm 方法,這樣大卡車在後退的時候就能夠發出警報聲了:

- (NSString *)goBackward
{
    NSMutableString *backwardString = [NSMutableString string];
    if (self.cargoCapacityCubicFeet > 100) {
        //Sound a backup alarm first
        [backwardString appendFormat:@"Wait for "%@", then %@", [self soundBackupAlarm], [self changeGears:@"Reverse"]];
    } else {
        [backwardString appendFormat:@"%@ Depress gas pedal.", [self changeGears:@"Reverse"]];
    }
 
    return backwardString;
}

不一樣的卡車喇叭也不一樣;好比小型的卡車喇叭聲和汽車的喇叭聲很像,然而越大的卡車就會擁有更大的喇叭聲。爲了解決這種狀況,你只須要在 makeNoise 方法中添加一些簡單的 if/else 語句就好了。
像下面這樣添加 makeNoise 方法:

- (NSString *)makeNoise
{
    if (self.numberOfWheels <= 4) {
        return @"Beep beep!";
    } else if (self.numberOfWheels > 4 && self.numberOfWheels <= 8) {
        return @"Honk!";
    } else {
        return @"HOOOOOOOOONK!";
    }
}

最後,你能夠重寫 vehicleDetailsString 方法以從你的 Truck 對象中獲取對應的信息。就像下面這樣:

-(NSString *)vehicleDetailsString
{
    //Get basic details from superclass
    NSString *basicDetails = [super vehicleDetailsString];
 
    //Initialize mutable string
    NSMutableString *truckDetailsBuilder = [NSMutableString string];
    [truckDetailsBuilder appendString:@"nnTruck-Specific Details:nn"];
 
    //Add info about truck-specific features.
    [truckDetailsBuilder appendFormat:@"Cargo Capacity: %d cubic feet", self.cargoCapacityCubicFeet];
 
    //Create the final string by combining basic and truck-specific details.
    NSString *truckDetails = [basicDetails stringByAppendingString:truckDetailsBuilder];
 
    return truckDetails;    
}

如今你的 Truck 對象已經寫好了,你能夠試着建立一些實例。回到VehicleListTableViewController.m 中,添加以下的導入語句到文件頭部以便它能使用 Truck 類:

#import "Truck.h"

找到 setupVehicleArray 方法,在數組排序以前添加以下代碼:

    //Create a truck
    Truck *silverado = [[Truck alloc] init];
    silverado.brandName = @"Chevrolet";
    silverado.modelName = @"Silverado";
    silverado.modelYear = 2011;
    silverado.numberOfWheels = 4;
    silverado.cargoCapacityCubicFeet = 53;
    silverado.powerSource = @"gas engine";
 
    //Add it to the array
    [self.vehicles addObject:silverado];
 
    //Create another truck
    Truck *eighteenWheeler = [[Truck alloc] init];
    eighteenWheeler.brandName = @"Peterbilt";
    eighteenWheeler.modelName = @"579";
    eighteenWheeler.modelYear = 2013;
    eighteenWheeler.numberOfWheels = 18;
    eighteenWheeler.cargoCapacityCubicFeet = 408;
    eighteenWheeler.powerSource = @"diesel engine";
 
    //Add it to the array
    [self.vehicles addObject:eighteenWheeler];

這將會在汽車和摩托車所在的數組中添加一些帶有卡車特色的 Truck 對象。
編譯並運行程序;點擊卡車其中的一個,確保你可以看到帶有卡車特色的詳情,就像下面顯示的這樣:
Truck-specific Details
看起來很棒!這些卡車信息的得來要歸功於 vehicleDetailsString 方法,繼承以及重寫的實現。

接下來作什麼呢?

你能夠下載到目前爲止的項目工程

你已經建立了一個卡車基類,還有汽車,摩托車,卡車子類,而且所有列在一個table view中。然而你卻沒有辦法確認對於不一樣大小類型的卡車,你的處理是否正確。

教程的第二部分 將會完成這個應用的剩餘部分,以顯示更多的有關車的信息。同時,你還將學習多態,以及其它一些主要的有關於面向對象編程的設計模式。

到那時,何不試試實現一個自行車類,或者爲其它車的子類添加更多相關屬性?或者你能夠試着讀讀蘋果有關面向對象編程的官方參考資料 Object-Oriented Programming with Objective-C

若是有任何問題,歡迎在評論中提出!

http://www.raywenderlich.com/zh-hans/57314/面向對象程序設計簡介(12)

相關文章
相關標籤/搜索