Objective-C 基礎教程第五章,複合

VxerLee暱稱已被使用發表於2022-03-06

Objective-C 基礎教程第五章,複合

什麼是複合?

程式設計中的複合(composition)就好像音樂中的作曲(composition)一樣:將多個元件組合在一起,配合使用,從而得到完整的作品。

Car程式

接下來我們不再用shape作為例子來寫程式碼了,這次用car作為例子寫程式碼,我們先來看看如何搭建汽車模型。

1輛汽車只需要1臺發動機和4個輪胎。

mainCarParts.m

#import <Foundation/Foundatin.h>

/*
 汽車輪胎
 */
@interface Tire : NSObject
@end

@implementation Tire
- (NSString*) description{
    return (@"I' am a tire. I last a while.");
}
@end
/*
 汽車發動機
 */
@interface Engine : NSObject
@end

@implementation Engine
- (NSString*) description{
    return (@"I am an engine. Vroom!");
}
@end
/*
 * 汽車
 */
@interface Car: NSObject
{
  //複合概念
  Engine *engine;
  Tire *tires[4]; //因為engine和tires是Car類的例項變數,所以他們是複合的。
                  //你可以說   汽車是由4個輪胎和一個發動機組成的。(但是人們一般不會這麼說)
}                 //可以這麼說 汽車有4個輪胎和一個發動機
-(void) print;
@end
  
@implementation Car//Car的init方法建立了4個新輪胎 並賦值給tires陣列,還建立了一臺傳送機並賦值給engine例項變數。
 -(id) init{
  if(self == [super init]){
    engine = [Engine new];
    tires[0] = [Tire new];
    tires[1] = [Tire new];
    tires[2] = [Tire new];
    tires[3] = [Tire new];
  }
  return (self)
}

-(void) print
{
  NSLog(@"%@",engine);
  for(int i=0;i<4;i++)
  {
    NSLog(@"%@",tires[i]);
  }
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *car = [Car new];
        [car print];
    }
    return 0;
}

Tips:(如果類中沒有包含任何例項變數,便可以省略掉介面定義中的花括號。)

自定義NSLog()

NSLog可以通過%@來輸出物件。NSLog處理%@說明符時,會詢問引數列表中相應的物件以得到這個物件的描述。

技術原理上是NSLog給這個物件傳送了description訊息,然後物件的description方法生成了一個NSString並將其返回。在類中提供description方法就可以自定義NSLog()會如何輸出物件。

存取方法get Set

程式設計人員很少會對自己編寫的程式感到滿意,因為軟體開發是永無止境的,永遠有bug要去修正。

我們可以使用存取方法來改進它,使它的程式碼更靈活。

存取(accessor)方法是用來讀取或改變某個物件屬性的方法。

settter存取方法之存方法。

getter存取方法之取方法。

我們在寫程式碼的時候最好不要直接改變類例項變數的值,比如在main函式中car->engine改變值,應該使用setter方法改變,這也算是間接工作的一個例子。

@interface Car:NSObject
{
  Engine *engine;
  Tire *tires[4];
}
-(Engine *)engine;
-(void) SetEngine:(Engine *) newEngine;
-(Tire*) tireAtIndex:(int) index;
-(void) SetTire:(Tire*) tire atIndex:(int) index;
-(void) print;
@end
@implementation Car
- (Engine*) engine
{
  return (engine);
}
-(void) SetEngine:(Engine*) newEngine
{
  engine = newEngine;
}
@end

 //getter方法engine返回例項變數engine的當前值,Objective-C中所有物件間的互動都是通過指標實現的。
 //setter方法讓engine指標 指向了newEngine,
int main()
{
  Engine *engine = [Engine new];
  [car SetEngine:engine];
  NSLog(@"the car's engine is %@",[car engine]);
}

Tires(輪胎) 存取方法

-(void) setTire:(Tire*) tire atIndex:(int) index;
-(Tire*) tireAtIndex:(int) index;

由於汽車的4個輪胎都有自己不同的位置,所以Car物件中包含一個輪胎的陣列。

在這裡我們需要用索引存取器而不能直接訪問tires陣列。

- (void)setTire:(Tire*) tire atIndex:(int) index
{
  if( index < 0 || index > 3)
  {
    NSLog(@"bad index(%d) in setTire:atIndex:",index);
    exit(1);
  }
  tire[index] = tire;
}

- (Tire*) tireAtIndex:(int) index
{
  if( index<0 || index>3)
  {
    NSLog(@"bad index(%d) in tireAtIndex:",index);
  }
  return (tires[index]);
}
Tire *tire = [Tire new];
[car setTire:tire atIndex:2];
NSLog(@"tire number two is %@",[car tireAtIndex:2]);

Car類程式碼的其他變化

Car類的init方法。因為我們有了能夠訪問engine和tire變數的方法,所以我們不再需要建立這兩個變數了,可以直接從外部進行控制,(而是由建立汽車的程式碼來負責配置發動機和輪胎)。所以我們可以完全剔除init方法

ps:新車的車主會得到一輛沒有輪胎和發動機的汽車,不過裝配是輕而易舉的事(有時軟體中的生活比現實生活要容易的多啊 ? )。

