【问题标题】:Where to implement functions from an interface's parent interface?从接口的父接口到哪里实现功能?
【发布时间】:2020-02-03 01:08:05
【问题描述】:

我被要求实现一个接口,我想知道什么是尽可能多地分解代码的最佳策略。

这是接口定义(我不应该改变它):

#include <string>

class BaseIf
{
public:
    virtual ~BaseIf() {}

    virtual std::string getName() = 0;
};

class IntIf : public BaseIf
{
public:
    virtual ~IntIf() {}

    virtual int getValue() = 0;
};

class FloatIf : public BaseIf
{
public:
    virtual ~FloatIf() {}

    virtual float getValue() = 0;
};

我最终会得到IntImpl(实现IntIf)和FloatImpl(实现FloatIf)。但我想知道我应该把这两个类共有的任何代码放在哪里(比如name 属性管理或BaseIf 所需的任何其他东西,实际上比这个MCVE 大得多)。

如果我使用公共代码创建BaseImpl(实现BaseIfgetName 函数),并从它派生IntImpl(和IntIf),那么我还需要实现getName因为它被报告为未实施。而且我还得到了BaseIf的双重继承...

我想知道 Pimpl 模式是否会有所帮助,然后 IntImpl 将有一个 BaseImpl 对象作为属性(并且仅从 IntIf 派生),但是我需要在 @987654339 中实现 getName @ 以“转发”对BaseImpl 属性的调用。因为BaseIf 实际上有很多虚函数,所以维护起来真的很痛苦。

是否没有聪明的解决方案/模式可以在一个共同的地方只实现一次getName?还是只是界面不好,应该重做?

【问题讨论】:

  • 根据您的其他要求和设计,似乎更好的选择是这里的模板和模板专业化。或者可能是一个没有任何特化或继承的通用“数字”模板。
  • 关于你当前的代码,除非你有多个“Int”实现都继承自IntIf,否则“Int”真的不需要单独的接口和实现类。
  • 我建议实现仍然保持纯虚拟成员的派生接口是“糟糕的设计”。正如其他人所说,模板在这里会更好地工作(但是,您显然不能更改设计)。
  • 添加派生接口只是愚蠢的。恕我直言,它完全破坏了界面范例的全部意义。您通常会从您的界面派生 usable 类 - 但有时,“中间”可能会有所帮助。例如,在这里,这样的中间体将定义 getName 成员。
  • 为什么对不同“类型”的处理不同?尤其是floatint 的处理为什么会有这么大的区别?从数学的角度来看,整数和实数 (float) 之间并没有太大区别,导致您的实现出现巨大差异的设计决策或要求是什么?对我来说,这似乎是需求分析和最终设计的问题。

标签: c++ inheritance design-patterns


【解决方案1】:

这是虚拟继承的主要用例。

尽管多重继承和虚拟继承有很多污名,但当我们的接口(无数据成员)被虚拟继承时,并没有什么特别的问题。这是要点:

class BaseIf
{
public:
    virtual ~BaseIf() {}

    virtual std::string getName() = 0;
};

class IntIf : public virtual BaseIf
{
public:
    virtual ~IntIf() {}

    virtual int getValue() = 0;
};

class BaseImpl : public virtual BaseIf
{
  public:
    std::string getName () override { return "whoa dude"; }
};

class IntImpl : public virtual IntIf, public BaseImpl
{
  public:
    int getValue() override { return 42; }
};

full demo

如果层次结构更深,可能还必须虚拟继承实现类,这不是很方便,但仍然可行。

实现的虚拟继承的替代方法是将实现分层为“构建块”层和最后一层。构建块是独立的,不会继承其他构建块。 (他们可能继承接口)。 final 类继承构建块,但不继承其他 final 类。

class BaseBlock : public virtual BaseIf
{
  public:
    std::string getName () override { return "whoa dude"; }
};

class IntBlock : public virtual IntIf
{
  public:
    int getValue() override { return 42; }
};

class BaseImpl : public BaseBlock {};

class IntImpl : public BaseBlock, public IntBlock {};

full demo

如果层次结构中没有虚拟继承,则确实需要对接口进行更改。然而,这些更改是透明的(无需更改客户端代码,只需重新编译)并且无论如何可能是有益的。

如果没有虚拟继承,就不得不求助于大量样板文件。

class BaseBlock // no base class!
{
  public:
    virtual std::string getName () { return "whoa dude"; }
};

class BaseImpl : public BaseIf, public BaseBlock
{
  public:
    // oops, getName would be ambiguous here, need boplerplate
    std::string getName () override { return BaseBlock::getName(); }
};

【讨论】:

  • 感谢您的回答。您可能想提到您建议修改接口,因为如果省略了虚拟继承(如果您相信的话),它肯定是不正确的,并且没有好的方法可以按照声明的方式实现它?正如我的 OP 提到的,首要目的不是改变界面。
【解决方案2】:

您可以创建一个模板类来实现接口的公共部分,如下所示:

template <class IFACE> class BaseImpl : public IFACE
{
    public:
    std::string getName () override { ... }
}

然后

class IntImpl : public BaseImpl<IntIf>
{
    public:
    int getValue() override { ... }
}

结果是一个简单的单继承链。 BaseIf IntIf BaseImpl IntImpl

确保您有充分的理由让 IntIfFloatIf 存在,但在您的 MCVE 中,它们看起来根本不需要存在。

【讨论】:

  • 太棒了!这是解决问题的唯一答案(实现一次getName)而不改变接口。正如 OP 中所评论的,IntIfFloatIf 只是我的 MCVE 的示例。谢谢。
【解决方案3】:

您可以为纯虚函数提供默认实现:

struct A {
    virtual void frob() = 0;
};

void A::frob() {
    std::cout << "default";
}

struct B : A {
    void frob() override {
        A::frob(); // calls the default
    }
};

如果我没看错你的问题,你会想要getName() 的默认实现。所以解决这个问题,只需提供一个实现并调用它:

class IntIf : public BaseIf
{
public:
    virtual ~IntIf() {}
    virtual int getValue() = 0;

    std::string getName() override {
        return BaseIf::getName();
    }
};

class FloatIf : public BaseIf
{
public:
    virtual ~FloatIf() {}
    virtual float getValue() = 0;

    std::string getName() override {
        return BaseIf::getName();
    }
};

【讨论】:

  • 谢谢,但是如果 BaseIf 有很多函数,它们最终会在每个子类中重复,这是我想要避免的。
猜你喜欢
  • 2010-12-18
  • 2016-10-29
  • 1970-01-01
  • 2016-06-20
  • 2010-11-11
  • 2023-03-07
  • 1970-01-01
  • 2020-07-08
  • 2021-09-14
相关资源
最近更新 更多