2016-05-12 64 views
5

Mam głupie pytanie na temat koncepcji OOP, jaki jest powód dla którego szukamy polimorfizmu?Jaki jest potrzebny polimorfizm w OOP?

prosty kod w 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; 
} 

Tutaj ptr-> narysować function() wywoła remisu Triangle, jeżeli wskazał Prostokąt Prostokąt następnie rysować, która jest późnym wiązaniem.

Co jest konieczne, aby utworzyć wskaźnik klasy Base i skierować go do różnych klas? Możemy stworzyć oddzielny obiekt klasy bez żadnej funkcji wirtualnej i zadzwonić, który jest potrzebny. podobnie jak

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

co zasadniczo robi to samo;

Dlaczego właściwie polimorfizm? dlaczego wirtualny?

Jaka jest jej potrzeba lub jaka jest różnica w korzystaniu z tej nieruchomości? A prawdziwy przykład przypadku pomoże!

+0

Nie ma potrzeby dodawania nowego wskaźnika w twoim przypadku. Ale to nie ma związku z "czym jest potrzeba (dla) polimorfizmu ...". – juanchopanza

+0

Punkt funkcji wirtualnych polega na tym, że możemy zdefiniować zachowanie w środowisku wykonawczym i umożliwić dynamiczne wykorzystanie obiektów. Polimorfizm to nazwa nadana temu. – Lawrence

+1

Na przykład możesz utworzyć kolekcję kształtów. Rozważmy '' 'std :: vector ' ''. Może zawierać różne kształty, ale można zrobić wspólne rzeczy dla wszystkich z nich. – isapego

Odpowiedz

3

Polimorfizm umożliwia ponowne wykorzystanie kodu, umożliwiając obiektów powiązanych rodzajów należy traktować tak samo.

Uważają, że może trzeba dziesiątki podklasy, które zachowują się inaczej:

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

Uważają, że może trzeba przetwarzania obiektów wskazanych przez tablicę Shape wskaźników.

z polimorfizmem, trzeba jeden wektor, a pojedyncza pętla z funkcją wirtualną nazywa:

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

Bez polimorfizmu, trzeba zarządzać oddzielną tablicę dla każdego typu i przetwarzać je oddzielnie:

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

Trzy linie kodu korzystające z polimorfizmu są łatwiejsze do utrzymania niż 3 * N wiersze kodu, które nie używają polimorfizmu.

Należy wziąć pod uwagę, że konieczne może być zmodyfikowanie procesu. Być może chcesz dodać wywołanie funkcji przed rysowaniem. To jest proste, gdy masz polimorfizmu na swojej stronie:

void pre_draw(Shape*); 

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

Bez polimorfizmu, trzeba zdefiniować dziesiątki funkcji i modyfikować każdy z kilkunastu pętle:

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

Uważają, że można dodawać kształty później . W przypadku polimorfizmu wystarczy zdefiniować nowy typ i funkcję wirtualną. Możesz po prostu dodać do niej wskaźniki i będą one przetwarzane tak jak obiekty każdego innego kompatybilnego typu.

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

Bez definiowania polimorfizmów, oprócz definiowania nowego typu, trzeba byłoby utworzyć dla niego nową tablicę. I musisz utworzyć nową funkcję pre_draw. I musisz dodać nową pętlę, aby je przetworzyć.

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

W rzeczywistości, trzeba by przejść przez cały kod bazowy dla miejsc, w których każdy z rodzaju kształt jest przetwarzany i dodać kod dla nowego typu tam.

Teraz N może być mały lub wielki. Im większe jest N, tym bardziej unika się polimorfizmu powtórzeń. Ale bez względu na to, jak mało masz podklas, nie musisz przeglądać całej bazy kodu, kiedy dodajesz nową, jest wielkim dobrodziejstwem.

3

Wyobraź sobie klasę bazową Shape. Odsłania metodę GetArea. Wyobraź sobie klasę Square i klasę Rectangle oraz klasę Circle. Zamiast tworzyć oddzielne metody, można zaimplementować tylko jedną metodę w każdej z klas pochodnych. Nie musisz wiedzieć, której dokładnie podklasy używasz Shape, wystarczy zadzwonić pod numer GetArea, a otrzymasz wynik, niezależnie od tego, który konkretny typ to.

Wystarczy popatrzeć na ten kod:

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

Ważną rzeczą, aby zauważyć tutaj jest - nie trzeba znać dokładny typ klasy używasz, tylko typ podstawowy i otrzymasz właściwy wynik. Jest to bardzo przydatne również w bardziej złożonych systemach.

+5

Miał już hierarchię klas za pomocą metody losowania. Czy nie wyjaśniłeś tego w ten sposób? –

0

IMHO najlepszy przykład za pomocą polimorfizmu jest kontenerem:

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

można iterację tego kontenera nie dbając o to, co jest rzeczywiste rodzaju obiektów:

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

Mówiąc najprościej, polimorfizm pozwala na umieszczanie różnych kształtów w tym samym pudełku i traktowanie wszystkiego w polu w niejednorodny sposób.

przedłużenia przykład:

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

RectanglejestShape (bo to publicznie pochodzi od Shape) W związku z tym może być pociągnięty przez wektor, który zajmuje się (wskaźniki do) Shape. Jednakże, ponieważ metoda draw jest wirtualna, Rectangle będzie rysował poprawnie, nawet jeśli osoba dzwoniąca myśli tylko o tym jako o numerze Shape.

oczekiwany wynik:

Triangle 
Rectangle 
0

Magiczne słowo to oddzielając. Załóżmy, że tworzysz aplikację do zarządzania magazynem w supermarkecie. Od samego początku wiesz, że będziesz potrzebować prędzej czy później sposobu przechowywania i pobierania danych. Jednak nie wiesz, która jest najlepsza technologia do wykorzystania (może to być na przykład relacyjna baza danych lub NoSql na przykład).

Więc utworzyć interfejs (abstract class):

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

Teraz można prowadzić z rozwojem innych części wniosku właśnie wdraża w wersji pamięci Serialize.

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

Kod, który zależy od Serialize nie obchodzi, która klasa jest za pomocą betonu, to jedyne miejsce, które naprawdę muszą zostać zmienione, aby zmodyfikować realizację serialize jest budowa betonowej klasy. Ale konstrukcja klasy może znajdować się tylko w jednym miejscu i całkiem blisko głównej funkcji (spójrz na strategy pattern, aby uzyskać więcej informacji).

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

Ponadto, wdrożenie interfejsu jest jedną z najprostszych metod wypełnienia Open/Close principle. Jeśli chcesz obsługiwać szeregowanie do pliku zamiast do pamięci, utworzysz nową klasę, która będzie znajdować się w oddzielnym pliku. Dzięki temu możesz zwiększyć funkcjonalność swojej aplikacji bez dotykania istniejącego kodu.

Ostatnia uwaga: testowanie jest znacznie łatwiejsze, jeśli polegamy na interfejsach zamiast na konkretnym obiekcie. Możesz łatwo zaimplementować zachowanie, którego potrzebujesz do testu.