Objective-C 中的策略模式实现与分析
策略模式简介
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,将每个算法都封装起来,并且使它们之间可以相互替换。策略模式让算法的变化独立于使用算法的客户。在实际应用中,我们常常会遇到这样的场景:某个行为在不同情况下有不同的实现方式。例如,在一个电商应用中,根据用户的会员等级不同,运费的计算方式也不同;或者在游戏开发中,不同角色有不同的攻击方式。如果我们不使用设计模式,可能会在代码中大量使用条件判断语句(如 if - else
或 switch - case
)来处理这些不同的情况,这样的代码不仅冗长,而且难以维护和扩展。策略模式通过将不同的算法封装成独立的类,使得代码更加清晰、可维护,并且易于扩展新的算法。
Objective - C 中的策略模式实现基础
在Objective - C中实现策略模式,我们需要定义一个抽象的策略协议(Protocol),然后为每个具体的策略实现该协议。接下来,我们创建一个上下文类(Context),该类持有一个策略对象的引用,并通过调用策略对象的方法来执行具体的策略。
定义策略协议
首先,我们定义一个策略协议,该协议声明了所有具体策略都需要实现的方法。假设我们正在开发一个图形绘制应用,不同的图形有不同的绘制算法,我们可以定义如下策略协议:
@protocol DrawingStrategy <NSObject>
- (void)draw;
@end
这个协议定义了一个 draw
方法,任何实现该协议的类都必须提供 draw
方法的具体实现。
具体策略类
接下来,我们创建具体的策略类,这些类实现了上述策略协议。以绘制圆形和矩形为例:
#import "DrawingStrategy.h"
@interface CircleDrawingStrategy : NSObject <DrawingStrategy>
- (void)draw {
NSLog(@"绘制圆形");
}
@end
@interface RectangleDrawingStrategy : NSObject <DrawingStrategy>
- (void)draw {
NSLog(@"绘制矩形");
}
@end
在上述代码中,CircleDrawingStrategy
和 RectangleDrawingStrategy
类都实现了 DrawingStrategy
协议的 draw
方法,分别实现了绘制圆形和矩形的具体逻辑。
上下文类
上下文类是策略模式中的关键部分,它持有一个策略对象的引用,并通过调用策略对象的方法来执行具体的策略。
#import <Foundation/Foundation.h>
#import "DrawingStrategy.h"
@interface DrawingContext : NSObject
@property (nonatomic, strong) id <DrawingStrategy> strategy;
- (void)executeDrawing;
@end
@implementation DrawingContext
- (void)executeDrawing {
[self.strategy draw];
}
@end
在 DrawingContext
类中,我们定义了一个 strategy
属性,用于持有具体的策略对象。executeDrawing
方法调用了 strategy
对象的 draw
方法,从而执行具体的绘制策略。
策略模式在实际项目中的应用场景分析
电商应用中的运费计算
在电商应用中,运费的计算方式可能根据不同的条件而有所不同。例如,普通用户和会员用户的运费计算方式可能不同,购买金额达到一定标准时运费也可能有优惠。我们可以使用策略模式来实现这种灵活的运费计算。 首先,定义运费计算的策略协议:
@protocol ShippingCostStrategy <NSObject>
- (float)calculateShippingCostWithOrderAmount:(float)orderAmount;
@end
然后,创建具体的运费计算策略类。例如,普通用户的运费计算策略:
#import "ShippingCostStrategy.h"
@interface RegularUserShippingStrategy : NSObject <ShippingCostStrategy>
- (float)calculateShippingCostWithOrderAmount:(float)orderAmount {
if (orderAmount < 50) {
return 10;
} else {
return 5;
}
}
@end
会员用户的运费计算策略:
#import "ShippingCostStrategy.h"
@interface MemberUserShippingStrategy : NSObject <ShippingCostStrategy>
- (float)calculateShippingCostWithOrderAmount:(float)orderAmount {
if (orderAmount < 30) {
return 8;
} else {
return 3;
}
}
@end
最后,创建运费计算的上下文类:
#import <Foundation/Foundation.h>
#import "ShippingCostStrategy.h"
@interface ShippingCostContext : NSObject
@property (nonatomic, strong) id <ShippingCostStrategy> strategy;
- (float)calculateShippingCostWithOrderAmount:(float)orderAmount;
@end
@implementation ShippingCostContext
- (float)calculateShippingCostWithOrderAmount:(float)orderAmount {
return [self.strategy calculateShippingCostWithOrderAmount:orderAmount];
}
@end
在实际使用中,我们可以根据用户类型设置不同的运费计算策略:
ShippingCostContext *context = [[ShippingCostContext alloc] init];
// 假设当前是普通用户
context.strategy = [[RegularUserShippingStrategy alloc] init];
float shippingCost = [context calculateShippingCostWithOrderAmount:40];
NSLog(@"普通用户运费: %.2f", shippingCost);
// 假设当前是会员用户
context.strategy = [[MemberUserShippingStrategy alloc] init];
shippingCost = [context calculateShippingCostWithOrderAmount:40];
NSLog(@"会员用户运费: %.2f", shippingCost);
通过这种方式,当我们需要添加新的运费计算策略时,只需要创建一个新的实现 ShippingCostStrategy
协议的类,而不需要修改大量现有的代码,提高了代码的可维护性和扩展性。
游戏开发中的角色攻击方式
在游戏开发中,不同的角色可能有不同的攻击方式。例如,战士可能是近战攻击,法师可能是远程魔法攻击。我们可以使用策略模式来管理这些不同的攻击方式。 定义攻击策略协议:
@protocol AttackStrategy <NSObject>
- (void)attack;
@end
创建具体的攻击策略类,如战士的近战攻击策略:
#import "AttackStrategy.h"
@interface MeleeAttackStrategy : NSObject <AttackStrategy>
- (void)attack {
NSLog(@"战士进行近战攻击");
}
@end
法师的远程魔法攻击策略:
#import "AttackStrategy.h"
@interface MagicAttackStrategy : NSObject <AttackStrategy>
- (void)attack {
NSLog(@"法师进行远程魔法攻击");
}
@end
创建角色上下文类:
#import <Foundation/Foundation.h>
#import "AttackStrategy.h"
@interface CharacterContext : NSObject
@property (nonatomic, strong) id <AttackStrategy> strategy;
- (void)performAttack;
@end
@implementation CharacterContext
- (void)performAttack {
[self.strategy attack];
}
@end
在游戏中使用时:
CharacterContext *warriorContext = [[CharacterContext alloc] init];
warriorContext.strategy = [[MeleeAttackStrategy alloc] init];
[warriorContext performAttack];
CharacterContext *mageContext = [[CharacterContext alloc] init];
mageContext.strategy = [[MagicAttackStrategy alloc] init];
[mageContext performAttack];
这样,当我们需要为角色添加新的攻击方式时,只需要创建新的实现 AttackStrategy
协议的类,然后在角色上下文类中设置相应的策略即可,不会影响到其他部分的代码。
策略模式的优势与劣势分析
优势
- 可维护性高:当需要添加新的策略时,只需要创建一个新的实现策略协议的类,而不需要修改大量现有的代码。例如在电商运费计算的例子中,添加新的会员等级对应的运费计算策略,只需要创建新的类实现
ShippingCostStrategy
协议,而不需要修改ShippingCostContext
类以及其他已有的策略类。 - 可扩展性强:可以很方便地扩展新的策略。在游戏开发中,如果要为角色添加新的攻击方式,如刺客的潜行攻击,只需要创建一个实现
AttackStrategy
协议的StealthAttackStrategy
类,然后在CharacterContext
中使用这个新策略即可。 - 代码复用:具体的策略类可以在不同的上下文中复用。比如在不同的游戏场景中,如果都有战士和法师角色,他们的攻击策略类可以在多个场景的角色上下文中复用。
- 解耦算法与使用算法的客户:策略模式将算法的实现与使用算法的客户代码分离,使得客户代码只需要关注如何使用策略,而不需要关心具体的算法实现细节。在图形绘制的例子中,
DrawingContext
类只关心调用strategy
的draw
方法,而不关心draw
方法内部是如何实现绘制圆形或矩形的。
劣势
- 增加系统复杂度:对于简单的应用场景,使用策略模式可能会增加不必要的类和协议,使系统变得复杂。例如,如果只是一个简单的应用,只有一种运费计算方式,使用策略模式就显得过于繁琐。
- 客户端需要了解策略细节:客户端在使用策略模式时,需要了解不同的策略,以便选择合适的策略。在电商运费计算中,客户端需要知道普通用户和会员用户的运费计算策略有什么不同,才能正确设置
ShippingCostContext
的strategy
属性。 - 策略类数量增多:随着应用的发展,策略类的数量可能会不断增加,导致代码管理难度加大。在游戏开发中,如果不断添加新的角色攻击方式,攻击策略类的数量会逐渐增多,需要更好的代码组织和管理方式。
策略模式与其他设计模式的关系
与工厂模式结合
在实际应用中,策略模式常常与工厂模式结合使用。工厂模式可以用来创建具体的策略对象,这样可以将策略对象的创建逻辑封装起来,使得客户端不需要直接实例化具体的策略类。以电商运费计算为例,我们可以创建一个运费策略工厂类:
#import <Foundation/Foundation.h>
#import "ShippingCostStrategy.h"
@interface ShippingCostStrategyFactory : NSObject
+ (id <ShippingCostStrategy>)shippingCostStrategyWithUserType:(NSString *)userType;
@end
@implementation ShippingCostStrategyFactory
+ (id <ShippingCostStrategy>)shippingCostStrategyWithUserType:(NSString *)userType {
if ([userType isEqualToString:@"regular"]) {
return [[RegularUserShippingStrategy alloc] init];
} else if ([userType isEqualToString:@"member"]) {
return [[MemberUserShippingStrategy alloc] init];
}
return nil;
}
@end
在客户端代码中,我们可以这样使用:
ShippingCostContext *context = [[ShippingCostContext alloc] init];
id <ShippingCostStrategy> strategy = [ShippingCostStrategyFactory shippingCostStrategyWithUserType:@"regular"];
context.strategy = strategy;
float shippingCost = [context calculateShippingCostWithOrderAmount:40];
NSLog(@"普通用户运费: %.2f", shippingCost);
通过将策略对象的创建逻辑封装在工厂类中,客户端代码更加简洁,并且更容易维护和扩展。如果以后需要添加新的用户类型对应的运费计算策略,只需要在工厂类中添加相应的创建逻辑,而不需要修改客户端代码。
与状态模式的区别
策略模式和状态模式在结构上有一些相似之处,都涉及到通过对象组合来改变行为。但它们的目的和应用场景有所不同。 状态模式主要用于对象的行为随着内部状态的改变而改变,状态的改变是自动的,对象在不同状态下的行为不同。例如,一个游戏角色在 “空闲”、“奔跑”、“攻击” 等不同状态下有不同的行为,并且角色的状态会根据游戏中的事件自动切换。 而策略模式主要用于将不同的算法封装起来,客户端可以根据需要选择不同的策略,策略的选择通常是由客户端主动决定的。例如在电商运费计算中,客户端根据用户类型选择不同的运费计算策略。 简单来说,状态模式侧重于对象自身状态的变化导致行为变化,而策略模式侧重于客户端根据不同需求选择不同的算法。
总结策略模式在 Objective - C 项目中的最佳实践
- 合理划分策略:在设计策略模式时,要根据实际需求合理划分策略。在电商运费计算中,根据用户类型划分策略是比较合理的,但如果划分得过细或过粗,都可能导致代码维护困难或功能实现不灵活。
- 使用协议扩展策略:在Objective - C中,通过协议可以很方便地扩展策略。当需要为策略添加新的方法时,只需要在协议中声明新方法,并在具体的策略类中实现即可。
- 结合其他设计模式:如前文所述,策略模式可以与工厂模式等其他设计模式结合使用,以提高代码的可维护性和扩展性。
- 文档化策略:对于复杂的策略模式应用,要对各个策略类和上下文类进行文档化,说明每个策略的作用、适用场景以及如何使用,方便其他开发人员理解和维护代码。
在Objective - C项目中,正确使用策略模式可以有效地提高代码的可维护性、扩展性和复用性,帮助开发人员更好地应对复杂多变的业务需求。通过与其他设计模式的结合使用,还可以进一步优化代码结构,提升项目的整体质量。在实际开发中,要根据具体的项目需求和场景,灵活运用策略模式,充分发挥其优势,避免其劣势。