C++:在不违反 SRP 的情况下向多态类层次结构添加方法?

Posted

技术标签:

【中文标题】C++:在不违反 SRP 的情况下向多态类层次结构添加方法?【英文标题】:C++: Adding methods to a polymorphic class hierarchy without violating SRP? 【发布时间】:2013-07-25 06:29:13 【问题描述】:

我有一个经常遇到的设计问题。

为了便于说明,假设我有一个多态的类层次结构

class A  public: virtual ~A()  ... ;
class B: public A  ... ;
class C: public B  ... ;
class D: public A  ... ;
...

我希望能够以多态方式打印这些类的实例,即每个类都有自己的打印方式。实现这一点的明显方法是添加

virtual void print(OutputStream &os) = 0;

进入基类并在每个子类中覆盖此方法。但是,如果类的原始职责与打印无关,这会给它们增加另一个职责,从而违反SRP。

我的问题是:在不违反 SRP 的情况下实现所需行为的正确方法是什么?

在this post中,提出了基于Visitor design pattern的解决方案。但是,然后我需要创建一个必须知道A 的每个子类的类。我希望能够添加和删除子类,而无需总是修改访问者。

除了上述两种方法之外,还有其他一些保留 SRP 的方法吗?

【问题讨论】:

你的 print() 方法是做什么的?它的输出是否包含有关类内部的任何信息?在这种情况下,我会将它的定义放入类中,因为唯一的另一种选择是打破封装,而且更不灵活。我知道的所有其他解决方案都与在 C++ 中实现函数参数的动态分派有关,这可能很难看。 @Markus Mayr:假设发出的信息可以从每个子类的公共接口获取,但不一定只使用基类接口。否则,正如您已经提到的,封装将需要被打破。 【参考方案1】:

有一个 非循环访问者 模式,无需了解每个子类。它依赖于dynamic_cast,但可能是您需要的。

【讨论】:

谢谢,我不知道这种模式!这可能是我需要的,尽管 n.m. 的答案。也有道理。但是您的回答也适用于其他明显违规的情况。【参考方案2】:

类打印本身并没有错。它不违反 SRP,因为打印不构成责任。

请记住,责任被定义为改变的理由。您不会更改课程,因为您对打印的要求发生了变化。该类只应将名称-值对发送到负责打印的实体,称为格式化程序。这个发送名称-值对的过程永远不会自行改变。其中的任何更改仅由与打印无关的其他更改提示(例如,当您添加一个字段时,您也将其表示添加到打印过程中)。

格式化程序应该对它打印的类一无所知,而只是根据某些要求呈现名称-值对。当打印要求发生变化时,格式化程序也会发生变化。因此,打印将是格式化程序的唯一责任。

【讨论】:

【参考方案3】:

为了做到这一点,您需要寻找某种双重调度解决方案的访客。双分派方法更轻量级,那么像这样的东西怎么样:

在 A 中:

class Processor

public:
  virtual void Process(const A &a)const 
  virtual void Process(const B &b)const 
  virtual void Process(const C &c)const 
  virtual void Process(const D &d)const 
  virtual void Process(const E &e)const 
;

在 A 中:

class A

public:
  virtual void Process(const Processor &processor) 
  
    processor.Process(*this);
  
;

然后,在每个派生类中使用相同的定义覆盖Process

virtual void Process(const Processor &processor) 

  processor.Process(*this);

这将确保调用Process 中的正确重载。

现在,创建一个流处理器:

class StreamProcessor : public Processor

private:
 OutputStream &m_OS;

public:
  StreamProcessor(OutputStream &os) : m_OS(os)
  
  

  virtual void Processor(const A &a)const
  
   m_os << "got a A";
  

  virtual void Processor(const B &b)const
  
   m_os << "got a B";
  

  virtual void Processor(const C &c)const
  
   m_os << "got a C";
  

  // etc
;

然后:

 OutputStream &operator<<(OutputStream &os, A &a)
 
   PrintProcessor(os);
   a.Process(PrintProcessor);
   return os;
 

【讨论】:

感谢您的回答。但是,正如我在问题中所建议的那样,我希望能够添加和删除子类,而无需始终修改访问者(在您的示例中为 Processor)。 很公平,但是您必须在添加或删除类时修改 something。在 C# 的 Java 之类的语言中,您可以使用 relfection,但在 C++ 中这不是您的选择。【参考方案4】:

您可以提供一个用于打印职责的接口,并将公共职责保留在您的类层次结构中。示例:

class Printer  public: virtual void print(OutputStream &os) = 0; 
class A  public: virtual ~A()  ... ;
class B: public A, public Printer  ... ; // this needs print function, use interface.
class C: public B  ... ;
class D: public A  ... ;

【讨论】:

感谢您的回答,但我希望能够打印A 的任何子类,而不仅仅是B 及其子类。这可以通过在A 中继承Printer 来解决。但是,我仍然想知道这种方法是否违反了 SRP。 如果您希望能够打印 A 的所有类,我不确定有什么问题。如果需要在 A 的子类上打印,那么您应该拥有它。评估这些类是否需要这种需求或不取决于您(我们不可能用有限的信息进行评估),但据我了解,您想要打印 A 的子类并且如果继承的类需要实现打印功能,则不违反 SRP .

以上是关于C++:在不违反 SRP 的情况下向多态类层次结构添加方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不复制对象的情况下向 Python 公开返回 C++ 对象的函数?

是否可以在不手动将重写的克隆方法添加到 C++ 中的每个派生类的情况下克隆多态对象?

在不创建新视图或知道特定父视图的情况下向 UIButton 类添加约束

在不破坏现有基类的情况下向具有许多派生类的抽象基类添加新方法

c++ 模板化抽象基类数组,不违反严格别名规则

开心档之C++ 多态