2016-05-12 3 views
5

私はOOPに関する愚かな質問を持っていますが、多形性の理由は何ですか?OOPにおけるポリモーフィズムの必要性は何ですか?

C++で

シンプルコード:遅延バインディングされた後のRectangleのドローを、長方形に指さ場合

class Shape{ 
public: 
virtual void draw(){ cout<<"Shape"<<endl;}; 
}; 

class Traingle: public Shape 
{ 
public: void draw(){cout<<"Triangle"<<endl;} 
}; 

class Rectangle: public Shape 
{ 
public: void draw(){cout<<"Rectangle"<<endl;} 
}; 

int main(){ 
Shape *ptr= new Traingle(); 
ptr->draw(); 
delete ptr; 
return 7; 
} 

ここptr->ドロー()関数は、三角形の描画を呼び出します。

Baseクラスのポインタを作成し、別のクラスを指す必要はありますか?仮想関数を使わずに別のクラスオブジェクトを作成し、必要な人を呼び出すことができます。基本的には同じことを

int main(){ 
    Traingle tObj; 
    tObj->draw(); 
    Rectangle rObj; 
    rObj->draw(); 
} 

などの 。

基本的に多形性はなぜですか?なぜ仮想?

このプロパティを使用すると、その必要性は何ですか?またどのような違いがありますか? 実際の例が役に立ちます!

+0

あなたのケースに新しいポインタを入れる必要はありません。しかし、それは「多形性の必要性」とは無関係です。 – juanchopanza

+0

仮想関数のポイントは、実行時に振る舞いを定義し、オブジェクトを動的に使用できるようにすることです。多態性はこれに与えられた名前です。 – Lawrence

+1

たとえば、図形のコレクションを作成することができます。 '' '' std :: vector '' 'を考えてみましょう。それは異なる形を含むかもしれませんが、あなたはそれらのすべてに共通のことをすることができます。 – isapego

答えて

3

多型は、関連するタイプのオブジェクトが同じに扱うことができるようにすることで、コードの再利用を可能にします。

あなたは異なる動作をサブクラスの数十を必要とするかもしれないことを考えてみましょう:

struct Shape1: public Shape { /* .. */ }; // triangle 
struct Shape2: public Shape { /* .. */ }; // rectangle 
// ... 
struct ShapeN: public Shape { /* .. */ }; // projection of rhombic triacontahedron 

はあなたがShapeポインタの配列が指すオブジェクトを処理する必要があるかもしれないことを考えてみましょう。多型と

、あなたは単一のベクターを必要とし、仮想関数を持つ単一のループが呼び出されます:

std::vector<Shape*> v = get_shape_vector(); 
for(Shape* s : v) 
    s->draw(); 

多型がなければ、あなたはタイプごとに別々の配列を管理し、それらを別々に処理しているだろう:

std::vector<Shape1> v1 = get_shape1_vector(); 
std::vector<Shape2> v2 = get_shape2_vector(); 
// ... 
std::vector<ShapeN> vN = get_shapeN_vector(); 

for(Shape1& s : v1) 
    s.draw(); 
for(Shape2& s : v2) 
    s.draw(); 
// ... 
for(ShapeN& s : vN) 
    s.draw(); 

多型を使用するコードの3行は、多型を使用しないコードの3行目よりも保守が簡単です。

プロセスを変更する必要がある場合があることを考慮してください。おそらく、描画の前に関数呼び出しを追加したいと思うかもしれません。

void pre_draw(Shape*); 

for(Shape* s : v) { 
    pre_draw(s); 
    s->draw(); 
} 

は多型がなければ、あなたはダースのそれぞれの機能の数十を定義および変更する必要がありますループ:

void pre_draw1(Shape1&); 
void pre_draw2(Shape2&); 
// ... 
void pre_drawN(ShapeN&); 

for(Shape1& s : v1) { 
    pre_draw1(s); 
    s.draw(); 
} 
for(Shape2& s : v1) { 
    pre_draw2(s); 
    s.draw(); 
} 
// ... 
for(ShapeN& s : v1) { 
    pre_drawN(s); 
    s.draw(); 
} 

は後でシェイプを追加することが考えてみましょう、あなたの側で多型を持っているとき、これは簡単です。多態性では、新しいタイプと仮想関数を定義するだけで済みます。配列にポインタを追加するだけで、他のすべての互換型のオブジェクトと同様に処理されます。

struct ShapeN1: public Shape { /* .. */ }; // yet another shape 

ポリモーピズムがなければ、新しいタイプを定義するだけでなく、新しいタイプを作成する必要があります。新しいpre_draw関数を作成する必要があります。そして、それらを処理するために新しいループを追加する必要があります。

void pre_drawN1(ShapeN1&); 
// ... 
std::vector<ShapeN1> vN1 = get_shapeN1_vector(); 
// ... 
for(ShapeN1& s : vN1) { 
    pre_drawN1(s); 
    s.draw(); 
} 

実際に、それぞれのシェイプタイプが処理される場所については、コードベース全体を調べてそこに新しいタイプのコードを追加する必要があります。


ここでNは小さくても大きくてもかまいません。 Nが大きいほど、多型の反復は避けられます。しかし、あなたが持っているサブクラスの数にかかわらず、新しいサブクラスを追加するときにコードベース全体を調べる必要はありません。

3

ベースクラスShapeを想像してください。それはGetAreaメソッドを公開します。 SquareクラスとRectangleクラスとCircleクラスを想像してください。別のGetSquareArea,GetRectangleAreaおよびGetCircleAreaのメソッドを作成する代わりに、派生クラスごとに1つのメソッドを実装するだけです。 Shapeの正確なサブクラスを知っている必要はありません。GetAreaと呼ぶと、具体的なタイプとは独立した結果が得られます。あなたは、単に基本タイプを使用しているクラスの正確な型を知っている必要はありません、と -

#include <iostream> 
using namespace std; 

class Shape 
{ 
public: 
    virtual float GetArea() = 0; 
}; 

class Rectangle : public Shape 
{ 
public: 
    Rectangle(float a) { this->a = a; } 
    float GetArea() { return a * a; } 
private: 
    float a; 
}; 

class Circle : public Shape 
{ 
public: 
    Circle(float r) { this->r = r; } 
    float GetArea() { return 3.14f * r * r; } 
private: 
    float r; 
}; 

int main() 
{ 
    Shape *a = new Circle(1.0f); 
    Shape *b = new Rectangle(1.0f); 

    cout << a->GetArea() << endl; 
    cout << b->GetArea() << endl; 
} 

ここで注目すべき重要なものです:

は、このコードを見てくださいあなたは正しい結果を得るでしょう。これはより複雑なシステムでも非常に便利です。多型を使用するための最良の例私見

+5

彼は既にdrawメソッドを持つクラス階層を持っていました。あなたはそれについて説明していないのですか? –

0

がコンテナである:

std::vector<Shape*> shapes; 
shapes.push_back(new Triangle()); 
shapes.push_back(new Rectangle()); 

使用すると、オブジェクトの実際の型が何であるかを気にすることなく、このコンテナを反復処理することができます

for (int i=0;i<shapes.size();i++){ 
    shapes[i]->draw(); 
} 
0

は、簡単に言えば、ポリモーフィズムを使用すると、同じボックスにさまざまな図形を配置し、ボックス内のすべてを異種の方法で扱うことができます。

あなたの例を拡張する:(それは公にShapeから派生しているため)

#include <iostream> 
#include <memory> 
#include <vector> 

class Shape{ 
public: 
    virtual void draw(){ std::cout<<"Shape"<< std::endl;}; 
}; 

class Traingle: public Shape 
{ 
public: 
    void draw() override 
    { 
     std::cout<<"Triangle"<< std::endl; 
    } 
}; 

class Rectangle: public Shape 
{ 
public: 
    void draw() override 
    { 
     std::cout<<"Rectangle"<< std::endl; 
    } 
}; 

int main(){ 

    std::vector<std::unique_ptr<Shape>> box_of_shapes; 

    box_of_shapes.emplace_back(new Traingle); 
    box_of_shapes.emplace_back(new Rectangle); 

    for (const auto& pshape : box_of_shapes) 
    { 
     pshape->draw(); 
    } 

    return 0; 
} 

RectangleShapeあるので、それは、(ポインタへ)Shapeに扱うのベクトルによって保持することができます。 ただし、drawメソッドが仮想なので、Rectangleは、発信者がShapeと考えている場合でも正しく描画されます。

期待出力:

Triangle 
Rectangle 
0

マジックワードはデカップリングです。たとえば、スーパーマーケットのストレージを管理するアプリケーションを作成しているとします。最初から、データを保存したり取得したりする方法が遅かれ早かれ必要になることは、ご存知のことです。しかし、あなたはどちらが最善の技術(まだリレーショナル・データベースかNoSqlか)を知っていません。

だから、あなたはインターフェイス(抽象クラ​​ス)を作成:

struct Serialize{ 
    virtual void save(Product product) = 0; 
    ... other method here ... 
}; 

を今、あなただけのシリアル化のメモリ内のバージョンを実装し、アプリケーションの他の部分の開発を続けていくことができます。

class InMemorySerialize : public Serialize { 
    ... implement stuff here ... 
}; 

シリアル化に依存したコードは、具体的なクラスが使用している気にしない、実際にシリアル化実装を変更するように変更する必要がある唯一の場所は、具象クラスの建設です。しかし、クラスの構築は1つの場所にしかなく、主な機能にかなり近いと思われます(詳細はstrategy patternをご覧ください)。

//in the main (switching these lines you will use different implementations) 
//the rest of your code will not change 
unique_ptr<Serialize> serializer(new InMemorySerialize()); 
//unique_ptr<Serialize> serializer(new OnFileSerialize(myfolder)); 

また、インターフェイスを実装するOpen/Close principleを達成する最も簡単な方法の一つです。メモリではなくファイルへのシリアライゼーションをサポートしたい場合は、別のファイルになる新しいクラスを作成します。したがって、既存のコードに触れることなく、アプリケーションの機能を拡張することができます。

具体的なオブジェクトの代わりにインターフェイスを使用すると、テストがはるかに簡単になります。テストに必要な動作を簡単に実装できます。

関連する問題