2016-05-12 43 views
5

Ho una domanda sciocca sul concetto di OOP, qual è la ragione per cui ci occupiamo di polimorfismo ??Qual è la necessità del polimorfismo in OOP?

semplice codice in C++:

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

Qui ptr-> funzione draw() chiamerà il sorteggio Triangolo, se puntato al rettangolo poi rettangolo sorteggio, che è l'associazione tardiva.

Qual è la necessità di creare un puntatore di classe Base e puntarlo verso classi diverse? Siamo in grado di creare oggetti di classe separati senza alcuna funzione virtuale e chiamare ciò che è necessario. come

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

che fa sostanzialmente la stessa cosa;

Perché il polimorfismo in pratica? perché virtuale?

Qual è il bisogno di esso o che differenza fa usando questa proprietà? E un esempio di caso reale potrebbe aiutare !!

+0

Non c'è bisogno di un nuovo puntatore nel tuo caso. Ma questo non è correlato a "qual è il bisogno (per) il polimorfismo ..." – juanchopanza

+0

Il punto delle funzioni virtuali è così che possiamo definire il comportamento in fase di esecuzione e consentire l'utilizzo dinamico degli oggetti. Il polimorfismo è il nome dato a questo. – Lawrence

+1

Ad esempio, potresti voler creare una raccolta di forme. Si consideri '' 'std :: vector ' ''. Può contenere forme diverse ma puoi fare cose comuni a tutte loro. – isapego

risposta

3

polimorfismo permette riutilizzo del codice, consentendo oggetti di tipi correlati ad essere trattati allo stesso.

consideri che potrebbe essere necessario decine di sottoclassi che si comportano in modo diverso:

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

consideri che potrebbe essere necessario per elaborare oggetti appuntiti da una serie di Shape puntatori.

Con polimorfismo, è necessario un singolo vettore, e un singolo ciclo con funzione virtuale chiamate:

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

Senza polimorfismo, si avrebbe il gestore di un array separato per ciascun tipo ed elaborarli separatamente:

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(); 

Le 3 righe di codice che utilizzano il polimorfismo sono molto più facili da gestire rispetto alle linee di codice 3 * N che non utilizzano il polimorfismo.

Considerare che potrebbe essere necessario modificare il processo. Forse vuoi aggiungere una chiamata di funzione prima di disegnare. Questo è semplice quando si ha il polimorfismo dalla vostra parte:

void pre_draw(Shape*); 

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

Senza il polimorfismo, è necessario definire le decine di funzioni e modificare ogni della dozzina loop:

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(); 
} 

Si consideri che è possibile aggiungere forme più tardi . Con il polimorfismo, è sufficiente definire il nuovo tipo e la funzione virtuale. È possibile semplicemente aggiungere puntatori ad esso nell'array e verranno elaborati proprio come gli oggetti di ogni altro tipo compatibile.

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

Senza polimorfismo, oltre a definire il nuovo tipo, è necessario creare un nuovo array per esso. E avresti bisogno di creare una nuova funzione pre_draw. E avresti bisogno di aggiungere un nuovo ciclo per elaborarli.

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

In realtà, si avrebbe bisogno di passare attraverso la vostra intera base di codice per i luoghi in cui ogni di tipo di forma viene elaborato e aggiungere il codice per il nuovo tipo lì.


Ora, N potrebbe essere piccolo o grande. Maggiore è N, più il polimorfismo di ripetizione evita. Ma, non importa quante poche sottoclassi hai, non dover guardare attraverso l'intera base di codice quando ne aggiungi una nuova è un grande vantaggio.

3

Immaginate una classe base Shape. Espone un metodo GetArea. Immagina una classe Square e una classe Rectangle e una classe Circle. Invece di creare separati metodi GetSquareArea, GetRectangleArea e GetCircleArea, è possibile implementare un solo metodo in ciascuna classe derivata. Non è necessario sapere quale sottoclasse esatta di Shape si utilizza, è sufficiente chiamare il numero GetArea e si ottiene il risultato, indipendentemente dal tipo concreto.

Date un'occhiata a questo codice:

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

Una cosa importante da notare qui è - non c'è bisogno di conoscere il tipo esatto della classe che si sta utilizzando, proprio il tipo di base, e otterrai il risultato giusto Questo è molto utile anche nei sistemi più complessi.

+5

Aveva già una gerarchia di classi con un metodo di disegno. Non hai spiegato in termini di questo? –

0

Imho il miglior esempio per l'utilizzo di polimorfismo è un contenitore:

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

è possibile scorrere il contenitore senza preoccuparsi di ciò che è il tipo effettivo degli oggetti:

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

In poche parole, il polimorfismo ti consente di inserire forme diverse nella stessa scatola e di trattare ogni cosa nella scatola in modo eterogeneo.

estendere il tuo esempio:

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

Rectangleè unaShape (perché è pubblicamente derivato da Shape) Di conseguenza può essere tenuto da un vettore che si occupa di (puntatori a) Shape. Tuttavia, poiché il metodo draw è virtuale, un Rectangle verrà disegnato correttamente anche se il chiamante sta pensando solo a Shape.

output previsto:

Triangle 
Rectangle 
0

La parola magica è disaccoppiamento. Supponiamo che tu stia creando un'applicazione per gestire lo stoccaggio di un supermercato. Sapete fin dall'inizio che avrete bisogno prima o poi di un modo per archiviare e recuperare i dati. Tuttavia, non si sa quale sia la migliore tecnologia da usare ancora (potrebbe essere un database relazionale o uno NoSQL per esempio).

Così, si crea un'interfaccia (classe astratta):

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

Ora, si può portare avanti con lo sviluppo delle altre parti della domanda appena applicazione di un versione di memoria di Serialize.

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

Il codice che dipendono da Serialize non si preoccupa quale classe sta usando concreto, l'unico posto che ha realmente bisogno di essere cambiato per modificare l'implementazione serializzare è la costruzione della classe concreta. Ma la costruzione della classe è probabile che sia in un solo posto e abbastanza vicino alla funzione principale (guarda strategy pattern per maggiori informazioni).

//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)); 

Inoltre, implementando un'interfaccia è uno del metodo più semplice per soddisfare la Open/Close principle. Se si desidera supportare una serializzazione su file anziché in memoria, si intende creare una nuova classe che si troverà in un file separato. Quindi, sei aperto ad aumentare la funzionalità della tua applicazione senza toccare il codice esistente.

Come ultima osservazione, il test è molto più semplice se si dipendono dalle interfacce anziché dall'oggetto concreto. È possibile implementare facilmente il comportamento necessario per il test.