在这种情况下(C#)替换开关块和枚举的正确方法是什么?

发布于 2024-12-03 13:30:57 字数 750 浏览 1 评论 0 原文

如果有帮助的话,以下问题是在我正在构建的游戏的背景下提出的。

在几个不同的地方我有以下场景。存在一个父类,在本示例中称为 Skill,并且我有许多子类实现父类中的方法。还有另一个父类,我们称之为 Vocation。这些技能需要列在职业的不同子类中。然而,这些技能需要适用于游戏中使用任何给定职业的任何事物。

我当前的设置是有一个名为 Skill.Id 的枚举,以便 Vocation 包含该 Enum 中的值的集合,并且当游戏中的实体采用该职业时,该集合将传递到另一个名为 SkillFactory 的类中。每次创建新的 Skill 子类时,Skill.Id 都需要一个新条目,以及新子类构造函数的 switch 块中的一个 case。

即:

//Skill.Id
Enum{FireSkill,WaterSkill,etc}

//SkillFactory
public static Skill Create(Skill.Id id)
{
    switch(id)
    {
        case Skill.Id.FireSkill:
             return new FireSkill();
        //etc
    }
}

这工作得很好,但是使用枚举和开关块作为中间的过渡感觉比我解决这个问题所需的开销更多。是否有更优雅的方法来创建这些技能子类的实例,但仍然允许职业包含标识它可以使用的技能的集合?

编辑:我可以扔掉枚举和关联的开关块,只要 Vocation 可以包含允许任意实例化 Skill 子类的集合。

If it helps, the following question is in the context of a game I am building.

In a few different places I have the following scenario. There exists a parent class, for this example called Skill, and I have a number of sub-classes implementing the methods from the parent class. There also exists another parent class that we will call Vocation. The skills need to be listed in different sub-classes of Vocation. However, those skills need to be available for anything in the game that uses any given vocation.

My current setup is to have an Enum called Skill.Id, so that Vocation contains a collection of values from that Enum and when an entity in the game takes on that Vocation the collection is passed into another class, called SkillFactory. Skill.Id needs a new entry every time I create a new Skill sub-class, as well as a case in the switch block for the new sub-classes' constructor.

i.e.:

//Skill.Id
Enum{FireSkill,WaterSkill,etc}

//SkillFactory
public static Skill Create(Skill.Id id)
{
    switch(id)
    {
        case Skill.Id.FireSkill:
             return new FireSkill();
        //etc
    }
}

This works perfectly fine, but using the enum and switch block as a go between feels like more overhead than I need to solve this problem. Is there a more elegant way to create instances of these Skill sub-classes, but still allows Vocation to contains a collection identifying the skills it can use?

Edit: I am fine throwing out the enum and associated switch block, so long as Vocation can contain a collection that allows arbitrary instantiation of the Skill sub-classes.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我们的影子 2024-12-10 13:30:57

您可以创建一个 Dictionary> 并使用它来实例化。

在构造函数中:

Dictionary<Skill.Id, Func<Skill>> creationMethods = new Dictionary<Skill.Id, Func<Skill>>();
public SkillFactory()
{
     creationMethods.Add(Skill.Id.FireSkill, () => new FireSkill());
     creationMethods.Add(Skill.Id.WaterSkill, () => new WaterSkill());
}

然后,您的 Create 方法变为:

public static Skill Create(Skill.Id id)
{
     return creationMethods[id]();
}

当然,这并没有好多少 - 除了它确实允许您将其扩展到每个 ID 的其他功能,而无需复制 switch 块(如果这成为要求)。 (只需将更多内容放入字典的值部分即可。)

话虽这么说,从长远来看,完全摆脱枚举对于可扩展性来说是一个很好的好处。然而,这将需要更复杂的改变。例如,如果您使用 MEF,则可以在运行时导入一组 SkillFactory 类型,并通过单个 ImportMany 将它们与名称(通过元数据)关联。这将允许您在不更改工厂的情况下添加新的技能子类,并通过名称或其他机制引用它们。

You can make a Dictionary<Skill.Id, Func<Skill>> and use it to instantiate.

In the constructor:

Dictionary<Skill.Id, Func<Skill>> creationMethods = new Dictionary<Skill.Id, Func<Skill>>();
public SkillFactory()
{
     creationMethods.Add(Skill.Id.FireSkill, () => new FireSkill());
     creationMethods.Add(Skill.Id.WaterSkill, () => new WaterSkill());
}

Then, your Create method becomes:

public static Skill Create(Skill.Id id)
{
     return creationMethods[id]();
}

Granted, this isn't much better - except that it does allow you to extend this to other functionality that's per ID without duplicating the switch block if that becomes a requirement. (Just put more into the value side of the Dictionary.)

That being said, in the long run, getting rid of the enum entirely can be a good benefit for extensibility. This will require a more elaborate change, however. For example, if you used MEF, you could import a set of SkillFactory types at runtime and associate them to a name (via metadata) via a single ImportMany. This would allow you to add new Skill subclasses without changing your factory, and refer to them by name or some other mechanism.

挖个坑埋了你 2024-12-10 13:30:57

如果这个创建函数的使用方式会导致“case”产生开销,那么带有枚举键的字典将生成大量垃圾。

在 xna 游戏的背景下,它可能比“案例”更糟糕。

“如果您使用枚举类型作为字典键,内部字典操作将导致装箱。您可以通过使用整数键来避免这种情况,并在将枚举值添加到字典之前将它们转换为整数。” 从此处提取

您可以使用一个简单的数组并将 enum 转换为 int 进行索引:

 Enum {FireSkill=0,WaterSkill=1,etc}

 Func<Skill>[] CreationMethods = new Func<Skill>() 
 {
     () => new FireSkill(),
     () => new WaterSkill(),
 } 

if this creation function is going to be so used that a "case" will produce overhead, dictionary with enums keys will generate a lot of garbage.

In the context of a xna game, it can be worse than the "case".

"If you use an enum type as a dictionary key, internal dictionary operations will cause boxing. You can avoid this by using integer keys, and casting your enum values to ints before adding them to the dictionary." Extracted from here

You can use a simple array and cast enum to int for indexing:

 Enum {FireSkill=0,WaterSkill=1,etc}

 Func<Skill>[] CreationMethods = new Func<Skill>() 
 {
     () => new FireSkill(),
     () => new WaterSkill(),
 } 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文