2012-02-05 61 views
5

所以我很明白它是如何工作的,但我無法掌握它的用途。你仍然需要定義所有單獨的函數,你仍然需要創建每個對象的實例,爲什麼不直接從該對象調用函數來創建對象,創建一個指向父對象的指針並傳遞派生對象引用,只是爲了調用一個函數?我不明白採取這一額外步驟的好處。有人可以解釋多態性的好處嗎?

爲什麼這樣做:

class Parent 
{ 
    virtual void function(){}; 
}; 

class Derived : public Parent 
{ 
    void function() 
    { 
    cout << "derived"; 
    } 
}; 

int main() 
{ 
    Derived foo; 
    Parent* bar = &foo; 
    bar->function(); 
    return -3234324; 
} 

VS此:

class Parent 
{ 
    virtual void function(){}; 
}; 

class Derived : public Parent 
{ 
    void function() 
    { 
    cout << "derived"; 
    } 
}; 

int main() 
{ 
    Derived foo; 
    foo.function(); 
    return -3234324; 
} 

他們這樣做完全一樣的正確的事情?據我所知,只有一個人使用更多的記憶和更多的困惑。

回答

15

你的兩個例子都做同樣的事情,但不同的方式
第一個示例調用function()通過使用Static binding而第二個調用它使用動態綁定

在第一種情況下,編譯器準確知道在編譯時本身調用哪個函數,而在第二種情況下,根據所指向的對象的類型在運行時決定應調用哪個函數基類指針。

有什麼優勢?
優點是更通用和鬆散耦合的代碼。

想象一下,一個類層次結構如下:

enter image description here

它使用這些類的調用代碼,將是這樣的:

Shape *basep[] = { &line_obj, &tri_obj, 
        &rect_obj, &cir_obj}; 
for (i = 0; i < NO_PICTURES; i++) 
    basep[i] -> Draw(); 

其中,line_objtri_obj等都是具體的對象Shape類Line,Triangle等等,它們被存儲在一個更通用的基類類型的指針數組中。

這提供了額外的靈活性和鬆散耦合,如果您需要添加另一個具體形狀類Rhombus,調用代碼不需要改變太多,因爲它指向基類Shape的指針的所有具體形狀。您只需將基類指針指向新的具體類即可。

與此同時,調用代碼可以調用這些類的適當方法,因爲Draw()方法在這些類中是虛擬的,調用的方法將在運行時根據基類指針指向的對象來決定。

以上是施加Open Closed Principle著名SOLID design principles的很好的例子。

+0

我明白了..有無論如何,你可以給我一個如何使它有用的例子嗎? – Jcrack 2012-02-05 07:05:25

+0

爲什麼感謝你,善良的先生。 – Jcrack 2012-02-05 07:07:48

+0

啊,但當然!我想我現在明白了。我會尋找實現多態的方法來進一步理解。感謝您花時間分享您的知識。 – Jcrack 2012-02-05 07:18:59

2

如果你有一個共享一個共同祖先的對象的列表/數組,並且你希望與它們做一些共同的事情,或者你有一個重載的方法,多態性是很好的。我學習了這個概念的例子,使用形狀作爲並重寫繪製方法。他們都做不同的事情,但他們都是'形狀',都可以畫出來。你的例子並沒有真正做任何有用的保證使用多態性

+0

你能否給我一個有用的例子,以便我能理解多態的用法? – Jcrack 2012-02-05 07:06:13

+0

我以爲我做了'形狀'的事情。我目前不在我的電腦上,因此輸入代碼很麻煩。與你有形狀的東西,例如一個有兩個點的正方形,以及一個帶有中心點和半徑的圓。每種形狀都有不同的形狀,它們具有不同的表現形式,也有不同的「拉伸」方法。但他們共享一個共同的祖先 - 具有繪製方法的Shape類。所以,你可以存儲一個形狀列表,並通過在列表中的每個形狀上調用Draw的簡單循環來顯示它們。 – 2012-02-05 07:11:46

