我有一个类的层次结构,如下所示:
class Critical
{
public:
Critical(int a, int b) : m_a(a), m_b(b) { }
virtual ~Critical() { }
int GetA() { return m_a; }
int GetB() { return m_b; }
void SetA(int a) { m_a = a; }
void SetB(int b) { m_b = b; }
protected:
int m_a;
int m_b;
};
class CriticalFlavor : public Critical
{
public:
CriticalFlavor(int a, int b, int flavor) : Critical(a, b), m_flavor(flavor) { }
virtual ~CriticalFlavor() { }
int GetFlavor() { return m_flavor; }
void SetFlavor(int flavor) { m_flavor = flavor; }
protected:
int m_flavor;
};
class CriticalTwist : public Critical
{
public:
CriticalTwist(int a, int b, int twist) : Critical(a, b), m_twist(twist) { }
virtual ~CriticalTwist() { }
int GetTwist() { return m_twist; }
void SetTwist(int twist) { m_twist = twist; }
protected:
int m_twist;
};
上述对我来说在设计方面似乎并不合适,最让我困扰的是 事实上,成员变量的添加似乎驱动了这些类的接口 (执行上述操作的真实代码稍微复杂一点,但仍然采用相同的模式)。 当需要另一个“关键”课程时,这将增加其他一些 属性。 这对你感觉合适吗?我怎么能重构这样的代码? 一个想法是在基础对象时只有一组接口并使用组合 如下:
class Critical
{
public:
virtual int GetA() = 0;
virtual int GetB() = 0;
virtual void SetA(int a) = 0;
virtual void SetB(int b) = 0;
};
class CriticalImpl : public Critical
{
public:
CriticalImpl(int a, int b) : m_a(a), m_b(b) { }
~CriticalImpl() { }
int GetA() { return m_a; }
int GetB() { return m_b; }
void SetA(int a) { m_a = a; }
void SetB(int b) { m_b = b; }
private:
int m_a;
int m_b;
};
class CriticalFlavor
{
public:
virtual int GetFlavor() = 0;
virtual void SetFlavor(int flavor) = 0;
};
class CriticalFlavorImpl : public Critical, public CriticalFlavor
{
public:
CriticalFlavorImpl(int a, int b, int flavor) : m_flavor(flavor), m_critical(new CriticalImpl(a, b)) { }
~CriticalFlavorImpl() { delete m_critical; }
int GetFlavor() { return m_flavor; }
void SetFlavor(int flavor) { m_flavor = flavor; }
int GetA() { return m_critical->GetA(); }
int GetB() { return m_critical->GetB(); }
void SetA(int a) { m_critical->SetA(a); }
void SetB(int b) { m_critical->SetB(b); }
private:
int m_flavor;
CriticalImpl* m_critical;
};
答案 0 :(得分:1)
我的建议:找到熟悉此代码的最耐心的人,并向他们询问其中的一些问题。我认为由于IP问题,您没有发布更完整的示例。这使得很难提供好的建议。
根据您的第一个代码示例,我会说只使用带有公共数据的结构而没有访问器。但如果我看到真正的代码,我可能会改变我的调子。
对于您的第二个代码示例:一个好处是,您可以让另一个类依赖于CriticalFlavor
,而不了解Critical
(例如,可用于实现类似Bridge模式的内容)。但是,如果潜在的利益在您的情况下不是实际的好处,那么它只是使您的代码不必要地复杂化和YAGNI(可能)。
评论者的评论:
基类应该是抽象的
我会说,“基类通常应该至少有一个虚方法,而不是析构函数”。如果没有,那么它只是一种在其他类之间共享公共代码或数据的方法;尝试使用构图。
大多数情况下,这些虚拟方法中至少有一个是纯虚拟的,因此基类将是抽象的。但有时候每个虚拟方法都有一个很好的默认实现,子类会选择要覆盖哪个。在这种情况下,请保护基类构造函数以防止实例化基类。
基类中不建议使用受保护的成员
...而且它们在非基类中完全没有意义,所以这个建议基本上就是说从不使用它们。
当是受保护的成员变量时,建议吗?不常。您的代码示例不够现实,无法确定您要执行的操作或最佳编写方式。成员受到保护,但有公共吸气者/安置者,因此他们基本上是公开的。
按界面设计
不要被这个带走,否则你可能会得到一个非常复杂的设计来完成一个非常简单的任务。在有意义的地方使用接口。如果没有看到一些使用Critical
及其子类的“调用代码”,很难判断它们是否有意义。谁致电GetFlavor
和GetTwist
?
调用代码是仅通过Critical
接口与Critical
子类交互,还是调用代码知道特定的子类并调用特定的子类方法?您是否为Critical
添加了接口方法,以便提供对仅存在于某些子类中的数据/功能的访问?这可能是一种难闻的气味。
您的评论:
成员变量的添加似乎驱动了这些类的接口
让我想起 C++ Coding Standards (Sutter / Alexandrescu)中的一个项目:“明白你正在写什么类的课程”。对不起,没有在线参考该项目(买书)。
我还建议您诚实地评估您的技能水平以及您的审核人员的技能水平。您的评论员是否经验丰富的C ++开发人员,他们真正知道他们在谈论什么(如果是这样,请听!),或者他们刚刚从Code Review 101返回并知道说“公共数据不好”和“析构函数应该始终是虚拟的” “?如果是后者,希望你能回答评论评论,说“这通常是好建议,但不适用于这种情况,因为XYZ。”
答案 1 :(得分:0)
如果不了解更多关于真实代码的内容,很难说继承是否是正确的想法。你想要多态性(如果它是CriticalFlavor,CriticalTwist或CriticalWhatever,那么就会对一个Critical发挥作用并获得不同的效果)? A和B的构造顺序是否重要?您是否计划使用更复杂的类树,例如CriticalFlavorTemperature和CriticalTwistColor?
那就是说,这就是我可能为组合方法做的事情:
class Critical
{
public:
Critical(int a, int b) : m_a(a), m_b(b) { }
~Critical() { }
int GetA() const { return m_a; }
int GetB() const { return m_b; }
void SetA(int a) { m_a = a; }
void SetB(int b) { m_b = b; }
private:
int m_a;
int m_b;
};
class CriticalFlavor
{
public:
CriticalFlavor(int a, int b, int flavor) : m_flavor(flavor), m_critical(a, b) { }
~CriticalFlavor() {}
int GetFlavor() const { return m_flavor; }
void SetFlavor(int flavor) { m_flavor = flavor; }
int GetA() const { return m_critical.GetA(); }
int GetB() const { return m_critical.GetB(); }
void SetA(int a) { m_critical.SetA(a); }
void SetB(int b) { m_critical.GetB(b); }
private:
int m_flavor;
Critical m_critical;
};
答案 2 :(得分:0)
@Marius我在帖子中可以理解的是,您希望将数据访问与实际数据结构分开。
class Critical
{
public:
virtual int GetA() = 0;
virtual int GetB() = 0;
virtual void SetA(int a) = 0;
virtual void SetB(int b) = 0;
};
class Flavor
{
public:
virtual int GetFlavor() = 0;
virtual void SetFlavor(int a) = 0;
};
template<class T>
class AccessCritical : public Critical
{
public:
AccessCritical(T &ref) : data(ref) {}
int GetA() { return (data.m_a); }
int GetB() { return (data.m_b); }
void SetA(int a) { data.m_a = a; }
void SetB(int b) { data.m_b = b; }
private:
T &data;
};
template<class T>
class AccessFlavor : public Flavor
{
public:
AccessFlavor(T &ref) : data(ref) {}
int GetFlavor() { return (data.m_flavor); }
void SetFlavor(int flavor) { data.m_flavor = flavor; }
private:
T &data;
};
struct MyStructA {
int m_a, m_b;
AccessCritical<MyStructA> critical;
MyStructA() : critical(*this) {}
};
struct MyStructB {
int m_flavor;
AccessFlavor<MyStructB> flavor;
MyStructB() : flavor(*this) {}
};
struct MyStructC {
int m_a, m_b;
int m_flavor;
AccessCritical<MyStructC> critical;
AccessFlavor<MyStructC> flavor;
MyStructC() : critical(*this), flavor(*this) {}
};
void processFlavor(Flavor *flavor)
{
flavor->SetFlavor(flavor->GetFlavor() * 3);
}
void processCritical(Critical *critical)
{
critical->SetA(critical->GetA() + critical->GetB());
}
int main() {
MyStructA a;
a.critical.SetA(2);
a.critical.SetB(3);
processCritical(& a.critical);
MyStructB b;
b.flavor.SetFlavor(4);
processFlavor(& b.flavor);
MyStructC c;
c.critical.SetA(20);
c.critical.SetB(30);
c.flavor.SetFlavor(40);
processFlavor(& c.flavor);
processCritical(& c.critical);
cerr << a.critical.GetA() << ' ' << a.critical.GetB() << endl;
cerr << b.flavor.GetFlavor() << endl;
cerr << c.critical.GetA() << ' ' << c.critical.GetB() << endl;
cerr << c.flavor.GetFlavor() << endl;
return 0;
}
答案 3 :(得分:0)
在我看来,这种类的层次只不过是数据桶,除了充当某些底层数据模型的访问者之外,它们实际上并没有做任何其他事情。也许只是将您的设计限制为使用关键访问器的单个类,以及另一个更通用的命名属性方法(即.std :: string GetStringProperty(std :: string key){})?