#import <Foundtation/Foundation.h>
int main(int argc,const char*argv[])
{
  //新車的車主購買一臺新汽車
  Car *car = [Car new];
  
  //車主繼續購買一個引擎 4個輪胎
  Engine *engine = [Engine new];
   //組裝上引擎
  [car setEngine:engine];
  for(int i=0;i<4;i++)
  {
    Tire *tire = [Tire new];
     //組裝上輪胎
    [car setTire:tire atIndex:i];
  }
  
  //開上我們心愛的小車車
  [car print];
  return (0);
}

擴充套件Car程式

現在我們Car類已經有了存取方法,我們可以更加充分的利用它,來擴充套件我們的Car,比如我們可以給汽車弄個新的具體什麼型號的發動機(Slant6),或者具體什麼型號 牌子的輪胎(米其林)。

@interface Slant6 :Engine//新型發動機 繼承自發動機介面
@end
  
@implementation Slant6
- (NSString *) description
{
  return (@"I am a slant- 6. VROOM! VROOM!");
}
@end
@interface MicheLin : Tire
@end
  
@implementation MicheLin
- (NSString *)description
{
  return (@"I am a tire , Extraordinary driving and driving experience(非凡駕控,持久體驗)");
}
@end
#import <Foundation/Foundation.h>

int main(int argc,const char*argv[])
{
  //新車的車主購買一臺新汽車
  Car *car = [Car new];
  
  //車主繼續購買一個Slant6引擎 4個米其林輪胎
  Engine *engine = [Slant6 new];
   //組裝上引擎
  [car setEngine:engine];
  for(int i=0;i<4;i++)
  {
    Tire *tire = [MicheLin new];
     //組裝上輪胎
    [car setTire:tire atIndex:i];
  }
  
  //開上我們更加心愛的小車車
  [car print];
}
2022-03-06 18:20:43.438697+0800 Car[13853:1512359] I am a slant- 6. VROOM! VROOM!
2022-03-06 18:20:43.439184+0800 Car[13853:1512359] I am a tire , Extraordinary driving and driving experience(非凡駕控,持久體驗)
2022-03-06 18:20:43.439286+0800 Car[13853:1512359] I am a tire , Extraordinary driving and driving experience(非凡駕控,持久體驗)
2022-03-06 18:20:43.439333+0800 Car[13853:1512359] I am a tire , Extraordinary driving and driving experience(非凡駕控,持久體驗)
2022-03-06 18:20:43.439371+0800 Car[13853:1512359] I am a tire , Extraordinary driving and driving experience(非凡駕控,持久體驗)

複合還是繼承

我們的Car程式同時用到了繼承和複合概念,也就是我們第一章和本章中所介紹的兩個萬能工具,那麼什麼時候用繼承,什麼時候用複合呢?這個問題問的不錯。

  • 繼承的類之間建立的關係為is a(是一個)。比如三角形是一個形狀,Slant6是一個發動機,米其林是一種輪胎的名字。簡單來說就是總類和一個細分類的關係。
  • 複合的類之間建立的關係為has a(有一個)。形狀有一個填充顏色,汽車有一個發動機和4個輪胎。與繼承不同,汽車不是一個發動機,也不是一個輪胎。簡單來說就是它是由什麼構成,它擁有什麼,那麼就可以用複合。

新手在物件導向程式設計時候經常會犯這樣的錯誤:對任何東西都想使用繼承,比如讓Car類繼承自Engine類,這樣可是不行的。我們得套用公司:is s(是一個)的關係來建立物件導向程式設計繼承的概念,藍貓是一個貓,狸花貓是一個貓。這種就可以用繼承的方式了。複合則套用has a(有一個)的公式,貓有一個嘴巴,兩隻眼睛,兩隻耳朵,一條尾巴等等。

實用的例子:這次假設你的程式可能會涉及有照車輛,就是需要某種執照才能合法駕駛的車輛。汽車、摩托車、牽引車都是有照車輛,套用公式is a(是一個):汽車是一個有照車輛、摩托車是一個有照車輛。所以可以用繼承。建立一個類LicensedVehicle(有照車輛),再套用公式has a(有一個),有照車輛有一個牌照 (牌照所在地、牌照號碼)(複合方式)。

//牌照
@interface Licenseplate
{
  NSString Address;
  NSString Number;
}
@end
//有照車輛
@interface LicensedVehicle : NSObject
{
  Licenseplate licsplate; //複合概念
}
@end
  
//繼承概念
@interface Car :LicensedVehicle 
@end
  
@interface MotorCycle :LicensedVehicle
@end

小結

複合是OOP的基礎概念,我們通過這種技巧來建立 引用其他類的物件,在本章中用了Car來做了很多複合的例子,還介紹了OC中比較重要的存取方法,存取方法和複合是密不可分的,我們以後應該規範化用settergetter方法。

最後還介紹了Cocoa存取方法的命名規則,不能使用get作為getter方法的名字,而是應該直接使用屬性名,因為get名規則另有用途。

相關文章