Objective C继承和多态


Objective C继承和多态

内容概述

  • 继承和组合
  • OCP设计原则及多态

9.1 继承和组合

在任何的面向对象的语言中继承和组合都是最常用的代码重用方法,Objective C也不例外。我们首先应该能够正确区分何时使用继承何时使用组合。

要区分继承和组合,应该使用经典的"is a"还是"has a"的判断方法,也就是如果一个对象是另外一个对象,那么使用"is a"关系,则使用继承。如果一个对象包含另外一个对象,那么使用"has a"关系,则使用组合。

首先我们来讨论继承,继承类和被继承类之间也成为父子类关系。子类可以继承父类的所有属性和方法。下图显示了动物(Animal)、猫(Cat)和狗之间的继承关系图。

图 9.1 继承关系

下面通过代码来演示这三个类之间的关系。

  1. 定义一个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
  1. 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
  1. 定义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
  1. 测试程序输出结果,可以看到我们并没有在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和内存之间的关系。

图 9.2 组合关系

下面通过代码演示这种组合关系。

  1. 创建两个类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
  1. 创建一个电脑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
  1. 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&#39;s name=%@",self.name);

    // 显示CPU名称

    NSLog(@"cpu&#39;s name=%@",self.cpu.name);

    // 显示ROM名称

    NSLog(@"rom&#39;s name=%@",self.rom.name);

}

@end
  1. 测试代码。
// 实例化Computer

Computer *cmp = [[Computer alloc]init];

// 显示信息

[cmp displayMsg];

9.2 OCP设计原则及多态

所谓OCP就是Open Close Principle,即开闭原则,是指软件的结构对扩展是开放的,对修改是关闭的。现有的软件结构可以无限度的扩展,而不能修改现有结构。

为了使软件达到OCP设计原则,就要将软件抽象,把软件的公共部分抽象出接口。然后其他累可以继承或依赖该接口,这样就可以达到OCP设计原则。

多态可以有多个称呼,可以叫向上类型转换、方法动态绑定以及一个方法多种实现等。继承和接口实现都具有多态性。

下面我们还是通过案例来演示OCP设计原则和多态,首先来看一个OCP的例子,例如,一个人要养很多宠物,在这个案例中我们如何设计软件结构达到OCP设计原则呢?人和宠物是直接的关联关系还是抽象出一个接口,让宠物类实现该接口,人关联这个抽象类。要符合OCP设计原则,应该选择第二种方案。

它们的设计图如下所示,在第一种设计中如果要再增加一个宠物,我们必须修改Person类,而在第二种设计中,我们只要添加一个新宠物类就可以,原有的代码并不需要修改。

图9.3 OCP设计原则

下面我们看一下代码的实现。首先,看第一种实现方法。步骤如下:

  1. 分别创建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
  1. 创建一个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
  1. Person类的display方法实现。
@implementation Person

// 显示信息

-(void)display{

    NSLog(@"%@养了:",self.name);

    NSLog(@"一条%@",self.dog.name);

    NSLog(@"一只%@",self.cat.name);

    NSLog(@"一只%@",self.bird.name);

}
  1. 测试程序结果如下。
#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设计原则来改进上述代码,步骤如下:

  1. 抽象出一个协议Pet,并添加name属性。
@protocol Pet <NSObject>

@property(nonatomic,strong)NSString *name;

@end
  1. 使得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
  1. 使得Person依赖宠物集合数组。
@interface Person : NSObject

// 姓名

@property(nonatomic,strong)NSString *name;

// 宠物集合数组

@property(nonatomic,strong)NSMutableArray *pets;

-(void)display;

@end
  1. Person的实现类,display方法实现。
#import "Person.h"

@implementation Person

// 显示信息

-(void)display{

    NSLog(@"%@养了:",self.name);

    for(id<Pet> pet in self.pets){

        NSLog(@"%@",pet.name);

    }

}

@end
  1. 程序测试结果如下。
#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] 报喜鸟

下面来看一下什么是多态,可能大家如果没有面向对象的基础理解多态是很困难的。这样我们举个现实生活中的例子,我们说白马、黑马都是马。这句话应该没有错误,其实程序中的多态就是这个意思。也就是任何子类都可以当父类使用。这中关系也叫做向上类型转换。如下图所示。

图9.4 多态特性

有时候也把多态特性叫做针对抽象编程,也就是说我们所依赖或继承的类一个是抽象的类或接口。而不是依赖具体的那个类,在程序运行中编译器会动态决定调用那个对象的实际方法。其实,在OCP设计原则一节中我们已经使用到了多态。下面我们还是通过一个案例来演示多态的用法。

该案例以人使用交通工具为例,演示多态的用法,人可以骑自行车、也可以驾驶汽车还可以开游轮。那么,可以将自行车、汽车和游轮抽象成交通工具。人依赖抽象的交通工具,这样人既可以骑车也可以驾驶汽车,还可以开游轮。这样的设计符合OCP设计原则,程序具有多态性。实现步骤如下:

  1. 创建一个父类交通工具类Vehicle,并添加run方法。
@interface Vehicle : NSObject

-(void)run;

@end
  1. 分别创建自行车Bicycle、游轮Steamship和汽车Car类,这些类都继承Vehicle类。
@interface Bicycle : Vehicle

@end

@interface Steamship : Vehicle

@end

@interface Car : Vehicle

@end
  1. 定义一个Person类,定义一个使用use方法。
@interface Person : NSObject

-(void)use:(Vehicle*)v;

@end
  1. 实现Person的实现类。
@implementation Person

-(void)use:(Vehicle*)v{

    [v run];

}

@end
  1. 测试程序,运行结果如下所示。
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!

该案例中使用了向上类型转换、方法动态绑定和依赖抽象等思想。