好吧,我更多地考虑了 Objective-C 中的决策表,并提出了一个实现基本决策表的解决方案。我不会在这里发布整个代码,只是使决策表工作的片段及其基本用途。如果你想查看完整的代码和一些关于如何改进它的好建议,我在Code Review SE上发布了这个。我现在发布这个是因为有人发表了评论要求我这样做,但我肯定会最终改进这一点并整合评论中的建议。无论如何,这是代码。
首先在初始化方法之前,我建立了一些 NSString 常量,它们将用作 NSDictionary 中的键。
//Two options for the decision table, either access the dictionary directly with 0-x, the enum values, or make strings for their names
//the advantage of strings is that it is more extensible, and the position in the enum doesnt matter
NSString* const kEnemyMovementStateJustSpawned = @"enemyMovementStateJustSpawned";
NSString* const kEnemyMovementStateIdle = @"enemyMovementStateIdle";
NSString* const kEnemyMovementStateNeedsMoving = @"enemyMovementStateNeedsMoving";
NSString* const kEnemyMovementStateToFloor = @"enemyMovementStateToFloor";
NSString *const kEnemyMovementStateAtDestinationFloor = @"enemyMovementStateAtDestinationFloor";
NSString* const kEnemyMovementStateToFloorExit = @"enemyMovementStateToFloorExit";
NSString* const kEnemyMovementStateToAttackWalls = @"enemyMovementStateToAttackWalls";
NSString* const kEnemyMovementStateToAttackFloor = @"enemyMovementStateToAttackFloor";
NSString* const kEnemyMovementStateToAttackRoom = @"enemyMovementStateToAttackRoom";
然后我使用这些常量以及类中的方法名称来构建 NSDictionary:
-(void) setupDecisionTable {
//the string objects are the names of methods in the class
_decisionTable = @{kEnemyMovementStateJustSpawned: @"doEnemyJustSpawned",
kEnemyMovementStateIdle: @"doEnemyIdle",
kEnemyMovementStateNeedsMoving: @"doEnemyNeedsMoving",
kEnemyMovementStateToFloorExit: @"doFloorMovement",
kEnemyMovementStateToFloor: @"doVerticalMovement",
kEnemyMovementStateAtDestinationFloor: @"doEnemyAtDestinationFloor",
kEnemyMovementStateToAttackWalls: @"doFloorMovement",
kEnemyMovementStateToAttackFloor: @"doFloorMovement",
kEnemyMovementStateToAttackRoom: @"doFloorMovement"
};
}
然后我每次调用这个方法,它使用从字典中提取的对象的名称执行方法:
-(void) doMovement {
//the selector is formed from a string inside the decision table dictionary
SEL methodToCallName = NSSelectorFromString([_decisionTable objectForKey:[self stringForState:self.state]]);
if (methodToCallName) {
IMP functionPointer = [self methodForSelector:methodToCallName];
void (*methodToCall)(id, SEL) = (void *)functionPointer;
methodToCall(self, methodToCallName);
}
}
-(NSString *) stringForState:(EnemyMovementState)state {
switch (state) {
case EnemyMovementStateJustSpawned:
return kEnemyMovementStateJustSpawned;
case EnemyMovementStateIdle:
return kEnemyMovementStateIdle;
case EnemyMovementStateNeedsMoving:
return kEnemyMovementStateNeedsMoving;
case EnemyMovementStateToFloor:
return kEnemyMovementStateToFloor;
case EnemyMovementStateAtDestinationFloor:
return kEnemyMovementStateAtDestinationFloor;
case EnemyMovementStateToFloorExit:
return kEnemyMovementStateToFloorExit;
case EnemyMovementStateToAttackWalls:
return kEnemyMovementStateToAttackWalls;
case EnemyMovementStateToAttackFloor:
return kEnemyMovementStateToAttackFloor;
case EnemyMovementStateToAttackRoom:
return kEnemyMovementStateToAttackRoom;
default:
return nil;
}
}
最后,这里有几个执行的方法,只是一个完整的例子:
-(void) doEnemyIdle {
if ([self checkFloorsForJobs]) {
self.state = EnemyMovementStateNeedsMoving;
} else {
[self doIdleMovement];
}
}
-(void) doEnemyNeedsMoving {
[self calculateFloorExitPositionByFloor];
self.state = EnemyMovementStateToFloorExit;
}
这是一个非常简单的实现。目前它只能处理一个输入,一个更好的决策表将能够评估多个输入并提供适当的输出。我认为可以通过使用中间方法来扩展它,该方法将状态与其他变量结合起来从字典中选择合适的对象。
在完成所有这些之后,我不确定决策表是否值得在 Objective-C 中付出努力。我不知道代码是否比 switch 语句更容易理解。为了向代码中添加新逻辑,它必须在比 switch 语句似乎需要的更多地方进行修改。我提供此代码作为示例,如果有人有的话,在 Objective-C 中查看其他版本的决策表会很酷。