我們都知道objc是一門面向?qū)ο蟮恼Z言,面向?qū)ο笠步o我們平時(shí)帶來了很多的方便。然而很多情況下面向?qū)ο笠灿凶约旱木窒扌?,濫用繼承多態(tài)可能會(huì)埋下很多坑。
然后今天看了Casa大神的跳出面向?qū)ο笏枷?二) 多態(tài),真的非常受益,是個(gè)解決繼承與多態(tài)過程中避免坑的好辦法。如果您看過這篇文章并且對此理解的很透徹的話可以直接不要讀下面的部分了,以下是個(gè)人對Casa這篇文章的一些淺見與實(shí)踐。
首先,傳統(tǒng)寫法的多態(tài)有什么樣的問題
之前在一家互聯(lián)網(wǎng)公司是做信用卡導(dǎo)入的,以下就以這個(gè)為背景吧,僅以此為背景而已,后面所說的與公司代碼及結(jié)構(gòu)毫無關(guān)系。
信用卡導(dǎo)入流程中很重要的步驟是銀行的賬戶登錄以及信用卡流水解析,需要針對不同銀行的數(shù)據(jù)進(jìn)行處理。傳統(tǒng)的寫法如下:
//BillImport.h
@interface BillImport : NSObject
- (void)billHandle;
- (void)analyze;
@end
//BillImport.m
@implementation BillImport
- (void)billHandle {
[self bankLogin];
[self analyze];
}
- (void)bankLogin {
NSLog(@"bank login");
}
- (void)bankImport {
NSLog(@"bank bill import");
}
- (void)analyze {
//to be override
}
如果想寫工商銀行的導(dǎo)入應(yīng)該覆蓋bankLogin與analyze,如下:
#import "BillImport.h"
@interface ICBCBillImport : BillImport
@end
@implementation ICBCBillImport
- (void)analyze {
NSLog(@"%@:%s", NSStringFromClass([self class]), __FUNCTION__);
}
@end
那么如此的設(shè)計(jì)有什么樣的缺陷呢?
a. 我們假設(shè)架構(gòu)師來實(shí)現(xiàn)父類,業(yè)務(wù)工程師來實(shí)現(xiàn)子類,那么哪些方法需要被覆蓋重載,哪些不需要?極其容易造成疑惑從而侵蝕原本的代碼邏輯
b. 父類中的analyze一個(gè)空方法掛在那里,意義不大
c.子類中可能引入很多外部邏輯,加大復(fù)用的難度。
那么使用面向接口編程的思想(IOP)后,這段代碼可以如下設(shè)計(jì):
// BillImport.h
@protocol BillHandleManager <NSObject>
- (void)bankLogin;
- (void)analyze;
@end
@interface BillImport : NSObject
@property (nonatomic, weak) id<BillHandleManager> assistant;
- (void)billHandle;
@end
// BillImport.m
@implementation BillImport
- (void)billHandle {
[self.assistant bankLogin];
[self.assistant analyze];
}
@end
// ICBCBillImport.h
@interface ICBCBillImport : BillImport <BillHandleManager>
@end
// ICBCBillImport.m
@implementation ICBCBillImport
- (instancetype)init {
self = [super init];
if (self) {
self.assistant = self;
}
return self;
}
- (void)analyze {
NSLog(@"ICBC analyze");
}
- (void)bankLogin {
NSLog(@"ICBC bankLogin");
}
@end
如上,僅僅是增加了一個(gè)增加了一個(gè)assistant對象,就把這層原本需要覆蓋重載的關(guān)系“轉(zhuǎn)移”了。
那么這樣做有什么樣的好處呢?
a.原本需要覆蓋重載的方法,不放在父類的聲明中,而是放在接口中去實(shí)現(xiàn)??梢栽诠緝?nèi)部做出規(guī)定,不允許覆蓋重載父類中的方法;而子類需要實(shí)現(xiàn)接口協(xié)議中的方法,從而避免了繼承上的困惑和代碼的侵蝕
b.父類中無需寫一個(gè)虛基類一樣的空方法了。
c.子類中如果引入其他邏輯,因?yàn)槭敲嫦蚪涌诰幊?,所以原本引入了不相關(guān)的邏輯,也很容易剝離。
最后,說下如上方法是如何解決Casa提到的多態(tài)的四種問題的:
父類有部分public的方法是不需要,也不允許子類覆重
上面提到了,父類聲明中的方法不允許子類覆蓋重載,需要實(shí)現(xiàn)的功能放入接口中實(shí)現(xiàn)
父類有一些特別的方法是必須要子類去覆重的,在父類的方法其實(shí)是個(gè)空方法
利用接口實(shí)現(xiàn)避免掛一個(gè)空方法在代碼中
父類有一些方法是可選覆重的,一旦覆重,則以子類為準(zhǔn)
這里可以通過設(shè)計(jì)一個(gè)攔截器來實(shí)現(xiàn),比如在上面的代碼中做如下更改:
// BillImport.h
@protocol BillHandleManager <NSObject>
- (void)bankLogin;
- (void)analyze;
@end
@protocol Interceptor <NSObject>
- (void)willBeginBillHandle;
- (void)didFinishBillHandle;
@end
@interface BillImport : NSObject
@property (nonatomic, weak) id<BillHandleManager> assistant;
@property (nonatomic, weak) id<Interceptor> interceptor;
- (void)billHandle;
@end
// BillImport.m
@implementation BillImport
- (void)billHandle {
if ([self.interceptor respondsToSelector:@selector(willBeginBillHandle)]) {
[self.interceptor willBeginBillHandle];
}
[self.assistant bankLogin];
[self.assistant analyze];
if ([self.interceptor respondsToSelector:@selector(didFinishBillHandle)]) {
[self.interceptor didFinishBillHandle];
}
}
@end
通過這樣的設(shè)計(jì),可以解決比如產(chǎn)品運(yùn)營想讓我們統(tǒng)計(jì)下每天發(fā)生的導(dǎo)入次數(shù)。如果按照過去的設(shè)計(jì),我們只能無奈的在子類中去覆蓋父類的方法?,F(xiàn)在只需要實(shí)現(xiàn)這個(gè)接口便可以實(shí)現(xiàn)了。所以很多時(shí)候父類的設(shè)計(jì)很大程度決定了自身邏輯被侵蝕的問題。
父類有一些方法即便被覆重,父類原方法還是要執(zhí)行的
因?yàn)槲覀円?guī)定了父類方法不可以被覆蓋重載,只能通過接口實(shí)現(xiàn)的方式來寫,所以這個(gè)問題便不存在了。