我遇到了一个经常遇到的设计问题。
为了便于说明,我们假设我有一个多态类层次结构
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保留方式,而不是上述两种方式?
答案 0 :(得分:3)
有一个非循环访问者模式,无需了解每个子类。它依赖于dynamic_cast
,但可能就是您所需要的。
答案 1 :(得分:3)
班级打印本身没有错。它不违反SRP,因为打印不构成责任。
请记住,责任被定义为改变的理由。您不会更改类,因为您对打印的要求会发生变化。该类应该只向负责打印的实体发送名称 - 值对,称为格式化程序。发送名称 - 值对的过程永远不会自行更改。其中的任何更改仅由与打印无关的其他更改提示(例如,当您添加字段时,还会将其表示添加到打印过程中)。
格式化程序应该对它打印的类一无所知,只是根据一些要求提供名称 - 值对。当打印要求发生变化时,格式化程序会更改。因此,打印将是格式化程序的唯一责任。
答案 2 :(得分:1)
为了做到这一点,你需要找一些双重调度解决方案的访问者。双重调度方法更轻巧,所以如下:
在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;
}
答案 3 :(得分:0)
您可以提供一个界面来打印责任,并在您的类层次结构下保持共同的责任。例如:
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 { ... };