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中比較重要的存取方法
,存取方法和複合是密不可分的,我們以後應該規範化用setter
、getter
方法。
最後還介紹了Cocoa存取方法的命名規則,不能使用get作為getter
方法的名字,而是應該直接使用屬性名,因為get名規則另有用途。