Objective C继承和多态 Objective C分类(Category)和协议(Protocol) C语言中的特性在Objective C中的应用 Objective C继承和多态 内容概述 继承和组合 OCP设计原则及多态 9.1 继承和组合 在任何的面向对象的语言中继承和组合都是最常用的代码重用方法,Objective C也不例外。我们首先应该能够正确区分何时使用继承何时使用组合。 要区分继承和组合,应该使用经典的"is a"还是"has a"的判断方法,也就是如果一个对象是另外一个对象,那么使用"is a"关系,则使用继承。如果一个对象包含另外一个对象,那么使用"has a"关系,则使用组合。 首先我们来讨论继承,继承类和被继承类之间也成为父子类关系。子类可以继承父类的所有属性和方法。下图显示了动物(Animal)、猫(Cat)和狗之间的继承关系图。 下面通过代码来演示这三个类之间的关系。 定义一个Animal类,该类有一个name属性和一个age属性,还有一个display方法,用来显示name和age。 #import <Foundation/Foundation.h> @interface Animal : NSObject // 姓名属性 @property(nonatomic,strong) NSString *name; // 年龄属性 @property(nonatomic)int age; // 显示姓名和年龄方法 -(void)display; @end Animal类的实现代码,实现display方法输出name和age信息,在init初始化方法中初始化name和age。 #import "Animal.h" @implementation Animal // 显示姓名和年龄方法 -(void)display{ NSLog(@"name=%@,age=%d",self.name,self.age); } - (id)init { self = [super init]; if (self) { self.name = @"动物"; self.age = 1; } return self; } @end 定义Dog类和Cat类,分别继承Animal类。 #import <Foundation/Foundation.h> #import "Animal.h" @interface Dog : Animal @end #import <Foundation/Foundation.h> #import "Animal.h" @interface Cat : Animal @end 测试程序输出结果,可以看到我们并没有在Dog和Cat中定义任何属性和方法,使用到的方法完全继承自Animal. #import <Foundation/Foundation.h> #import "Animal.h" #import "Dog.h" #import "Cat.h" int main(int argc, const char * argv[]) { @autoreleasepool { // 实例化Cat Cat *cat = [[Cat alloc]init]; // 实例化Dog Dog *dog = [[Dog alloc]init]; // 设置cat属性 cat.name = @"花花"; cat.age = 2; // 显示姓名和年龄 [cat display]; // 设置dog属性 dog.name = @"黑豹"; dog.age = 3; // 显示姓名和年龄 [dog display]; } return 0; } 下面我们来看一个组合关系的例子,例如,电脑和CPU以及内存之间的关系就是组合关系,因为他们之间符合电脑中有CPU和内存。其实,实际程序开发中组合关系要远多于继承关系。 下图描述了电脑、CPU和内存之间的关系。 下面通过代码演示这种组合关系。 创建两个类CPU和ROM,并添加name属性。 @interface CPU : NSObject // CPU名称 @property(nonatomic,strong)NSString *name; @end #import <Foundation/Foundation.h> @interface ROM : NSObject // 内存名称 @property(nonatomic,strong)NSString *name; @end 创建一个电脑Computer类,为该类指定name属性,并关联CPU类和ROM类。 #import <Foundation/Foundation.h> #import "CPU.h" #import "ROM.h" @interface Computer : NSObject // 电脑名称 @property(nonatomic,strong)NSString *name; // 关联CPU @property(nonatomic,strong)CPU *cpu; // 关联ROM @property(nonatomic,strong)ROM *rom; -(void)displayMsg; @end Computer类的实现,在初始化方法中设置name、cpu和rom属性,并实现displayMsg方法。 #import "Computer.h" @implementation Computer - (id)init { self = [super init]; if (self) { // 指定电脑名称 self.name = @"Apple"; // 初始化CPU self.cpu = [[CPU alloc]init]; // 设置CPU名称 self.cpu.name = @"英特尔"; // 初始化ROM self.rom = [[ROM alloc]init]; // 指定ROM名称 self.rom.name = @"金士顿"; } return self; } -(void)displayMsg{ // 显示电脑名称 NSLog(@"computer's name=%@",self.name); // 显示CPU名称 NSLog(@"cpu's name=%@",self.cpu.name); // 显示ROM名称 NSLog(@"rom's name=%@",self.rom.name); } @end 测试代码。 // 实例化Computer Computer *cmp = [[Computer alloc]init]; // 显示信息 [cmp displayMsg]; 9.2 OCP设计原则及多态 所谓OCP就是Open Close Principle,即开闭原则,是指软件的结构对扩展是开放的,对修改是关闭的。现有的软件结构可以无限度的扩展,而不能修改现有结构。 为了使软件达到OCP设计原则,就要将软件抽象,把软件的公共部分抽象出接口。然后其他累可以继承或依赖该接口,这样就可以达到OCP设计原则。 多态可以有多个称呼,可以叫向上类型转换、方法动态绑定以及一个方法多种实现等。继承和接口实现都具有多态性。 下面我们还是通过案例来演示OCP设计原则和多态,首先来看一个OCP的例子,例如,一个人要养很多宠物,在这个案例中我们如何设计软件结构达到OCP设计原则呢?人和宠物是直接的关联关系还是抽象出一个接口,让宠物类实现该接口,人关联这个抽象类。要符合OCP设计原则,应该选择第二种方案。 它们的设计图如下所示,在第一种设计中如果要再增加一个宠物,我们必须修改Person类,而在第二种设计中,我们只要添加一个新宠物类就可以,原有的代码并不需要修改。 下面我们看一下代码的实现。首先,看第一种实现方法。步骤如下: 分别创建Dog、Cat和Bird类,并添加name属性。 @interface Dog : NSObject @property(nonatomic,strong)NSString *name; @end @interface Cat : NSObject @property(nonatomic,strong)NSString *name; @end @interface Bird : NSObject @property(nonatomic,strong)NSString *name; @end 创建一个Person类,添加name属性,关联Dog、Cat和Bird类,并添加一个display方法。 @interface Person : NSObject // 姓名 @property(nonatomic,strong)NSString *name; // 关联dog @property(nonatomic,strong)Dog *dog; // 关联cat @property(nonatomic,strong)Cat *cat; // 关联bird @property(nonatomic,strong)Bird *bird; -(void)display; @end Person类的display方法实现。 @implementation Person // 显示信息 -(void)display{ NSLog(@"%@养了:",self.name); NSLog(@"一条%@",self.dog.name); NSLog(@"一只%@",self.cat.name); NSLog(@"一只%@",self.bird.name); } 测试程序结果如下。 #import <Foundation/Foundation.h> #import "Person.h" #import "Dog.h" #import "Cat.h" #import "Bird.h" int main(int argc, const char * argv[]) { @autoreleasepool { // 实例化Person Person *per = [[Person alloc]init]; per.name = @"张三"; // 实例化Dog Dog *dog = [[Dog alloc]init]; // 为dog属性name赋值 dog.name = @"招财狗"; // 为person属性dog赋值 per.dog = dog; // 实例化Cat Cat *cat = [[Cat alloc]init]; cat.name = @"幸福猫"; per.cat = cat; Bird *bird = [[Bird alloc]init]; bird.name = @"报喜鸟"; per.bird = bird; [per display]; } return 0; } 013-04-18 10:17:08.315 chapter09-03[461:303] 张三养了: 2013-04-18 10:17:08.317 chapter09-03[461:303] 一条招财狗 2013-04-18 10:17:08.317 chapter09-03[461:303] 一只幸福猫 2013-04-18 10:17:08.317 chapter09-03[461:303] 一只报喜鸟 下面我们遵循OCP设计原则来改进上述代码,步骤如下: 抽象出一个协议Pet,并添加name属性。 @protocol Pet <NSObject> @property(nonatomic,strong)NSString *name; @end 使得Dog、Pet、Bird都实现该协议。 #import "Pet.h" // 实现Pet协议 @interface Cat : NSObject<Pet> @property(nonatomic,strong)NSString *name; @end #import <Foundation/Foundation.h> #import "Pet.h" // 实现Pet协议 @interface Dog : NSObject<Pet> @property(nonatomic,strong)NSString *name; @end #import <Foundation/Foundation.h> #import "Pet.h" // 实现Pet协议 @interface Bird : NSObject<Pet> @property(nonatomic,strong)NSString *name; @end 使得Person依赖宠物集合数组。 @interface Person : NSObject // 姓名 @property(nonatomic,strong)NSString *name; // 宠物集合数组 @property(nonatomic,strong)NSMutableArray *pets; -(void)display; @end Person的实现类,display方法实现。 #import "Person.h" @implementation Person // 显示信息 -(void)display{ NSLog(@"%@养了:",self.name); for(id<Pet> pet in self.pets){ NSLog(@"%@",pet.name); } } @end 程序测试结果如下。 #import <Foundation/Foundation.h> #import "Pet.h" #import "Person.h" #import "Dog.h" #import "Bird.h" #import "Cat.h" int main(int argc, const char * argv[]) { @autoreleasepool { id<Pet> dog = [[Dog alloc]init]; dog.name = @"招财狗"; id<Pet> cat = [[Cat alloc]init]; cat.name = @"幸福猫"; id<Pet> bird = [[Bird alloc]init]; bird.name = @"报喜鸟"; Person *per = [[Person alloc]init]; per.name = @"Tom"; NSMutableArray *pets = [NSMutableArray arrayWithCapacity:2]; [pets addObject:dog]; [pets addObject:cat]; [pets addObject:bird]; per.pets = pets; [per display]; } return 0; } 2013-04-18 10:41:55.168 chapter09-04[664:303] Tom养了: 2013-04-18 10:41:55.170 chapter09-04[664:303] 招财狗 2013-04-18 10:41:55.170 chapter09-04[664:303] 幸福猫 2013-04-18 10:41:55.171 chapter09-04[664:303] 报喜鸟 下面来看一下什么是多态,可能大家如果没有面向对象的基础理解多态是很困难的。这样我们举个现实生活中的例子,我们说白马、黑马都是马。这句话应该没有错误,其实程序中的多态就是这个意思。也就是任何子类都可以当父类使用。这中关系也叫做向上类型转换。如下图所示。 有时候也把多态特性叫做针对抽象编程,也就是说我们所依赖或继承的类一个是抽象的类或接口。而不是依赖具体的那个类,在程序运行中编译器会动态决定调用那个对象的实际方法。其实,在OCP设计原则一节中我们已经使用到了多态。下面我们还是通过一个案例来演示多态的用法。 该案例以人使用交通工具为例,演示多态的用法,人可以骑自行车、也可以驾驶汽车还可以开游轮。那么,可以将自行车、汽车和游轮抽象成交通工具。人依赖抽象的交通工具,这样人既可以骑车也可以驾驶汽车,还可以开游轮。这样的设计符合OCP设计原则,程序具有多态性。实现步骤如下: 创建一个父类交通工具类Vehicle,并添加run方法。 @interface Vehicle : NSObject -(void)run; @end 分别创建自行车Bicycle、游轮Steamship和汽车Car类,这些类都继承Vehicle类。 @interface Bicycle : Vehicle @end @interface Steamship : Vehicle @end @interface Car : Vehicle @end 定义一个Person类,定义一个使用use方法。 @interface Person : NSObject -(void)use:(Vehicle*)v; @end 实现Person的实现类。 @implementation Person -(void)use:(Vehicle*)v{ [v run]; } @end 测试程序,运行结果如下所示。 int main(int argc, const char * argv[]) { @autoreleasepool { // 实例化Bicycle Bicycle *b = [[Bicycle alloc]init]; // 实例化Steamship Steamship *ss = [[Steamship alloc]init]; // 实例化Car Car *car = [[Car alloc]init]; // 实例化Person Person *per = [[Person alloc]init]; // 使用自行车 [per use:b]; // 使用游轮 [per use:ss]; // 使用汽车 [per use:car]; } return 0; } 2013-04-18 11:29:47.577 chapter09-05[839:303] Bicycle is running! 2013-04-18 11:29:47.579 chapter09-05[839:303] Steamship is running! 2013-04-18 11:29:47.579 chapter09-05[839:303] Car is running! 该案例中使用了向上类型转换、方法动态绑定和依赖抽象等思想。 Objective C分类(Category)和协议(Protocol) C语言中的特性在Objective C中的应用