+0

謝謝,我想我現在明白了。我會嘗試每個人的建議和想法。 – Jcrack 2012-02-05 07:25:03

2

一個很好的例子有用的多態性是.NET Stream類。它有許多實現,例如「FileStream」,「MemoryStream」,「GZipStream」等等。使用「Stream」而不是「FileStream」的算法可以在任何其他流類型上重用,只需很少修改或不需要修改。

1

多態性是OOP的原則之一。使用多態可以在運行時選擇多種行爲。在你的示例中,你有一個Parent的實現,如果你有更多的實現,你可以在運行時通過參數選擇一個。多態性有助於解耦應用程序層。在你的第三部分示例中使用這個結構體,那麼它只能看到父接口,並不知道在運行時的實現,所以第三方獨立於父接口的實現。你可以看到依賴注入模式也可以更好地設計。

6

假設你想讓某人出現工作。你不知道他們是否需要坐車,坐公共汽車,步行或是什麼。你只是希望他們出現工作。多態性,你只要告訴他們出現工作,他們就是這樣做的。沒有多態性,你必須弄清楚他們需要如何工作並指導他們進入這個過程。

現在說一些人開始採取Segway工作。沒有多態性,每個告訴某人開始工作的代碼都必須學習這種新的工作方式,以及如何確定以這種方式工作的人以及如何告訴他們這樣做。使用多態性,您可以將代碼放在一個地方,即Segway-ride的實現中,並且所有告訴人們去上班的代碼都會告訴Segway車手採用他們的Segway,儘管它不知道這是什麼它在做。

有許多現實世界的編程類比。假設你需要告訴某人他們需要調查的問題。他們喜歡的聯繫方式可能是電子郵件,也可能是即時消息。也許這是一條短信。使用多態通知方法,您可以添加新的通知機制,而無需更改可能需要使用它的每一位代碼。

+0

另一個很好的例子,謝謝。我希望我可以選擇多個最佳答案。 – Jcrack 2012-02-05 07:19:36

+0

這是一個很好的例子。你不能選擇多個答案,但你可以給所有你喜歡的答案。 – 2012-02-05 07:35:49

2

有無數好的多態性用法的例子。考慮一個代表GUI小部件的類。最基本的類會有這樣的:

class BaseWidget 
{ 
... 
virtual void draw() = 0; 
... 
}; 

這是一個純虛函數。這意味着所有繼承Base的類都需要實現它。當然,GUI中的所有小部件都需要自己繪製,對吧?所以這就是爲什麼你需要所有這一切都是常見的功能的基類所有的GUI控件被定義爲純虛函數,因爲在任意一個孩子,你會做這樣的:在你的代碼

class ChildWidget 
{ 
... 
    void draw() 
    { 
    //draw this widget using the knowledge provided by this child class 
    } 
}; 


class ChildWidget2 
{ 
... 
    void draw() 
    { 
    //draw this widget using the knowledge provided by this child class 
    } 
}; 

然後你不必關心檢查你正在繪製什麼樣的小部件。知道如何繪製自己的責任在於小部件(對象),而不是與你在一起。所以,你可以做這樣的事情在你的主循環:

for(int i = 0; i < numberOfWidgets; i++) 
{ 
    widgetsArray[i].draw(); 
} 

和上面將利用所有的部件不管他們是ChildWidget1,ChildWidget2,文本框,按鈕式的。

希望它有助於理解多態性的好處。

2

多態性指不更多。換句話說,多態性是不相關的,除非有多個派生函數。

在這個例子中,我有兩個派生函數。其中之一是根據mode變量進行選擇的。請注意,agnostic_function()不知道選擇了哪一個。不過,它會調用function()的正確版本。

所以多態性的一點是,大部分代碼不需要知道正在使用哪個派生類。實例化哪個類的具體選擇可以本地化爲代碼中的單個點。這使得代碼更清潔,更容易開發和維護。

