经过长时间的C风格程序编码,我才开始“获得”OOP。所以我怀疑可能有标准的方法来处理我所面临的情况。我有一个应用程序,其类层次结构如下所示:
#include <iostream>
using namespace std;
class A {
public:
virtual int intf() { return 0;} // Only needed by B
virtual double df() {return 0.0;} // Only needed by C
};
class B : public A {
int intf() {return 2;}
// B objects have no use for df()
};
class C : public B {
double df() {return 3.14;}
// C objects have no use for intf()
};
int main(){
// Main needs to instantiate both B and C.
B b;
C c;
A* pa2b = &b;
A* pa2c = &c;
cout << pa2b->intf() << endl;
cout << pa2b->df() << endl;
cout << pa2c->intf() << endl;
cout << pa2c->df() << endl;
return 0;
}
现在这个程序编译并运行正常。但是,我对它的设计有疑问。 A类是通用接口,不需要实例化。 B类和C类需要。关于函数:B需要intf()而不是C,而C需要df()而不是B.如果我在A中使intf(){df()}纯虚,那么就没有合理的定义对于B {C},df(){intf()}。
编辑: B和C共享一些数据成员以及f()以外的一些成员函数。我没有向我展示我的精简代码。
最后,作为标准,我的应用程序需要通过指向A的指针访问B和C.所以我的问题是:有没有办法'清理'这个设计,以便不需要/空成员函数定义(如正如我在A)的声明/定义中所做的那样可以消除吗?这些类之间存在明确的“IS-A”关系。所以即使我分享每个新手对继承的兴奋,我也不觉得我已经延伸了我的设计,所以我可以使用继承。
后台以防万一:我正在实施一个回归套件。 A类实现每个回归共有的函数和矩阵(例如依赖变量和独立变量)。 B类是逻辑回归,有两个类('0'和'1'),定义了成本函数,以及两类逻辑回归的训练算法。 C类是多类逻辑回归。它通过使用“one-vs-all”算法训练多个类来扩展B类。因此,在某种意义上,如果您认为您感兴趣的类作为正例,而所有其他作为反面例子,则C是二元逻辑回归。然后,您为每个类执行此操作以实现多类回归。有问题的函数(intf和df)返回输出。在逻辑回归的情况下,返回值是向量,而对于多类回归,它是矩阵。并且,如上所述,B和C对彼此的返回功能没有任何用处。除了我似乎无法消除A(回归类)中的冗余定义。
感谢您的帮助。
答案 0 :(得分:5)
查看利斯科夫替代原则(http://en.wikipedia.org/wiki/Liskov_substitution_principle)。它声明子类必须履行与超类相同的合同。在您的示例中,两个子类都不会这样做。 “Is-A”关系不足以证明继承的合理性。
一种选择是使用单个模板方法,如下所示:
template <typename T>
class A<T> {
T getValue();
}
class B : A<int> {
int getValue();
}
class C: A<double> {
double getValue();
}
这将允许两个子类履行合同,同时允许方法的返回类型根据子类定义而变化。
如果你想学习更多面向对象的编程“最佳实践”,谷歌“罗伯特马丁SOLID”
答案 1 :(得分:3)
你触及了OOP最具争议的一点:is-a ==派生模式,导致了“上帝对象”的反模式,因为一切都是神的孩子,上帝知道每一种方法每个人都有一个“答案”(读作“默认实施”)。
“Is-a”不足以证明继承是合理的,在这种情况下不存在替换能力,但在现实世界中,任何对象都不能完全替换为另一个对象,否则它就不会有所不同。
你处于“无处可去”的地方,替换原则不能很好地运作,但是 - 同时 - 虚函数看起来是实现动态调度的最佳工具。
你唯一可以做的就是达成妥协,并牺牲其中一个。
到目前为止,由于B和C没有任何共同之处(没有共享的有用方法),所以根本不要让这些方法来自A. 如果您有“共享”的内容,则可能是在输入B相关特定代码或C相关特定代码之前发现B或C类型的运行时机制。
这通常使用一个具有运行时类型指示符的公共库来完成,或者只是一个虚拟函数(通常是析构函数),以使dynamic_cast能够工作。
class A
{
public:
virtual ~A() {}
template<class T>
T* is() { return dynamic_cast<T*>(this); }
};
class B: public A
{
public:
int intf() { return 2; }
};
class C: public A
{
public:
double df() { return 3.14; }
};
int main()
{
using namespace std;
B b;
C c;
A* ba = &b;
A* ca = &c;
B* pb = ba->is<B>();
if(pb) cout << pb->intf() << endl;
C* pc = ca->is<C>();
if(pc) cout << pc->df() << endl;
}