在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
8,类方法以及私有方法本系列讲座有着很强的前后相关性,如果你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里。 Objective-C里面区别于实例方法,和Java或者C++一样,也支持类方法。类方法(Class Method) 有时被称为工厂方法(Factory Method)或者方便方法(Convenience method)。工厂方法的称谓明显和一般意义上的工厂方法不同,从本质上来说,类方法可以独立于对象而执行,所以在其他的语言里面类方法有的时候被称为静态方法。就像@interface曾经给我们带来的混乱一样,现在我们就不去追究和争论工厂方法的问题了,我们看到Objective-C的文章说工厂方法,就把它当作类方法好了。 在Objective-C里面,最受大家欢迎的类方法应该是alloc,我们需要使用alloc来为我们的对象分配内存。可以想象,如果没有alloc,我们将要如何来为我们的类分配内存! 和其他的语言类似,下面是类方法的一些规则,请大家务必记住。 1,类方法可以调用类方法。 2,类方法不可以调用实例方法,但是类方法可以通过创建对象来访问实例方法。 3,类方法不可以使用实例变量。类方法可以使用self,因为self不是实例变量。 4,类方法作为消息,可以被发送到类或者对象里面去(实际上,就是可以通过类或者对象调用类方法的意思)。 如果大家观察一下Cocoa的类库,会发现类方法被大量的应用于方便的对象创建和操作对象的,考虑到类方法的上述的特性,同学们在设计自己的类的时候,为了谋求这种方便,可以考虑使用类方法来创建或者操作对象。笔者认为,这个就是类方法的潜规则,在本章的范例程序里面,笔者将要遵守这个潜规则。 在上一章我们讲了一下实例变量的作用域,实例变量的作用域的方式和其他面向对象的语言没有什么不同。对于方法,非常遗憾的是,Objective-C并没有为我们提供诸如public,private和protected这样的限定,这就意味着在Objective-C里面,从理论上来说所有的方法都是公有的。但是,我们可以利用Objective-C的语言的特性,我们自己来实现方法的私有化。当然我们自己的私有化手段没有得到任何的编译器的支持,只是告诉使用者:“这是一个私有的方法,请不要使用这个方法”。所以,无论作为类的设计者和使用者都应该清楚在Objective-C里面的方法私有化的所有手段,这样就在类的设计者和使用者之间达成了一种默契,这种方式明显不是Objective-C语法所硬性规定的,所以也可以把这种手法成为一种潜规则。 本章所述的方法的私有化是一种有缺陷的手段,有一定的风险而且也没有完全实现私有化,在后面的章节里面笔者会陆续的给出其他的实现方法私有化的方法。 另外,Objective-C里面有一个其他不支持指针的语言没有的一个动态特性,那就是程序在执行的时候,可以动态的替换类的手段。动态的方法替换有很多种应用,本章实现了一个类似java里面的final函数。和final函数不同的是,如果子类重写了这个方法,编译器不会报错,但是执行的时候总是执行的你的超类的方法。 类方法,方法私有化和动态方法替换将是本章的主题。 8.1,本章程序的执行结果在本章里面,我们将要继续使用我们在第4章已经构筑好的类Cattle和Bull。 笔者在这里暂时违反一下不修改已经生效的代码规则改写了一下Cattle和Bull类,在里面追加了一些类方法,用于创建Cattle系列的对象。 笔者也改写了Cattle的头文件用来实现方法的私有化。 面向对象的程序有一个很大的特色就是动态性,但是由于某种原因我们在设计超类的时候,也许会考虑把某个方法设定成为静态的,这样就有了诸如final的概念。在本章我们将要使用动态的方法替换来实现这个功能。我们将要构筑一个新类,名字叫做UnknownBull,我们使用动态方法替换导致即使UnknownBull重载了Cattle类的saySomething,但是向UnknownBull发送saySomething的时候,仍然执行的是Cattle的saySomething。本章程序的执行结果请参照下图:
图8-1,本章程序的执行结果。 本章程序可以点击这里下载。 8.2,实现步骤第一步,按照我们在第2章所述的方法,新建一个项目,项目的名字叫做07-InitWithAndIvarScope。如果你是第一次看本篇文章,请到这里参看第二章的内容。 第二步,按照我们在第4章的4.2节的第二,三,四步所述的方法,把在第4章已经使用过的“Cattle.h”,“Cattle.m”,“Bull.h”还有“Bull.m”, 导入本章的项目里面。 第三步,打开“Cattle.h”和“Cattle.m”,分别修改成为下面的代码并且保存:
#import <Foundation/Foundation.h>
@interface Cattle : NSObject { int legsCount; } - (void)saySomething; + (id) cattleWithLegsCountVersionA:(int) count; + (id) cattleWithLegsCountVersionB:(int) count; + (id) cattleWithLegsCountVersionC:(int) count; + (id) cattleWithLegsCountVersionD:(int) count; @end
#import "Cattle.h"
#import <objc/objc-class.h> @implementation Cattle -(void) saySomething { NSLog(@"Hello, I am a cattle, I have %d legs.", legsCount); } -(void) setLegsCount:(int) count { legsCount = count; } + (id) cattleWithLegsCountVersionA:(int) count { id ret = [[Cattle alloc] init]; //NEVER DO LIKE BELOW //legsCount = count; [ret setLegsCount:count]; return [ret autorelease]; } + (id) cattleWithLegsCountVersionB:(int) count { id ret = [[[Cattle alloc] init] autorelease]; [ret setLegsCount:count]; return ret; } + (id) cattleWithLegsCountVersionC:(int) count { id ret = [[self alloc] init]; [ret setLegsCount:count]; return [ret autorelease]; } + (id) cattleWithLegsCountVersionD:(int) count { id ret = [[self alloc] init]; [ret setLegsCount:count]; if([self class] == [Cattle class]) return [ret autorelease]; SEL sayName = @selector(saySomething); Method unknownSubClassSaySomething = class_getInstanceMethod([self class], sayName); //Change the subclass method is RUDE! Method cattleSaySomething = class_getInstanceMethod([Cattle class], sayName); //method_imp is deprecated since 10.5 unknownSubClassSaySomething->method_imp = cattleSaySomething->method_imp; return [ret autorelease]; } @end 第四步,打开“Bull.h”和“Bull.m”,分别修改成为下面的代码并且保存:
#import <Foundation/Foundation.h>
#import "Cattle.h" @interface Bull : Cattle { NSString *skinColor; } - (void)saySomething; - (NSString*) getSkinColor; - (void) setSkinColor:(NSString *) color; + (id) bullWithLegsCount:(int) count bullSkinColor:(NSString*) theColor; @end
#import "Bull.h"
@implementation Bull -(void) saySomething { NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount); } -(NSString*) getSkinColor { return skinColor; } - (void) setSkinColor:(NSString *) color { skinColor = color; } + (id) bullWithLegsCount:(int) count bullSkinColor:(NSString*) theColor { id ret = [self cattleWithLegsCountVersionC:count]; [ret setSkinColor:theColor]; //DO NOT USE autorelease here! return ret; } @end 第五步,创建一个新类,名字叫做“UnknownBull”,然后分别打开“UnknownBull.h”和“UnknownBull.m”,分别修改成为下面的代码并且保存:
#import <Foundation/Foundation.h>
#import "Bull.h" @interface UnknownBull : Bull { } -(void)saySomething; @end
#import "UnknownBull.h"
@implementation UnknownBull -(void)saySomething { NSLog(@"Hello, I am an unknown bull."); } @end 第六步,打开“08-Class_Method_And_Private_Method.m” ,修改成为下面的样子并且保存
#import <Foundation/Foundation.h>
#import "Cattle.h" #import "Bull.h" #import "UnknownBull.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; id cattle[5]; cattle[0] = [Cattle cattleWithLegsCountVersionA:4]; cattle[1] = [Bull cattleWithLegsCountVersionB:4]; cattle[2] = [Bull cattleWithLegsCountVersionC:4]; cattle[3] = [Bull bullWithLegsCount:4 bullSkinColor:@"red"]; cattle[4] = [UnknownBull cattleWithLegsCountVersionD:4]; for(int i = 0 ; i < 5 ; i++) { [cattle[i] saySomething]; } [pool drain]; return 0; } 第七步,选择屏幕上方菜单里面的“Run”,然后选择“Console”,打开了Console对话框之后,选择对话框上部中央的“Build and Go”,如果不出什么意外的话,那么应该出现入图8-1所示的结果。如果出现了什么意外导致错误的话,那么请仔细检查一下你的代码。如果经过仔细检查发现 还是不能执行的话,可以到这里下载笔者为同学们准备的代码。 如果笔者的代码还是不能执行的话,请告知笔者。 8.2,方法的私有化
在讲述方法私有化之前,我们首先要提到一个Objective-C里面的一个概念,动态类型和静态类型。 所谓的动态类型,就是使用id来定义一个对象,比如说
id cattle = [[Cattle alloc] init];
所谓的静态类型,就是使用已知变量的的类型来定义对象,比如说
Cattle cattle = [[Cattle alloc] init];
动态类型和静态类型各有好处,动态类型实现了多态性,使用静态类型的时候编译器会为你检查一下也许会出现危险的地方,比如说向一个静态类型的对象发送一个它没有定义的消息等等。 好的,我们现在打开“cattle.h”,大家可以发现,和以前的版本相比,我们的“cattle.h”少了一个方法的定义,那就是-(void) setLegsCount:(int) count;。笔者在本章的范例程序里面实现私有方法的手段比较简单,直接把-(void) setLegsCount:(int) count从“cattle.h”给删除掉了。 大家打开““cattle.m”,可以看到里面-(void) setLegsCount:(int) count是有实现部分的。实现部分和过去的版本没有任何区别的。 我们本章里面讲述的实现方法私有化的手段,就是从头文件当中不写方法的声明。这样做会导致如下几个现象 1,在类的实现文件.m里面,你可以向平常一样使用[self setLegsCount:4] 来发送消息,但是确省设定的编译器会很不礼貌的给你一个警告。 2,你可以向Cattle以及从Cattle继承的类的静态对象发送setLegsCount:4的消息,但是同样,确省设定的编译器会很不礼貌的给你一个警告。 3,你可以向Cattle以及从Cattle继承的类的动态对象发送setLegsCount:4的消息,编译器不会向你发送任何警告的。 说到这里,同学们也许会觉得这一节的方法私有化有一点奇怪,因为在上面的第二条里面,不能阻止对对象的私有方法进行调用。令我们更为恼火的是,居然在我们自己的类的实现文件里面需要调用的时候产生诸如第一条的警告! 让我们冷静一下。 我们说,在面向对象的程序里面,一般而言类的使用者只关心接口,不关心实现的。当我们类的实现部分的某个方法,在头文件里面没有定义的话,那么由于我们的类的使用者只是看头文件,所以他不应该是用我们定义的所谓的私有方法的。这一点,对于其他的语言来说也是一样的,其他的语言的私有方法和变量,如果我们把它们改为public,或者我们不修改头文件,使用指针也可以强行的访问到私有的变量和方法的,从这个角度上来说,私有化的方法和变量也只不过是一个摆设而已,没有人可以阻止我们去访问他们,探求埋藏在里面的奥秘。所谓的私有化只不过是一个潜规则而已,在正常的时候,我们大家都会遵守这个潜规则的。但是被逼无奈走投无路的时候我们也许会除了访问私有的东西无可选择。但是也不能过分,我们显然不可以把访问私有变量和函数当作一种乐趣。 说到这里,我想大家应该可以理解这种私有化方法的定义了。它只不过是一种信号,告诉类的使用者,“这是一个私有的函数,请不要使用它,否则后果自负” 。我们在看到别人的代码的时候看到了这种写法的时候,或者别人看到我们的代码的时候,大家都需要做到相互理解对方的隐藏私有部分的意图。还是还是这句话,在大多数时候,请不要破坏潜规则。 8.3, 类方法
|
请发表评论