首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

深入解析 Objective-C 繼承與多態機制

     編輯:關於C語言

Objective-C 繼承與多態核心概念

面向對象程式設計之所以成為主流範式,核心支柱便是繼承與多態。雖然各大 OOP 語言皆支援這兩大特性,但實作細節各有差異。Objective-C 與 Java 同樣採用單一繼承模型,不支援多重繼承;反觀 C++ 則允許多重繼承。關於 Objective-C 為何捨棄多重繼承,稍後將詳細探討。

繼承:減少冗餘、遵循 DRY 原則

繼承關係常被比喻為「人是人他媽生的,妖是妖他媽生的」——出自《大話西游》。人與妖同屬動物類別,卻各擁獨特屬性與行為。動物為父類別,人、妖為子類別。繼承的本質在於消除代碼重複,貫徹 DRY 原則,讓子類別自動擁有父類別的屬性與方法,並可擴展或覆寫專屬功能。

關鍵字:super 與 self

在 Objective-C 中,super 指向直接父類別,self 指向當前實例,相當於 Java 的 this。值得注意的是,在 @implementation 中可定義 @interface 未聲明的方法,這類方法屬私有,僅供類別內部使用。

NSObject 核心方法詳解

Objective-C 幾乎所有類別皆繼承自 NSObject,該類別提供大量強大方法。以下針對常用方法進行實測與說明:

1. +(void)load

類別載入至運行環境時自動調用,屬類別方法,無需實例化即可觸發。

// 重寫 NSObject 中的 load 方法
+(void) load {
    NSLog(@"ObjectTest 中的 load 方法被調用啦!!");
}

運行結果:
2014-07-30 08:58:31.704 HelloOC[550:303] ObjectTest中的load方法被調用啦!!

2. +(void)initialize

類別首次被使用時調用,後續不再重複呼叫。

// 重寫 initialize 方法
+(void) initialize {
    NSLog(@"initialize 方法被調用了(類第一次被實例化)!");
}

運行結果:
2014-07-30 09:27:53.767 HelloOC[624:303] load方法被調用啦!!
2014-07-30 09:27:53.769 HelloOC[624:303] initialize方法被調用了(類第一次被實例化)!

3. 記憶體分配與初始化:alloc、init、new

  • + (id)alloc:分配記憶體並返回對象
  • - (id)init:對已分配記憶體的實例進行初始化
  • new:等同於同時呼叫 alloc 與 init

可於子類別中重寫 alloc 與 init 觀察調用順序:

// 重寫 alloc 方法
+(id) alloc {
    NSLog(@"alloc函數被調用啦");
    return [super alloc];
}

// 重寫 init 方法
-(id) init {
    NSLog(@"init被調用了");
    self = [super init];
    return self;
}

測試代碼與結果:

// 第一次使用 ObjectTest 類
ObjectTest *o1 = [[ObjectTest alloc] init];
ObjectTest *o2 = [ObjectTest new];

輸出顯示 new 內部確實依序呼叫 alloc 與 init。

4. - (void)dealloc

釋放對象自身佔用的記憶體。

5. 類別與父類別查詢

// 返回當前對象所屬類別
NSString *className = (NSString *)[self class];
NSLog(@"%@類的display方法", className);

// 返回當前對象的父類別
NSString *superClassName = (NSString *)[self superclass];
NSLog(@"%@類的父類是%@", className, superClassName);

6. isKindOfClass: 判斷實例是否屬於某類別或其子類別

BOOL b = [o2 isKindOfClass:[ObjectTest class]];
if (b == YES) {
    NSLog(@"o2是ObjectTest類的對象");
}

7. isMemberOfClass: 僅判斷實例是否屬於某特定類別(不含繼承鏈)

BOOL result = [o2 isMemberOfClass:[NSObject class]];
if (result == NO) {
    NSLog(@"isMemberOfClass不能判斷是否為NSObject子類的對象");
}

8. description: 返回對象描述字串,便於除錯

NSString *descript = [o2 description];
NSLog(@"%@", descript);

輸出: <ObjectTest: 0x100206650>

9. hash: 返回對象哈希值

NSUInteger hash = [o2 hash];
NSLog(@"%p", hash);

輸出: 0x100107b70

10. isEqual: 比較兩個對象是否相同

預設比較記憶體位址,若相等則 hash 值必相同。

NSString *str1 = @"111";
NSString *str2 = str1;
if ([str2 isEqual:str1] == YES) {
    NSLog(@"str2 == str1");
} else {
    NSLog(@"str2 != str1");
}

