2016-05-12 17 views
5

Tôi có câu hỏi ngớ ngẩn về khái niệm OOPs, lý do chúng tôi đi cho đa hình là gì ??Sự đa hình cần trong OOP là gì?

mã đơn giản trong 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; 
} 

Đây ptr-> vẽ() chức năng sẽ gọi hòa Triangle, nếu chỉ vào Rectangle sau đó Rectangle bốc thăm, đó là ràng buộc muộn.

Sự cần thiết của việc tạo con trỏ Lớp cơ sở và trỏ nó đến các lớp khác nhau là gì? Chúng ta có thể tạo đối tượng lớp riêng biệt mà không cần bất kỳ chức năng ảo nào và cần gọi whicever. như

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

về cơ bản cũng thực hiện tương tự;

Tại sao đa hình về cơ bản? tại sao ảo?

Nhu cầu của nó hoặc sự khác biệt nào khi sử dụng thuộc tính này? Và ví dụ trường hợp thực sự sẽ giúp !!

+0

Không cần phải có con trỏ mới trong trường hợp của bạn. Nhưng điều đó không liên quan đến "đa hình cần (cho) đa hình ..." – juanchopanza

+0

Điểm của các hàm ảo là để chúng ta có thể định nghĩa hành vi khi chạy và cho phép sử dụng động các đối tượng. Đa hình là tên được đặt cho điều này. – Lawrence

+1

Ví dụ: bạn có thể muốn tạo bộ sưu tập hình dạng. Xem xét '' 'std :: vector ' ''. Nó có thể chứa các hình dạng khác nhau nhưng bạn có thể làm những việc chung cho tất cả chúng. – isapego

Trả lời

3

Polymorphism phép tái sử dụng mã bằng cách cho phép các đối tượng của loại liên quan đến được đối xử như nhau.

Hãy xem xét rằng bạn có thể cần hàng chục lớp con mà hành xử khác nhau:

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

Hãy xem xét rằng bạn có thể cần để xử lý đối tượng được trỏ bởi một loạt các Shape con trỏ.

Với đa hình, bạn cần có một véc tơ duy nhất, và một vòng duy nhất với chức năng ảo gọi:

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

Nếu không có đa hình, bạn sẽ phải quản lý một mảng riêng cho từng loại và xử lý chúng một cách riêng biệt:

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 dòng mã sử dụng đa hình là cách dễ dàng hơn để duy trì hơn 3 * N dòng mã không sử dụng đa hình.

Hãy xem xét rằng bạn có thể cần phải sửa đổi quy trình. Có lẽ bạn muốn thêm một cuộc gọi hàm trước khi vẽ. Đây là đơn giản khi bạn có tính đa hình về phía bạn:

void pre_draw(Shape*); 

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

Nếu không có đa hình, bạn cần phải xác định hàng chục chức năng và chỉnh sửa từng chục bẹ:

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

Hãy xem xét rằng bạn có thể thêm hình dạng sau . Với tính đa hình, bạn chỉ cần xác định loại mới và hàm ảo. Bạn có thể chỉ cần thêm con trỏ vào nó vào mảng và chúng sẽ được xử lý giống như các đối tượng của mọi loại tương thích khác.

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

Nếu không có polymorhpism, ngoài việc xác định loại mới, bạn sẽ phải tạo một mảng mới cho nó. Và bạn sẽ cần phải tạo một hàm pre_draw mới. Và bạn sẽ cần phải thêm một vòng lặp mới để xử lý chúng.

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

Thực tế, bạn sẽ cần phải đi qua toàn bộ cơ sở mã cho những vị trí mà mỗi loại hình được xử lý và thêm mã cho loại mới đó.


Bây giờ, N có thể nhỏ hoặc tuyệt vời. N càng lớn thì tính đa hình lặp lại càng tránh được. Tuy nhiên, không có vấn đề làm thế nào vài lớp con bạn có, không phải xem xét thông qua toàn bộ cơ sở mã của bạn khi bạn thêm một cái mới là một lợi ích tuyệt vời.

3

Hãy tưởng tượng một lớp cơ sở Shape. Nó hiển thị phương thức GetArea. Hãy tưởng tượng một lớp học Square và một lớp học Rectangle và lớp học Circle. Thay vì tạo các phương thức riêng biệt GetSquareArea, GetRectangleAreaGetCircleArea, bạn chỉ cần thực hiện một phương thức trong mỗi lớp dẫn xuất. Bạn không cần phải biết lớp con chính xác của Shape bạn sử dụng, bạn chỉ cần gọi GetArea và bạn nhận được kết quả của mình, độc lập với loại bê tông nào.

Có một cái nhìn tại mã này:

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

Một điều quan trọng cần lưu ý ở đây là - bạn không cần phải biết chính xác loại của lớp bạn đang sử dụng, chỉ cần loại cơ sở, và bạn sẽ nhận được kết quả đúng. Điều này rất hữu ích trong các hệ thống phức tạp hơn.

+5

Ông đã có một hệ thống phân cấp lớp với phương thức vẽ. COuld bạn đã không giải thích về điều đó? –

0

IMHO ví dụ tốt nhất cho việc sử dụng đa hình là một container:

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

bạn có thể lặp qua thùng chứa này mà không cần quan tâm đến là những gì các loại thực tế của các đối tượng:

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

một cách đơn giản, đa hình cho phép bạn đặt các hình dạng khác nhau trong cùng một hộp và xử lý mọi thứ trong hộp theo cách không đồng nhất.

mở rộng ví dụ của bạn:

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

Rectanglelà mộtShape (vì nó công khai nguồn gốc từ Shape) Do đó nó có thể được tổ chức bởi một vector mà những giao dịch trong (con trỏ tới) Shape. Tuy nhiên, vì phương thức draw là ảo, một Rectangle sẽ vẽ chính xác ngay cả khi người gọi chỉ đang nghĩ đến nó là Shape.

đầu ra mong đợi:

Triangle 
Rectangle 
0

Từ ảo thuật là tách riêng. Giả sử bạn đang tạo một ứng dụng để quản lý việc lưu trữ siêu thị. Bạn khá nhiều biết từ đầu rằng bạn sẽ cần sớm hay muộn một cách để lưu trữ và lấy dữ liệu. Tuy nhiên, bạn không biết đó là công nghệ tốt nhất để sử dụng nào (nó có thể là một cơ sở dữ liệu quan hệ hoặc một ví dụ NoSql).

Vì vậy, bạn tạo một giao diện (lớp trừu tượng):

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

Bây giờ, bạn có thể tiếp tục với sự phát triển của các bộ phận khác của ứng dụng chỉ thực hiện một trong phiên bản bộ nhớ của Serialize.

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

Mã phụ thuộc vào Serialize không quan tâm lớp bê tông nào đang sử dụng, nơi duy nhất thực sự cần phải thay đổi để sửa đổi việc triển khai tuần tự là xây dựng lớp bê tông. Nhưng việc xây dựng lớp học có thể chỉ ở một nơi và khá gần với chức năng chính (xem strategy pattern để biết thêm thông tin).

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

Ngoài ra, triển khai giao diện là một trong những phương pháp đơn giản nhất để hoàn thành Open/Close principle. Nếu bạn muốn hỗ trợ tuần tự hóa cho tệp thay vì trong bộ nhớ, bạn sẽ tạo một lớp mới sẽ nằm trong một tệp riêng biệt. Vì vậy, bạn mở để tăng chức năng của ứng dụng mà không cần chạm vào mã hiện có.

Như một nhận xét cuối cùng, thử nghiệm sẽ dễ dàng hơn nhiều nếu bạn phụ thuộc vào các giao diện thay vì đối tượng cụ thể. Bạn có thể dễ dàng thực hiện hành vi mà bạn cần cho bài kiểm tra của mình.

Các vấn đề liên quan