【问题标题】:Avoid copying switch statments避免复制 switch 语句
【发布时间】:2020-08-21 10:52:30
【问题描述】:

能否重构此代码以避免复制 switch 语句?

enum class Animal
{ Cat, Dog, Fish};

float GetMaxSpeed(Animal a)
{
 switch (a)
    {
        case Animal::Cat:
            return 30;
        case Animal::Dog:
            return 40;
        case Animal::Fish:
            return 15;
    }
 }

string GetGermanTranslation(Animal a)
{
 switch (a)
    {
        case Animal::Cat:
            return "Katze";
        case Animal::Dog:
            return "Hund";
        case Animal::Fish:
            return "Fische";
    }
 }

显然,这是一个玩具示例,我真正的枚举类要大得多。

【问题讨论】:

  • 看起来很简单。根据枚举值的数量,您是否尝试简单地使用枚举枚举值的映射或数组,并给出相应的速度和翻译?或者,如果保证枚举值在数字上是连续的,那么两者的简单数组?任何地方都不需要 switch 语句。
  • 考虑使用不同的方法:const AnimalData animalData[] = {{Animal::Cat, 30, "Katze"}, {Animal::Dog, 40, "Hund"}, {Animal::Fish, 15, "Fische"}}; - 不幸的是 C++ 不支持指定的初始化语法,[Animal::Cat] = {30, "Katze"}
  • @user253751 你能详细说明一下吗,最好在答案中。
  • @SamVarshavchik 谢谢,这也非常有用。在没有直接映射的情况下,例如最大速度未知时,是否有任何解决方案?
  • 地图 (std::map) 可用于过滤掉稀疏索引,但这似乎有点过头了。对于某些情况下可能不存在的功能,您可以使用 std::optional。或者您可以使用std::bitset 来跟踪现有功能。

标签: c++ switch-statement boilerplate


【解决方案1】:

更面向对象的方法包括依赖多态性,而不是不断切换表示类型的枚举器值。

要遵循这种方法,首先,定义一个抽象类Animal,指定其子类应实现的成员函数:

class Animal {
public:
    virtual std::string GetGermanTranslation() const = 0;
    virtual float GetMaxSpeed() const = 0;
    virtual ~Animal() = default;
};

然后,从这个类公开派生并覆盖虚拟成员函数GetGermanTranslation()GetMaxSpeed()

例如,对于Cat

class Cat: public Animal {
public:
   std::string GetGermanTranslation() const override { return "Katze"; };
   float GetMaxSpeed() const override  { return 30; };
};

那么,Dog 类似:

class Dog: public Animal {
public:
   std::string GetGermanTranslation() const override { return "Hund"; };
   float GetMaxSpeed() const override  { return 40; };
};

您可以类似地定义Fish 类。

最后,有了Animal对象的指针或引用,你只需调用相应的虚成员函数:

void displayAnimal(const Animal& animal) {
   std::cout << "The " << animal.GetGermanTranslation();
   std::cout << " runs at " << animal.GetMaxSpeed() << '\n';
}

如您所见,不再需要打开表示用于区分不同动物的类型的枚举器。

您可以使用不同的Animal 对象调用此函数displayAnimal()

auto cat = std::make_unique<Cat>();
displayAnimal(*cat);

auto dog = std::make_unique<Dog>();
displayAnimal(*dog);

【讨论】:

  • 这是方式矫枉过正。 IMO 如何不这样做的一个例子。该交换机更简单、更快速且更易于维护。无论如何,您的解决方案也是一种正交的; OP 有枚举值而不是事物的实例。
  • @AsteroidsWithWings 这种方法遵循Open-closed Principleswitch 没有。例如,假设您必须用 mouse 动物扩展动物:使用 OOP 方法,您只需创建一个新类 Mouse派生自Animal 并覆盖相应的成员函数。另一方面,使用switch 方法,您将不得不遍历整个代码以查找所有switch 语句,并为每个switch 语句添加一个新案例。
  • 多态方法无需修改现有代码即可扩展,而基于switch 语句的方法需要修改现有代码以进行扩展。后者不仅需要做更多的工作,而且更容易出错。它的本地化程度也较低,并且分布在整个代码中。
  • 不正确。编译器警告开关中缺少枚举。这是一个非常非常小的工作。您对一个非常简单的问题进行了过度设计。
【解决方案2】:

如果您没有另外指定,则枚举类型中的第一个枚举数的值为 0,随后的枚举数的值比其前一个枚举数大一。所以与

enum Animal
{ Cat, Dog, Fish};

Cat 的值为 0,Dog 的值为 1,Fish 的值为 2。要将这些值映射到其他一组值,只需将它们用作数组索引:

int max_speed[] = {
    30, 40, 15
};

const char* german_name[] = {
    "Katze",
    "Hund",
    "Fische"
};

现在您可以编写 max_speed[Cat]german_name[Dog] 之类的内容。

请注意,字符串文字位于 引号内,例如 "Katze",而不是问题中的单引号。多个字符周围的单引号创建了一个时髦的东西,称为“多字符文字”。它们几乎没用。

【讨论】:

  • 这非常有用,但在我的实际情况中,我希望枚举及其属性之间有明确的、可读的映射。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多