4

After a long time of C-style procedural coding, I am just beginning to 'get' OOP. So I suspect there may be standard way of dealing with the situation I am facing. I have an application with the class hierarchy shown below:

#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;
}

Now this program compiles and runs fine. However, I have question about its design. Class A is the common interface and does not need to be instantiated. Class B and C need to be. Regarding the functions: intf() is needed by B but not C, and df() is needed by C but not B. If I make intf() {df()} pure virtual in A, then there is no reasonable definition of df() {intf()} for B {C}.

Edit: B and C share some data members and also some member functions other than f(). I have not shown it my stripped down code.

Finally, as is standard, my application needs to access both B and C through a pointer to A. So my question is: Is there a way to 'clean up' this design so that unrequired/empty member function definitions (such as I have done in declaration/definition of A) can be eliminated? There is a clear "IS-A" relationship between the classes. So even though I share every newbie's thrill about inheritance, I dont feel I have stretched my design just so I could use inheritance.

Background in case it helps: I am implementing a regression suite. Class A implements functions and matrices common to every regression (such as dependent and independent variables). Class B is logistic regression with two classes ('0' and '1') and defines cost functions, and training algorithm for two-class logistic regression. Class C is multi-class logistic regression. It extends class B by training for multiple classes using the "one-vs-all" algorithm. So in a sense C is a binary logistic regression if you think of your class of interest as positive examples and all others as negative examples. Then you do it for every class to implement multi-class regression. The functions (intf and df) in question return the output. In case of logistic regression, the return value is a vector, while for multiclass regression, it is a matrix. And, as stated above, B and C dont have any use for each others' return functions. Except that I cant seem to be able to eliminate redundant definitions in A (the regression class).

Thanks for your help.

4

2 に答える 2

5

Liskov Substitution Principle (http://en.wikipedia.org/wiki/Liskov_substitution_principle) を参照してください。サブクラスはスーパークラスと同じ契約を満たさなければならないと述べています。あなたの例では、どちらのサブクラスもこれを行いません。「Is-A」関係は、継承を正当化するのに十分ではありません。

1 つのオプションは、次のような単一のテンプレート メソッドを使用することです。

template <typename T>
class A<T> {
    T getValue();
}

class B : A<int> {
    int getValue();
}

class C: A<double> {
    double getValue();
}

これにより、サブクラスの定義に基づいてメソッドの戻り値の型を変更しながら、両方のサブクラスでコントラクトを実行できます。

オブジェクト指向プログラミングの「ベスト プラクティス」について詳しく知りたい場合は、「Robert Martin SOLID」をググってください。

于 2012-10-26T19:50:52.113 に答える
3

あなたは、OOP の最も物議を醸すポイントの 1 つに触れました: is-a == 派生パターンは、結果的に「神のオブジェクト」アンチパターンになります。すべてに対して「答え」(「デフォルトの実装」を読む)を持っています。

「Is-a」は、置換機能が存在しない継承を正当化するのに十分ではありませんが、現実の世界では、別のオブジェクトと完全に置換できるオブジェクトはありません。そうでなければ、違いはありません。

あなたは、置換原理がうまく機能しない「辺境の地」にいますが、同時に、仮想関数は動的ディスパッチを実装するための最良のツールに見えます。

あなたができる唯一のことは、妥協して、2 つのうちの 1 つを犠牲にすることです。

状況を見る限り、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;
}
于 2012-10-26T20:12:18.030 に答える