子类化和添加数据成员

时间:2010-03-28 09:41:15

标签: c++ class

我有一个类的层次结构,如下所示:


    class Critical
    {
    public:
        Critical(int a, int b) : m_a(a), m_b(b) { }
        virtual ~Critical() { }
        int GetA() { return m_a; }
        int GetB() { return m_b; }
        void SetA(int a) { m_a = a; }
        void SetB(int b) { m_b = b; }
    protected:
        int m_a;
        int m_b;
    };

    class CriticalFlavor : public Critical
    {
    public:
        CriticalFlavor(int a, int b, int flavor) : Critical(a, b), m_flavor(flavor) { }
        virtual ~CriticalFlavor() { }
        int GetFlavor() { return m_flavor; }
        void SetFlavor(int flavor) { m_flavor = flavor; }
    protected:
        int m_flavor;
    };

    class CriticalTwist : public Critical
    {
    public:
        CriticalTwist(int a, int b, int twist) : Critical(a, b), m_twist(twist) { }
        virtual ~CriticalTwist() { }
        int GetTwist() { return m_twist; }
        void SetTwist(int twist) { m_twist = twist; }
    protected:
        int m_twist;
    };

上述对我来说在设计方面似乎并不合适,最让我困扰的是 事实上,成员变量的添加似乎驱动了这些类的接口 (执行上述操作的真实代码稍微复杂一点,但仍然采用相同的模式)。 当需要另一个“关键”课程时,这将增加其他一些 属性。 这对你感觉合适吗?我怎么能重构这样的代码? 一个想法是在基础对象时只有一组接口并使用组合 如下:


    class Critical
    {
    public:
        virtual int GetA() = 0;
        virtual int GetB() = 0;
        virtual void SetA(int a) = 0;
        virtual void SetB(int b) = 0;
    };

    class CriticalImpl : public Critical
    {
    public:
        CriticalImpl(int a, int b) : m_a(a), m_b(b) { }
        ~CriticalImpl() { }
        int GetA() { return m_a; }
        int GetB() { return m_b; }
        void SetA(int a) { m_a = a; }
        void SetB(int b) { m_b = b; }
    private:
        int m_a;
        int m_b;
    };

    class CriticalFlavor
    {
    public:
        virtual int GetFlavor() = 0;
        virtual void SetFlavor(int flavor) = 0;
    };

    class CriticalFlavorImpl : public Critical, public CriticalFlavor
    {
    public:
        CriticalFlavorImpl(int a, int b, int flavor) : m_flavor(flavor), m_critical(new CriticalImpl(a, b)) { }
        ~CriticalFlavorImpl() { delete m_critical; }
        int GetFlavor() { return m_flavor; }
        void SetFlavor(int flavor) { m_flavor = flavor; }
 int GetA() { return m_critical->GetA(); }
        int GetB() { return m_critical->GetB(); }
        void SetA(int a) { m_critical->SetA(a); }
        void SetB(int b) { m_critical->SetB(b); }
    private:
        int m_flavor;
 CriticalImpl* m_critical;
    };

4 个答案:

答案 0 :(得分:1)

我的建议:找到熟悉此代码的最耐心的人,并向他们询问其中的一些问题。我认为由于IP问题,您没有发布更完整的示例。这使得很难提供好的建议。

根据您的第一个代码示例,我会说只使用带有公共数据的结构而没有访问器。但如果我看到真正的代码,我可能会改变我的调子。

对于您的第二个代码示例:一个好处是,您可以让另一个类依赖于CriticalFlavor,而不了解Critical(例如,可用于实现类似Bridge模式的内容)。但是,如果潜在的利益在您的情况下不是实际的好处,那么它只是使您的代码不必要地复杂化和YAGNI(可能)。


评论者的评论:

  

基类应该是抽象的

我会说,“基类通常应该至少有一个虚方法,而不是析构函数”。如果没有,那么它只是一种在其他类之间共享公共代码或数据的方法;尝试使用构图。

大多数情况下,这些虚拟方法中至少有一个是纯虚拟的,因此基类将是抽象的。但有时候每个虚拟方法都有一个很好的默认实现,子类会选择要覆盖哪个。在这种情况下,请保护基类构造函数以防止实例化基类。

  

基类中不建议使用受保护的成员

...而且它们在非基类中完全没有意义,所以这个建议基本上就是说从不使用它们。

受保护的成员变量时,建议吗?不常。您的代码示例不够现实,无法确定您要执行的操作或最佳编写方式。成员受到保护,但有公共吸气者/安置者,因此他们基本上是公开的。

  

按界面设计

不要被这个带走,否则你可能会得到一个非常复杂的设计来完成一个非常简单的任务。在有意义的地方使用接口。如果没有看到一些使用Critical及其子类的“调用代码”,很难判断它们是否有意义。谁致电GetFlavorGetTwist

调用代码是仅通过Critical接口与Critical子类交互,还是调用代码知道特定的子类并调用特定的子类方法?您是否为Critical添加了接口方法,以便提供对仅存在于某些子类中的数据/功能的访问?这可能是一种难闻的气味。


您的评论:

  

成员变量的添加似乎驱动了这些类的接口

让我想起 C++ Coding Standards (Sutter / Alexandrescu)中的一个项目:“明白你正在写什么类的课程”。对不起,没有在线参考该项目(买书)。


我还建议您诚实地评估您的技能水平以及您的审核人员的技能水平。您的评论员是否经验丰富的C ++开发人员,他们真正知道他们在谈论什么(如果是这样,请听!),或者他们刚刚从Code Review 101返回并知道说“公共数据不好”和“析构函数应该始终是虚拟的” “?如果是后者,希望你能回答评论评论,说“这通常是好建议,但不适用于这种情况,因为XYZ。”

答案 1 :(得分:0)

如果不了解更多关于真实代码的内容,很难说继承是否是正确的想法。你想要多态性(如果它是CriticalFlavor,CriticalTwist或CriticalWhatever,那么就会对一个Critical发挥作用并获得不同的效果)? A和B的构造顺序是否重要?您是否计划使用更复杂的类树,例如CriticalFlavorTemperature和CriticalTwistColor?

那就是说,这就是我可能为组合方法做的事情:

class Critical
{
public:
    Critical(int a, int b) : m_a(a), m_b(b) { }
    ~Critical() { }
    int GetA() const { return m_a; }
    int GetB() const { return m_b; }
    void SetA(int a) { m_a = a; }
    void SetB(int b) { m_b = b; }
private:
    int m_a;
    int m_b;
};

class CriticalFlavor
{
public:
    CriticalFlavor(int a, int b, int flavor) : m_flavor(flavor), m_critical(a, b) { }
    ~CriticalFlavor() {}
    int GetFlavor() const { return m_flavor; }
    void SetFlavor(int flavor) { m_flavor = flavor; }
    int GetA() const { return m_critical.GetA(); }
    int GetB() const { return m_critical.GetB(); }
    void SetA(int a) { m_critical.SetA(a); }
    void SetB(int b) { m_critical.GetB(b); }
private:
    int m_flavor;
    Critical m_critical;
};

答案 2 :(得分:0)

@Marius我在帖子中可以理解的是,您希望将数据访问与实际数据结构分开。

class Critical
{
public:
    virtual int GetA() = 0;
    virtual int GetB() = 0;
    virtual void SetA(int a) = 0;
    virtual void SetB(int b) = 0;
};

class Flavor
{
public:
    virtual int GetFlavor() = 0;
    virtual void SetFlavor(int a) = 0;
};

template<class T>
class AccessCritical : public Critical
{
public:
    AccessCritical(T &ref) : data(ref) {}

    int GetA() { return (data.m_a); }
    int GetB() { return (data.m_b); }
    void SetA(int a) { data.m_a = a; }
    void SetB(int b) { data.m_b = b; }
private:
    T &data;
};

template<class T>
class AccessFlavor : public Flavor
{
public:
    AccessFlavor(T &ref) : data(ref) {}
    int GetFlavor() { return (data.m_flavor); }
    void SetFlavor(int flavor) { data.m_flavor = flavor; }
private:
    T &data;
};

struct MyStructA {
    int m_a, m_b;

    AccessCritical<MyStructA> critical;
    MyStructA() : critical(*this) {}
};

struct MyStructB {
    int m_flavor;

    AccessFlavor<MyStructB> flavor;
    MyStructB() : flavor(*this) {}
};

struct MyStructC {
    int m_a, m_b;
    int m_flavor;

    AccessCritical<MyStructC> critical;
    AccessFlavor<MyStructC> flavor;
    MyStructC() : critical(*this), flavor(*this) {}
};
void processFlavor(Flavor *flavor)
{
    flavor->SetFlavor(flavor->GetFlavor() * 3);
}
void processCritical(Critical *critical)
{
    critical->SetA(critical->GetA() + critical->GetB());
}
int main() {
    MyStructA a;
    a.critical.SetA(2);
    a.critical.SetB(3);
    processCritical(& a.critical);

    MyStructB b;
    b.flavor.SetFlavor(4);
    processFlavor(& b.flavor);

    MyStructC c;
    c.critical.SetA(20);
    c.critical.SetB(30);
    c.flavor.SetFlavor(40);
    processFlavor(& c.flavor);
    processCritical(& c.critical);

    cerr << a.critical.GetA() << ' ' << a.critical.GetB() << endl;
    cerr << b.flavor.GetFlavor() << endl;
    cerr << c.critical.GetA() << ' ' << c.critical.GetB() << endl;
    cerr << c.flavor.GetFlavor() << endl;

    return 0;
}

答案 3 :(得分:0)

在我看来,这种类的层次只不过是数据桶,除了充当某些底层数据模型的访问者之外,它们实际上并没有做任何其他事情。也许只是将您的设计限制为使用关键访问器的单个类,以及另一个更通用的命名属性方法(即.std :: string GetStringProperty(std :: string key){})?