【问题标题】:Removing code duplication in inheritance?删除继承中的代码重复?
【发布时间】:2020-10-19 09:33:26
【问题描述】:

我写了以下代码:

std::shared_ptr<mtm::Character>
Game::makeCharacter(CharacterType type, Team team, units_t health, units_t ammo, units_t range, units_t power) {
    if (health <= 0 || ammo < 0 || range < 0 || power < 0)
    {
        throw mtm::IllegalArgument();
    }
    std::shared_ptr<Character> out = nullptr;
    if (type == SOLDIER)
    {
        out = std::shared_ptr<Character>(new mtm::Soldier(team, health, ammo, range, power));
    }
    if (type == MEDIC)
    {
        out = std::shared_ptr<Character>(new mtm::Medic(team, health, ammo, range, power));
    }
    return out;
}

如您所见,我有某种代码重复,如果有 100 种类型怎么办...我将不得不编写 100 个 if 语句,这听起来并不完美。

有什么建议吗?

【问题讨论】:

  • 这里不好提建议,因为不清楚CharacterType type在调用makeCharacter时是如何确定的。您也许可以将CharacterType 更改为模板参数,但这可能只是将那些if 子句移动到另一个地方。如果您在多个位置有 CharacterType type 到类映射,那么您可以通过模板专业化对其进行概括,这样您就不需要在多个位置使用 if 子句。

标签: c++ class c++11 inheritance polymorphism


【解决方案1】:

你可以把它做成一个函数模板:

template<typename C>
std::shared_ptr<mtm::Character>
Game::makeCharacter(Team team, units_t health, units_t ammo, units_t range, units_t power) {
    if (health <= 0 || ammo < 0 || range < 0 || power < 0)
    {
        throw mtm::IllegalArgument();
    }
    return std::make_shared<C>(team, health, ammo, range, power);
}

并称它为:

auto res = Game::makeCharacter<Soldier>(t, h, a, r, p);

【讨论】:

  • 比和简单的auto res = make_shared&lt;Soldier&gt;(t,h,a,r,p)有什么区别?
  • @MikaelH 那么你会如何选择正确的子类呢?
  • @BigSur 您需要将枚举映射到某处的类型。我不会太担心工厂函数中的代码重复,因为通常这是您需要为每种类型切换的唯一地方。
  • @MikaelH 如果想保留枚举,这是一种不错的方法。我也added a deduction guide了。
  • @TedLyngmo 从技术上讲,这不是演绎指南,而是辅助别名模板 :)。但是,是的,它使它更具可读性。扣分指南:en.cppreference.com/w/cpp/language/…
【解决方案2】:

正如@idclev 在评论中指出的那样,如果你想从枚举映射到类型,你需要一些代码来...从枚举映射到类型。

但是,您可以更改代码的分发方式,有些人会发现重新分发的版本更符合他们的喜好。

举一个明显的例子,您可以将实际的创建代码包含为每个类的static 成员。然后通过将枚举值和该类的创建函数的地址传递给工厂来注册一个由工厂创建的类:

class Soldier : public Character {
    // ...
    static std::shared_ptr<Character> create(int t, int h, int a, int r, int p) { 
        return std::make_shared<Soldier>(t, h, a, r, p);
    }
};

class Medic {
    // ...
    static std::shared_ptr<Character> create(int t, int h, int a, int r, int p) {
       return std::make_shared<Medic>(t, h, a, r, p);
    }
};

class Factory {
    typedef std::shared_ptr<Character>(*creator)(int, int, int, int, int);

    std::map<int, creator> creators;

public:
    void register(int value, creator c) { 
        creators[value] = c;
    }

    std::shared_ptr<Character> create(int type, int t, int h, int a, int r, int p) { 
        auto creator = creators.find(type);
        if (creator == creators.end()) {
            throw std::logic_error("Unknown character type");
        }
        return (creator->second)(t, h, a, r, p);
    }
};

因此,您仍然拥有基本相同的重复代码(即,每个类仍然需要一个 create 函数来创建该类型的字符),但它是每个类的一部分,而不是全部塞进工厂。另一方面,它还添加了一些额外的代码,用于向工厂注册每个派生类。作为交换,您至少可以摆脱巨大的开关类型,当它变大时可能会非常痛苦(例如,有 100 个案例,即使只是找到一个您关心的案例也会开始变得有些痛苦。

这种设计对于处理可下载内容等问题也特别有用。使用switch语句,工厂中的代码结构反映了系统中的所有字符类型。使用数据驱动工厂,查找 DLL/.so、加载它并调用适当的函数向系统注册它以添加新的字符类型会更容易一些。

【讨论】:

    猜你喜欢
    • 2019-03-25
    • 2023-03-04
    • 1970-01-01
    • 2012-06-02
    • 2013-03-10
    • 2013-07-24
    • 1970-01-01
    • 1970-01-01
    • 2016-01-25
    相关资源
    最近更新 更多