繼承實戰:富人與富二代範例

繼承關係屬「is-a」關係:貓是動物,動物為父類,貓為子類。子類擁有父類屬性與行為,並可擴展專屬特性——「父類更一般,子類更具體」。

1. 定義富人類別

// Richer.h
#import 

@interface Richer : NSObject {
    @protected
    NSString *name;
    int age;
    NSString *gender;
}

@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int age;
@property (copy, nonatomic) NSString *gender;

// 便利初始化方法
- (id)initWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender;
// 便利建構器
+ (id)richerWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender;
// 刷卡方法
- (void)poss;

@end

2. 實作富人類別

// Richer.m
#import "Richer.h"

@implementation Richer

@synthesize name, age, gender;

// 便利初始化
- (id)initWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender {
    if (self = [super init]) {
        self->name = vName;
        self->age = vAge;
        self->gender = vGender;
    }
    return self;
}

// 便利建構器
+ (id)richerWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender {
    Richer *richer = [[Richer alloc] initWithName:vName AndAge:vAge AndGender:vGender];
    return richer;
}

// 刷卡方法
- (void)poss {
    NSLog(@"%@有錢你就刷吧", name);
}
@end

3. 定義富二代類別(繼承富人)

// Richer2nd.h
#import "Richer.h"

@interface Richer2nd : Richer

@property (copy, nonatomic) NSString *hobby;

// 便利初始化
- (id)initWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender AndHobby:(NSString *)vHobby;
// 便利建構器
+ (id)richer2ndWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender AndHobby:(NSString *)vHobby;
// 新增方法
- (void)myHobby;

@end

4. 實作富二代類別

// Richer2nd.m
#import "Richer2nd.h"

@implementation Richer2nd

@synthesize hobby;

// 便利初始化:復用父類初始化
- (id)initWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender AndHobby:(NSString *)vHobby {
    if (self = [super initWithName:vName AndAge:vAge AndGender:vGender]) {
        self->hobby = vHobby;
    }
    return self;
}

// 便利建構器
+ (id)richer2ndWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender AndHobby:(NSString *)vHobby {
    Richer2nd *richer = [[Richer2nd alloc] initWithName:vName AndAge:vAge AndGender:vGender AndHobby:vHobby];
    return richer;
}

// 重寫刷卡方法
- (void)poss {
    [super poss];
    NSLog(@"我是富二代,我爸有錢,我就刷!");
}

// 新增愛好方法
- (void)myHobby {
    NSLog(@"我是富二代%@,我超喜歡%@", name, hobby);
}
@end

5. 運行結果

2014-07-30 08:38:12.956 HelloOC[483:303] Bill有錢你就刷吧
2014-07-30 08:38:12.957 HelloOC[483:303] BILL`s son有錢你就刷吧
2014-07-30 08:38:12.958 HelloOC[483:303] 我是富二代,我爸有錢,我就刷!
2014-07-30 08:38:12.958 HelloOC[483:303] 我是富二代BILL`s son,我超喜歡飙車

多態:同一方法、不同反應

多態簡單來說,就是不同對象響應同一訊息時產生不同行為。Objective-C 中動態類型 id 是實現多態的關鍵,id 可指向任意對象類型。

範例 1:以 id 統一呼叫

id richer = nil;

// 測試富人類
richer = [Richer richerWithName:@"Bill" AndAge:40 AndGender:@"Man"];
[richer poss];

// 測試富二代類
richer = [Richer2nd richer2ndWithName:@"BILL`s son" AndAge:16 AndGender:@"男" AndHobby:@"飙車"];
[richer poss];
[richer myHobby];

輸出結果與繼承範例一致,證明同一變數指向不同實例時,呼叫相同方法產生不同行為。

範例 2:父類別指標指向子類別實例

Animal *animal = nil;

// 實例化貓
animal = [Cat new];
[animal eat];

// 實例化狗
animal = [Dog new];
[animal eat];

父類別指標 animal 依序指向 Cat、Dog 實例,呼叫 eat 時分別執行子類別覆寫的方法,即為多態體現。

設計原則:OCP 與 LSP

  • OCP (Open Closed Principle):對擴展開放,對修改關閉。新增功能應透過擴展而非修改既有代碼。
  • LSP (Liskov Substitution Principle):里氏替換原則。任何基類別可出現之處,子類別必能替換且不影響程式正確性。

這兩大原則是設計健壯、可維護繼承體系的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved