具有共同属性但值不同的派生类的正确设计设置

Posted

技术标签:

【中文标题】具有共同属性但值不同的派生类的正确设计设置【英文标题】:Proper design setup for derived classes with common attributes but different values 【发布时间】:2015-05-08 18:36:31 【问题描述】:

所以我可以想出几种方法来做到这一点,但我觉得我只是在为每个新的子类重新输入相同的东西。有没有一种设计模式可以让我为我的子类设置完整的结构,从而减少实现所需的代码量(如果可能的话,还可以强制执行正确的实现?)

这看起来很简单,但我的想法不起作用,我发现最好的方法是在调用构造函数时在参数中硬编码,或者在每个子类中设置新常量然后使用那些.

我目前拥有的是这样的:

"parent.hpp"
class Parent 
    private:
        std::string name;
        int someValue;

    protected:
        Parent(std::string name, int someValue); // NOTE: There will be 7 parameters/attributes that need initial base values
        void setName(std::string name)  this->name = name; 
        void setSomeValue(int someValue)  this->someValue = someValue; 

    public:
        std::string getName()  return this->name; 
        int getSomeValue()  return this->someValue; 
;

"parent.cpp"
Parent::Parent(std::string name, int someValue) 
    setName(name);
    setSomeValue(someValue);


"child.hpp"
class Child : public Parent 
    public:
        Child();
;

"child.cpp - option 1"
static const std::string DEFAULT_NAME = "Jon";
static const int DEFAULT_SOME_VALUE = 100;

Child::Child() : Parent(DEFAULT_NAME, DEFAULT_SOME_VALUE) 
    // other stuff if needed


"child.cpp - option 2"
Child::Child() : Parent("Jon", 100) 
    // other stuff if needed

会有虚拟方法,我稍后会添加,但现在我只想知道正确的设计模式(可能)有很多子类。还有更多共同的参数都是 int 值。我似乎不清楚将构造函数设为Child::Child("string", 1, 2, 3, 4, 5, 6),尽管实现新的子类会更容易。

另一方面,如果我只是为每个子类中的基值重新键入样板常量,则构造函数将更具描述性,但会有很多代码重用。

在我看来,我想做的是在父类中拥有虚拟受保护常量,子类需要定义这些常量,然后从构造函数中调用它们,但这是不允许的。两种选择中的一种更好吗?有没有更好的“长期”设置?

我查看了所有类似的问题,发现最接近的是:Proper way to make base class setup parent class。虽然我不确定这个想法是否能解决我的问题或让任何事情变得更清晰。

我的另一个想法是从默认构造函数中调用纯虚方法,但据我所知,这也是不允许的。

【问题讨论】:

我想我对static const std::string DEFAULT_NAME = "Jon"; static const int DEFAULT_SOME_VALUE = 100; 感到困惑。这只是一个例子,还是你真的在考虑硬编码常量值?除了那种情况,这不会让你的代码毫无用处吗?? 这只是一种为不同参数提供上下文的方法。每个不同的子类都需要实现常量,然后所有的构造函数都将使用相同的变量。例如Child:Parent(NAME, AGE, WEIGHT, HEIGHT)Child:Parent("Jim", 30, 176, 90) 【参考方案1】:

我会像 Ami 一样使用另一个对象来保持状态,尽管我会出于不同的原因这样做。由于 state 是一个单独的类,你可以在构造实际的 Parent 和 Child 之前完全构造它,并且它可以有自己的层次结构。

标题

class Parent 
protected:
    struct ParentState 
        std::string name;
        int someValue;
    ;

    Parent(ParentState);

    void setName(std::string name)  data.name = name; 
    void setSomeValue(int someValue)  data.someValue = someValue; 

public:
    std::string getName()  return data.name; 
    int getSomeValue()  return data.someValue; 

private:
    ParentState data;
;

class Child : public Parent 
    struct ChildDefaults : public Parent::ParentState 
        ChildDefaults();
    ;

public:
    Child();
;

实现

Parent::Parent(ParentState init) 
    // since you have setters, they should be used
    // instead of just data=init;
    setName(init.name);
    setSomeValue(init.someValue);


Child::ChildDefaults::ChildDefaults()
    name = "Jon";
    someValue = 100;


Child::Child() : Parent(ChildDefaults())
    // other stuff if needed

如果将 ParentState 和 ChildDefault 类放在单独的文件中,则可以使用该文件将所有默认值放在一个位置,以便轻松查找或更改它们。如果它们没有隐藏在类中,它们也可能更漂亮,从而强制使用额外的范围语法。

附录: 要将整个默认设置层次结构放在自己的标题中,只需将它们全部移动到一个标题中即可。请务必执行包含保护以避免重复定义构造函数。

#ifndef THE_DEFAULTS_H
#define THE_DEFAULTS_H

struct ParentState 
    std::string name;
    int someValue;
;

struct ChildDefaults : public Parent::ParentState 
    ChildDefaults() 
        name = "Jon";
        someValue = 100;
    
;

// more default settings for other classes

#endif

【讨论】:

所以这是我目前最喜欢的,但似乎我仍然需要在每个 Child 类中编写和实现三个单独的部分才能实现此功能。 struct ChildDefaults : public ... Child::ChildDefaults::ChildDefaults() 然后是子构造函数。您能否提供一个将 ChildDefault 类放在单独文件中的示例?【参考方案2】:

也许你可以在这里结合两个想法:

    Avoiding a large number of args passed to a function 大体上(包括演员)。

    Method chaining.

(第一个在这里更基础,第二个不太重要,只是为了提高可读性。)

更详细的:

具有任何函数,特别是基类的 ctor,采用 7 个参数,看起来非常冗长和脆弱。假设您意识到您需要添加另一个参数。您现在是否必须检查所有派生类?这是有问题的。

让我们从以下内容开始:

class Parent

protected:
    explicit Parent(const ParentParams &params);
;

ParentParams 看起来像这样:

class ParentParams

public:
     // Initialize with default stuff.
     ParentParams(); 

     // Changing only the foo aspect (via method chaining).
     ParentParams &setFoo(Foo foo_val)
     
         m_foo = foo_val;
         return *this;
     

     // Changing only the bar aspect (via method chaining).
     ParentParams &setBar(Bar bar_val)
     
         m_bar = bar_val;
         return *this;
     

     // Many more - you mentioned at least 7.
     .... 
;

现在孩子可能看起来像这样:

// A child that happens to have the property that it changes foo and bar aspects.
class FooBarChangingChild :
    public Parent

public:
     FooBarChangingChild();
;

在它的实现中:

// Static cpp function just creating the params.
static ParentParams makeParams() 

     // Note the clarity of which options are being changed.
     return ParentParams()
          .setFoo(someFooVal)
          .setBar(someBarVal);


FooBarChangingChild::FooBarChangingChild() :
    Parent(makeParams())



【讨论】:

以上是关于具有共同属性但值不同的派生类的正确设计设置的主要内容,如果未能解决你的问题,请参考以下文章

虚基类菱形派生关系

C ++将派生类的属性设置为从同一基类派生的未知类对象?

5继承与派生6-虚基类

使用通用虚函数实现的任意组合实现派生类的正确方法是啥?

从基类指针访问两个具有不同类型的派生成员变量。

调用派生类的构造函数在基类的构造函数之前执行