#include <iostream> 
using namespace std; 

class Parent 
{ 
public: 
    virtual void function() const {}; 
}; 

class Derived1 : public Parent 
{ 
    void function() const { cout << "derived1"; } 
}; 

class Derived2 : public Parent 
{ 
    void function() const { cout << "derived2"; } 
}; 

void agnostic_function(Parent const & bar) 
{ 
    bar.function(); 
} 

int main() 
{ 
    int mode = 1; 
    agnostic_function 
    (
     (mode==1) 
     ? static_cast<Parent const &>(Derived1()) 
     : static_cast<Parent const &>(Derived2()) 
    ); 
} 
+0

我不知道爲什麼我說「derived * function *」而不是「derived * class *」。 – nobar 2015-11-30 15:06:12

2

重用,概括和可擴展性。

我可能有一個像這樣的抽象類層次結構:Vehicle> Car。然後,我可以簡單地從Car派生來實現SaloonCar,CoupeCar等具體類型。我在抽象基類中實現通用代碼。我可能也已經建立了一些與Car耦合的其他代碼。我的SaloonCar和CoupeCar都是Cars,所以我可以在不改變的情況下將它們傳遞給客戶代碼。

現在考慮我可能有一個接口; IInternalCombustionEngine和一個與此相關的類,比如說Garage(據我所知,與我一起留下)。我可以在單獨的類層次結構中定義的類上實現此接口。例如。

public abstract class Vehicle {..} 

public abstract class Bus : Vehicle, IPassengerVehicle, IHydrogenPowerSource, IElectricMotor {..} 

public abstract class Car : Vehicle {..} 

public class FordCortina : Car, IInternalCombustionEngine, IPassengerVehicle {..} 

public class FormulaOneCar : Car, IInternalCombustionEngine {..} 

public abstract class PowerTool {..} 

public class ChainSaw : PowerTool, IInternalCombustionEngine {..} 

public class DomesticDrill : PowerTool, IElectricMotor {..} 

所以,我現在可以說FordCortina的對象實例是車輛,這是一個汽車,它是一個IInternalCombustionEngine(OK再次做作,但你明白了吧),它也是一個轎車。這是一個強大的構造。

1

只需再補充一點。多態性是實現運行時插件所必需的。可以在運行時爲程序添加功能。在C++中,派生類可以實現爲共享對象庫。運行時系統可以編程爲查看庫目錄,並且如果出現新的共享對象,它將其鏈接並開始調用它。這也可以在Python中完成。

-1

假設我的School類有educate()方法。這種方法只接受可以學習的人。他們有不同的學習風格。有人抓住,有人只是把它舉起來,等等。

現在讓我們說,我有學校課堂周圍的男孩,女孩,狗和貓。如果School想要教育他們,我將不得不爲不同的對象編寫不同的方法,在School下。

相反,不同的人對象(男孩,女孩,貓..)實現Ilearnable接口。然後,School類不必擔心它需要教育什麼。

學校就只能寫一個

public void Educate (ILearnable anyone) 

方法。

我寫過貓和狗,因爲他們可能想參觀不同類型的學校。只要它是某種類型的學校(PetSchool:School)並且他們可以學習,他們就可以受到教育。

  1. 所以它保存具有相同的實現,但不同的輸入類型
  2. 執行相匹配的現實生活場景,所以它的設計目的是便於
  3. 我們可以專注於類的一部分,而忽略多種方法一切。
  4. 該課程的延伸(例如,經過多年的教育後,你會知道,嘿,所有學校周圍的人都必須通過GoGreen計劃,每個人都必須以同樣的方式種植一棵樹,如果你有一個基類所有這些人都是抽象的LivingBeings,我們可以添加一個方法來調用PlantTree並在PlantTree中編寫代碼。沒有人需要在他們的類體中編寫代碼,因爲他們繼承了LivingBeings類,只是將它們類型化爲PlantTree將確保它們能夠樹)。
相關問題