簡體   English   中英

從具體類派生抽象類

[英]Deriving an abstract class from concrete class

假設我們有一個具體的class Apple (可以實例化Apple對象。)現在,有人來從Apple派生抽象class Peach 它是抽象的,因為它引入了新的純虛函數。 現在,Peach的用戶被迫從中派生並定義此新功能。 這是常見的模式嗎? 這是正確的嗎?

樣品:


class Apple
{
public:
    virtual void MakePie();
    // more stuff here
};

class Peach : public Apple { public: virtual void MakeDeliciousDesserts() = 0; // more stuff here };

現在,我們有一個具體的class Berry 有人從漿果派生了抽象class Tomato 它是抽象的,因為它會覆蓋Berry的虛擬功能之一,並使之成為純虛擬的。 Tomato的用戶必須重新實現Berry中先前定義的功能。 這是常見的模式嗎? 這是正確的嗎?

樣品:

 class Berry { public: virtual void EatYummyPie(); // more stuff here }; 

\n\n

class Tomato : public Berry { public: virtual void EatYummyPie() = 0; // more stuff here };

注意:名稱是人為設計的,並且不反映任何實際代碼(希望如此)。 在撰寫這個問題時沒有任何成果受到損害。

來自蘋果的Re Peach:

  • 如果Apple是值類(例如,具有復制ctor,不相同的實例可以相等,等等),則不要執行此操作。 有關原因,請參見Meyers更有效的C ++項目33。
  • 如果Apple有公共的非虛擬析構函數,則不要執行此操作,否則當用戶通過指向Peach的指針刪除Apple時,您會引發未定義的行為。
  • 否則,您可能很安全,因為您沒有違反Liskov替代性 桃IS-A蘋果。
  • 如果您擁有Apple代碼,則更喜歡分解出一個通用的抽象基類(也許是Fruit)並從中派生Apple和Peach。

來自漿果的Re Tomato:

  • 與上述相同,另外:
  • 避免,因為這很不尋常
  • 如果必須的話,記錄下Tomato的派生類必須做什么才能不違反Liskov的可替代性。 在Berry中您要重寫的功能-我們稱它為Juice() -提出了一定的要求並做出了一定的承諾。 派生類的Juice()必須沒有更多要求,也不會更少承諾。 然后,僅知道Berry的DerivedTomato IS-A Berry和代碼是安全的。

通過記錄DerivedTomatoes必須調用Berry::Juice() ,可能會滿足最后一個要求。 如果是這樣,請考慮改用Template Method:

class Tomato : public Berry
{
public:
    void Juice() 
    {
        PrepareJuice();
        Berry::Juice();
    }
    virtual void PrepareJuice() = 0;
};

現在,極有可能番茄IS-A漿果違反了植物學的預期。 (例外是,如果派生類的PrepareJuice的實現強加了Berry::Juice施加的前提以外的其他前提)。

在我看來,這似乎表明設計不好。 如果您想從一個封閉的庫中獲取一個具體的定義並對其進行擴展,並從中分支出很多東西,可能會被強制執行,但是那時候我將認真考慮有關繼承封裝的准則。 ,您可能應該。

是的,我想得越多,這是一個非常糟糕的主意。

不一定是錯誤的 ,但肯定是臭的。 特別是如果您將水果放在陽光下太長時間。 (而且我認為我的牙醫不希望我吃水泥蘋果。)

雖然,我在這里看到的主要問題不是從具體類派生的抽象類,而是真正的繼承層次。

編輯:重新閱讀我看到這是兩個層次結構。 所有的水果東西使我感到困惑。

如果您使用具有繼承模型“ is-a”的推薦做法,則這種模式幾乎永遠不會出現。

一旦有了具體的類,您就可以說它實際上是您可以創建其實例的東西。 如果然后從中派生一個抽象類,則作為基類的屬性的某些東西對派生類而言並不正確,派生類中的klaxons應該是不正確的。

在您的示例中,桃子不是蘋果,因此不應衍生自它。 源自漿果的番茄也是如此。

我通常會在這里建議圍堵,但這似乎並不是一個好的模型,因為Apple不包含Peach。

在這種情況下,我將排除常見的接口-PieFilling或DessertItem。

有點不尋常,但是如果您還有基類的其他子類,並且抽象類的子類具有足夠的通用知識來證明抽象類的存在,例如:

class Concrete
{
public:
    virtual void eat() {}
};
class Sub::public Concrete { // some concrete subclass
    virtual void eat() {}
};
class Abstract:public Concrete // abstract subclass
{
public:
    virtual void eat()=0;
    // and some stuff common to Sub1 and Sub2
};
class Sub1:public Abstract {
    void eat() {}
};
class Sub2:public Abstract {
    void eat() {}
};
int main() {
    Concrete *sub1=new Sub1(),*sub2=new Sub2();
    sub1->eat();
    sub2->eat();
    return 0;
}

嗯...通過思考“什么.....”幾秒鍾,我得出一個結論,這並不常見...而且,我不會從蘋果中得到桃子,從漿果中得到番茄...你有更好的例子嗎?

您可以在C ++中做很多奇怪的事...我什至都不會想到其中的1%...

關於使用純虛擬機覆蓋虛擬機,您可能只是將其隱藏,這真是很奇怪。

如果您可以找到一個愚蠢的C ++編譯器來將該函數鏈接為虛函數,那么您將獲得運行時純虛函數調用...

我認為這只能針對駭客,我不知道到底是哪種駭客...

要回答您的第一個問題,您可以這樣做,因為Apple用戶(如果給出了派生自Peach的具體實例將不會有任何不同)。 並且該實例將不會知道它不是Apple(除非您沒有告訴我們有關Apple的一些虛擬功能的說明,這些虛擬功能將被覆蓋)。

我還無法想象用純虛函數覆蓋虛函數會多么有用-甚至合法嗎?

通常,您要遵循Scott Meyers的著作中的“使所有非葉類抽象化”項目。

無論如何,除了您描述的內容似乎是合法的-只是我看不到您經常需要它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM