【问题标题】:Design complexity with subclassing in Objective-CObjective-C 中子类化的设计复杂性
【发布时间】:2013-01-31 22:56:27
【问题描述】:

我正在使用 Cocos2D for iOS,但您很可能不必熟悉 Cocos2D,只需 Obj-C 即可回答我的问题。

我在开发这款游戏时有一个敌人类,我已经使用了很长时间,但现在它已经达到了一个复杂的程度,我需要做一些事情来使它更有条理和可读性。

它目前的工作方式是这样的:我有一个敌人类,我分配了一定次数并插入到一个可变数组中。然后我可以随时翻阅那个可变数组。当敌人类被分配时,它也被引导初始化并传递一个敌人名称的字符串。在它的 init 中有一系列 if/if else 语句检查敌人名称并为其设置正确的值。这工作得很好,除了设计方面,当我添加越来越多的敌人时,查看所有这些名称会变得非常混乱。

我现在想做的是从所有不同敌人的敌人类别中分离出来。我需要访问敌人的属性,就像访问该类的其他类型的敌人一样。

现在在敌人类初始化中我有类似的东西:

-(id) initWithEnemy:(NSString *)kind {

    if([kind isEqualToString:@"enemyName"]){

    //set values

    }
    else if([kind isEqualToString:@"anotherEnemyName"]){

    //set values

    }

    //etc, etc..

}

现在我想让这个设定值发生在其他文件中。每个敌人的一个或一组头文件/主文件。所以在 initWithEnemy 中,我想也许我可以从传递的“kind”字符串中分配一个敌人名称类。不确定我是否可以使用 NSClassFromString。我已经对其进行了一些试验,但我不确定如何像以前那样访问类属性。即使我确实以与以前相同的方式访问属性,这是否意味着所有敌人名称类都必须具有相同数量的属性?

【问题讨论】:

标签: objective-c cocos2d-iphone subclassing


【解决方案1】:

您可以将您的敌人拆分为带有具体子类的抽象基类——这是一个好方法。但是请注意,使用组合而不是继承通常会更好 - 这是您将对象注入到持有者类中以建模某些东西的地方。否则,您可能会遇到一个问题,即您的敌人既是“怪物”又是“巫师”,而单一继承链不允许这样做。

这里有两种设计模式似乎很合适——它们都专注于将复杂的实例化规则与类本身分离。一种是工厂模式,另一种是建造者模式。如果您拆分为类层次结构,则前者是合适的,否则是后者。

抱歉,无法提供更多示例 - 在 iPad 上编写此内容,并在出门的路上。

【讨论】:

  • 这是很多要消化的信息,但似乎很有用。我需要一点一点地解决它。谢谢。
  • @LearnCocos2D - 完美。 . .不能要求更多。
【解决方案2】:
  1. 声明enum而不是字符串

    typedef enum {
        kEnemyInvalid = 0,
        kEnemyName1,
        kEnemyName2,
        [...]
    } EnemyType;
    
  2. 使用所有敌人类型的全局属性创建一个 Enemy 类。

  3. 为每种类型创建必要的敌人子类。一个职业可能会涵盖不止一种敌人类型。
  4. 创建一个函数(可能是类方法)

    Class EnemyClassFromEnemyType(EnemyType type) {
        switch (type) {
            case kEnemyName1:
               return [EnemyName1 class];
            case kEnemyName2:
               return [EnemyName2 class];
            default:
               return Nil;
        }
    }
    

    这个函数在敌人类型和实现它的类之间建立了联系。有一些方法可以让它更漂亮,我首选的方法是使用 X-Macros。

  5. 现在让我们创建一个工厂方法来创建敌人

    + (Enemy*)createEnemyWithType:(EnemyType*)enemyType {
        Class enemyClass = EnemyClassFromEnemyType(enemyType);
        return [[enemyClass alloc] initWithType:enemyType];
    }
    

同样使用 X-Macros

头文件

#define ENEMY_DEFINITIONS \
  ENEMY_DEFINITION(kEnemyInvalid, = 0, Nil) \
  ENEMY_DEFINITION(kEnemyName1,, [EnemyName1 class]) \
  ENEMY_DEFINITION(kEnemyName2,, [EnemyName2 class])

#define ENEMY_DEFINITION(name, intValue, enemyClass) name intValue, 

/**
 * Your enum declaration.
 */
typedef enum {
    ENEMY_DEFINITIONS
} EnemyType;

#undef ENEMY_DEFINITION

Class EnemyClassFromEnemyType(EnemyType type);
NSString* NSStringFromEnemyType(EnemyType type);

实现文件

#define ENEMY_DEFINITION(name, intValue, enemyClass) [name] = @#name,

NSString* EnemyTypeStringTable[] = {
    ENEMY_DEFINITIONS
}

#undef ENEMY_DEFINITION

NSString* NSStringFromEnemyType(EnemyType type) {
    return EnemyTypeStringTable[type]
}

#define ENEMY_DEFINITION(name, intValue, enemyClass) classTable[name] = enemyClass;

Class EnemyClassFromEnemyType(EnemyType type) {
    static Class* classTable = nil;

    if (classTable == nil) {
        classTable = malloc(sizeof(Class) * sizeof(EnemyTypeStringTable) / sizeof(NSString*));

        ENEMY_DEFINITIONS
    }

    return classTable[type];
}

#undef ENEMY_DEFINITION

使用 X-Macros 技术的美妙之处在于您将所有内容都放在一个地方,您可以轻松添加更多类型而无需更改任何其他内容。你会得到类似 Java 枚举的东西,因为枚举可以有属性。

【讨论】:

  • 谢谢,这也是我之前得到的建议。我需要做很多将 NSStrings 替换为枚举的工作。
  • 第 4 步和 on 有点让我困惑。为什么你提到我可以在那里放一个 switch 语句?我可以将我的敌人类型传递给我正在调用的方法,对吗?
  • @VagueExplanation 稍微解释了第四步。此函数由步骤 5 中的工厂方法使用。
  • 通过第 4 步,您可以只使用 NSClassFromString,对吗?这样你就不必使用 switch 语句了。
  • @VagueExplanation 从技术上讲,你可以。但是,从代码质量的角度来看,不推荐。如果你输入错误的字符串会发生什么?你得到运行时错误,或者,使用 obj-c,你可能会得到一些 nil 对象,你将不得不调试问题。使用这种设计,你会得到一个编译错误。此外,有时您希望对不同类型的敌人使用相同的类。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多