【问题标题】:Too Many Constructor Args for Dependency Injection/Inheritance Design Pattern依赖注入/继承设计模式的构造函数参数过多
【发布时间】:2012-08-29 09:24:34
【问题描述】:

所以我决定使用工厂设计模式和依赖注入。

class ClassA
{        
    Object *a, *b, *c;
    public:
    ClassA(Object *a, Object *b, Object *c) :
    a(a), b(b), c(c) {}
};

class ClassB : public ClassA
{        
    Object *d, *e, *f;
    public:
    ClassB(Object *a, Object *b, Object *c, Object *d, Object *e, Object *f) :
    ClassA(a, b, c), d(d), e(e), f(f) {}
};

现在,问题在于 classB 的构造函数参数太多。这是一个单继承层的例子,但是当继承层开始变深,每个层类需要构造更多的对象时,顶层的构造函数最终需要太多的参数才能构造!

我知道我可以使用 setter 代替构造函数,但是还有其他方法吗?

【问题讨论】:

  • 我的一般做法是尽可能避免继承,并尽量让每个类都专注于单一职责。你能不能单独构造ClassA,然后用引用初始化ClassB,而不是通过继承将它们紧密耦合?
  • 迈克·西摩是对的。与更紧密耦合的 is-a 关系(继承)相比,更喜欢 has-a 关系(组合)。也许如果你解释你想做什么,我们可以告诉你如何更好地完成它?
  • 我认为继承没有任何重大问题。它应该在任何需要的地方使用。它不应该像其他一切一样被滥用。
  • @KirillKobelev:我完全同意;除非需要,否则应避免使用。
  • 什么是“参数过多”?使用 IOC 时,容器会处理它们,而这只发生在您滥用继承或拥有一个可以做所有事情并依赖于宇宙的“上帝对象”时。

标签: c++ design-patterns inheritance dependency-injection


【解决方案1】:

不建议将 Setter 用于此类事情,因为它们会导致部分构造的对象非常容易出错。构造需要许多参数的对象的常见模式是使用构建器。 ClassBBuilder 的职责是创建 ClassB 对象。您将 ClassB 构造函数设为私有,并仅允许生成器使用朋友关系调用它。现在,构建器可以看起来像这样

ClassBBuilder {
  public:
    ClassBBuilder& setPhoneNumber(const string&);
    ClassBBuilder& setName(consg string&);
    ClassBBuilder& setSurname(const string&);
    ClassB* build(); 
} 

而你使用这样的构建器:

ClassB* b = ClassBBuilder().setName('alice').setSurname('Smith').build();

build() 方法检查是否设置了所有必需的参数,它要么返回正确构造的对象,要么返回 NULL。不可能创建部分构造的对象。您仍然有一个带有许多参数的构造函数,但它是私有的并且只在一个地方调用。客户不会看到它。构建器方法还很好地记录了每个参数的含义(当您看到 ClassB('foo', 'bar') 时,您需要检查构造器以确定哪个参数是名称,哪个是姓氏)。

【讨论】:

  • 这可能比 setter 好一点,但您实际上是在延迟检查是否存在足够的信息来构建运行时的对象。我不认为这是对带有几个参数的普通构造函数的改进。
  • 如果你想强制编译时,你有时仍然可以从构建器中受益。当类构造函数采用许多具有默认值的可选参数时就是这种情况。然后,您可以使用构建器的设置器来设置这些可选参数,并让 build() 方法获取所有必需的参数。这将使您在编译时强制传递所有必需的参数,但比调用带有许多参数的构造函数更易读且更不容易出错,其中一些参数需要其中一些可选的默认值。
  • 我喜欢这种方法。构建对象的客户端代码更加简洁。尽管您确实会丢失编译时检查,但 build() 函数仍然允许在您的代码中进行验证检查。
【解决方案2】:

这是 C++ 问题之一(如果这可以称为问题)。除了试图保持ctor的参数数量最少之外,它没有其他解决方案。

其中一种方法是使用 props 结构,例如:

struct PropsA
{
    Object *a, *b, *c;
};

class ClassA
{
    ClassA(PropsA &props, ... other params);
};

这似乎很明显,但我确实使用过几次。在许多情况下,事实证明某些参数组是相关的。在这种情况下,为它们定义一个结构是有意义的。

我最糟糕的噩梦是瘦包装类。可以直接访问基础的方法和数据字段,而必须复制所有 ctor。当有 10 多个 ctor 时,创建包装器开始成为问题。

【讨论】:

  • 在大多数情况下,通过将相关方法也放入其中来使PropsA 成为一个成熟的类是有意义的。否则你最终会得到一个不是非常面向对象的数据结构。
  • 我把应用程序的要求、开发速度和代码的清晰度远远放在“面向对象”的花哨东西之前。
  • OOP 并不“花哨”,但它是您选择或离开的选择。我对某人只是以程序方式使用结构和函数来做这件事很好,但是如果您打算使用类,那么努力做正确的事情是值得的,否则您最终会陷入混乱,一半的代码是面向对象,其余的都是程序性的——我认为这不会带来太多的清晰度。
  • OOP 本身并不花哨。这是一组非常有用的方法。只需要首先考虑该集合中的哪些元素在每种情况下都是有用的,并且只有在考虑之后才能使用它们。如果某些东西没有合理的方法和/或存活时间很短 - 它应该是一个结构。它是一个结构是正确的。这并没有使代码不那么面向对象。 OOP 只是为了 OOP - 这是无用的花哨的东西。
  • 我完全同意结构有它们的位置,但这就是为什么我说“在大多数情况下,......” - 在大多数情况下,当构造函数接受“太多”参数时,这是一个标志该类做得太多,可以重构为更小的类。
【解决方案3】:

我认为你所描述的在 C++ 中不是问题——事实上,C++ 很好地反映了你的设计所表达的依赖关系:

  1. 要构造ClassA 类型的对象,您需要拥有三个Object 实例(abc)。
  2. 要构造ClassB 类型的对象,还需要三个Object 实例(def)。
  3. ClassB 类型的每个对象都可以被视为ClassA 类型的对象。

这意味着要构造ClassB 类型的对象,您需要提供三个Object 对象,它们是实现ClassA 接口所需的,然后是另外三个用于实现ClassB 接口的对象.

我相信这里的实际问题是你的设计。您可以考虑使用不同的方法来解决此问题:

  1. 不要让ClassB 继承ClassA。取决于您是否需要对任一类型的对象进行同质访问(例如,因为您有一个 ClassA* 集合,并且该集合还可能包含指向 ClassB 的指针),这可能是一个选项,也可能不是一个选项。
  2. 寻找总是一起出现的对象。就像 - 可能传递给任一构造函数的前两个对象(abde)代表某种配对。也许是对象标识符之类的?在这种情况下,为此引入一个专用的抽象(阅读:类型)可能会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-26
    • 2011-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多