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

Proper design setup for derived classes with common attributes but different values

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

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

我目前的情况是这样的:

"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
}

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

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

在我看来,我想做的是在 Parent class 中拥有子 classes 需要定义的虚拟保护常量,然后从构造函数中调用它们,但这是不允许的。这两个选项中的一个更好吗?有更好的 "long-term" 设置吗?

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

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

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

  1. Avoiding a large number of args passed to a function 一般(包括一个 ctor)。

  2. Method chaining.

(第一个比较基础,第二个不太重要,只是为了提高可读性。)

更详细:

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

所以让我们从以下内容开始:

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.
     .... 
};

现在 child 可能看起来像这样:

// 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())
{

}

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

header

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 classes 放在一个单独的文件中,您可以使用该文件将所有默认值放在一个您可以轻松获取的位置查找或更改它们。如果它们没有隐藏在 classes 中,强制使用额外的范围语法,它们也可能会更漂亮。

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

#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