一、多态的概念
一说起面向对象语言的三大特性,你可能会脱口而出:封装、继承、多态。那什么是多态呢,你或许可以背出关于多态的定义,可以举出关于猫、狗吃东西的例子,但你真的理解多态在程序设计中的妙用吗?恐怕不尽然,尤其是对于一些刚接触面向对象的童鞋来说,不明白为什么要在程序中使用多态。
那到底什么是多态呢?从字面上来理解,多态就是多种形态,其在代码上的体现就是父类指针指向之类对象。所以说多态必须要有继承(不理解继承的同学自己去查资料,这里不做讲解),不同的子类重写父类的方法,当父类指针指向不同子类时,其调用同样的方法可以体现出不同的行为。额!这个概念好抽象,越看越懵逼。好吧,就不在概念上深究了,直接通过实例来讲解吧。
二、项目需求
多态在实际项目中运用很广,下面以回合制的卡牌游戏为例来讲解多态的使用。
游戏卡牌英雄分3种职业,分别是战士、法师、术士,不同职业的英雄攻击方式各不相同,我们可以同时上阵5个英雄(比如2个法师2个术士1个战士),轮到我方回合时,上阵的5个英雄轮流发起攻击。
下面我们来看看不使用多态和使用多态两种方式来实现这个需求。
三、不使用多态来实现需求
每个职业创建一个类,分别实现它们的攻击方法。然后创建一个英雄管理类,在这个类中初始化5个英雄放入一个数组,然后遍历数组取出每个英雄进行攻击。先来看看具体代码吧:
战士类
/*战士*/
#import
NS_ASSUME_NONNULL_BEGIN
@interface Warrior : NSObject
/**
战士攻击
*/
- (void)warriorAttack;
@end
NS_ASSUME_NONNULL_END
#import "Warrior.h"
@implementation Warrior
- (void)warriorAttack{
// 战士的攻击方式
NSLog(@"战士进行攻击");
}
@end
法师类
/*法师*/
#import
NS_ASSUME_NONNULL_BEGIN
@interface Mage : NSObject
/**
法师攻击
*/
- (void)mageAttack;
@end
NS_ASSUME_NONNULL_END
#import "Mage.h"
@implementation Mage
- (void)mageAttack{
// 法师的攻击方式
NSLog(@"法师进行攻击");
}
@end
术士类
/*术士*/
#import
NS_ASSUME_NONNULL_BEGIN
@interface Warlock : NSObject
/**
术士攻击
*/
- (void)warlockAttack;
@end
NS_ASSUME_NONNULL_END
/*术士*/
#import "Warlock.h"
@implementation Warlock
- (void)warlockAttack{
// 术士的攻击方式
NSLog(@"术士进行攻击");
}
@end
英雄管理类
#import "HeroManager.h"
#import "Warrior.h"
#import "Mage.h"
#import "Warlock.h"
@implementation HeroManager
{
NSArray *_heroArr;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self createHero];
[self heroAttack];
}
return self;
}
// 创建英雄
- (void)createHero{
// 2个法师
Mage *mage1 = [[Mage alloc] init];
Mage *mage2 = [[Mage alloc] init];
// 2个术士
Warlock *warlock1 = [[Warlock alloc] init];
Warlock *warlock2 = [[Warlock alloc] init];
// 1个战士
Warrior *warrior1 = [[Warrior alloc] init];
_heroArr = @[mage1,mage2,warlock1,warlock2,warrior1];
}
// 英雄攻击
- (void)heroAttack{
for (NSInteger i = 0; i < _heroArr.count; i++) {
if ([_heroArr[i] isKindOfClass:[Warrior class]]) {
// 判断如果是战士类型就进行战士攻击
Warrior *warrior = (Warrior *)_heroArr[i];
[warrior warriorAttack];
}else if ([_heroArr[i] isKindOfClass:[Mage class]]) {
// 判断如果是法师类型就进行法师攻击
Mage *mage = (Mage *)_heroArr[i];
[mage mageAttack];
}else if ([_heroArr[i] isKindOfClass:[Warlock class]]) {
// 判断如果是术士类型就进行术士攻击
Warlock *warlock = (Warlock *)_heroArr[i];
[warlock warlockAttack];
}
}
}
@end
运行结果如下:
2019-06-27 09:43:46.172172+0800 abc[22549:4026177] 法师进行攻击
2019-06-27 09:43:46.172607+0800 abc[22549:4026177] 法师进行攻击
2019-06-27 09:43:46.172623+0800 abc[22549:4026177] 术士进行攻击
2019-06-27 09:43:46.172635+0800 abc[22549:4026177] 术士进行攻击
2019-06-27 09:43:46.172646+0800 abc[22549:4026177] 战士进行攻击
从上面英雄攻击的方法可以看出,每次攻击时都需要判断当前攻击的英雄是什么职业,然后再调用相应职业的攻击方法。现在只有3个职业,如果是10个职业呢,那这个方法里面就会有10个if……else if的判断,而且如果游戏更新,新增加了一个职业,那么这个方法又要进行改动,又要添加一个else if的判断,程序拓展起来非常的不灵活。
三、使用多态来实现需求
使用多态时先创建一个英雄父类,父类里面有个攻击的方法,然后每个职业都继承自这个父类并重写攻击的方法。下面看下具体代码的实现:
英雄父类
/*英雄父类*/
#import
NS_ASSUME_NONNULL_BEGIN
@interface Hero : NSObject
/**
攻击
*/
- (void)attack;
@end
NS_ASSUME_NONNULL_END
#import "Hero.h"
@implementation Hero
@end
战士类
/*战士*/
#import "Hero.h"
NS_ASSUME_NONNULL_BEGIN
@interface Harrior : Hero
@end
NS_ASSUME_NONNULL_END
#import "Harrior.h"
@implementation Harrior
// 重写父类攻击方法
- (void)attack{
// 战士的攻击方式
NSLog(@"战士进行攻击");
}
@end
法师类
/*法师*/
#import "Hero.h"
NS_ASSUME_NONNULL_BEGIN
@interface Mage : Hero
@end
NS_ASSUME_NONNULL_END
#import "Mage.h"
@implementation Mage
// 重写父类攻击方法
- (void)attack{
// 法师的攻击方式
NSLog(@"法师进行攻击");
}
@end
术士类
/*术士*/
#import "Hero.h"
NS_ASSUME_NONNULL_BEGIN
@interface Warlock : Hero
@end
NS_ASSUME_NONNULL_END
#import "Warlock.h"
@implementation Warlock
// 重写父类攻击方法
- (void)attack{
// 术士的攻击方式
NSLog(@"术士进行攻击");
}
@end
英雄管理类
#import "HeroManager.h"
#import "Warrior.h"
#import "Mage.h"
#import "Warlock.h"
@implementation HeroManager
{
NSArray *_heroArr;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self createHero];
[self heroAttack];
}
return self;
}
- (void)createHero{
// 2个法师
Mage *mage1 = [[Mage alloc] init];
Mage *mage2 = [[Mage alloc] init];
// 2个术士
Warlock *warlock1 = [[Warlock alloc] init];
Warlock *warlock2 = [[Warlock alloc] init];
// 1个战士
Warrior *warrior1 = [[Warrior alloc] init];
_heroArr = @[mage1,mage2,warlock1,warlock2,warrior1];
}
// 英雄攻击
- (void)heroAttack{
for (NSInteger i = 0; i < _heroArr.count; i++) {
Hero *hero = (Hero *)_heroArr[i];
[hero attack];
}
}
@end
从上面英雄攻击的方法可以看出,当父类指针指向子类时,调用attack方法时它会根据子类的类型去进行不同的攻击,我们完全不用关心也不用判断英雄的职业。这样设计代的话码少了很多,而且就算有10个、100个职业,heroAttack方法里面完全不用改的,新增职业时这里也不需要变动,拓展起来就灵活了很多。