2010-11-19 132 views
5

我有一個C++模塊,需要從其他類獲取信息,而無需知道這些類。顯而易見的方法是使用接口。有很多虛擬方法的接口?或者只有一個虛擬方法有很多接口?

讓我給你舉個例子。假設我有一個管理書籍的圖書館,而且所有書籍都有自己的特點和功能,並且允許圖書館從圖書中獲取特性或執行功能,則該書需要實現一個界面。像這樣:

class Library 
    { 
    public: 
     void addBook(IBook &book); 
    }; 

class IBook 
    { 
    public: 
     string getAuthor() = 0; 
     string getTitle()  = 0; 
     string getISBNCode() = 0; 
     size_t getNofPages() = 0; 
     size_t getNofImages() = 0; 
     double getPrice()  = 0; 
     void printBook() = 0; 
     void convertToPdf() = 0; 
    }; 

不幸的是,對所有種類的書實施所有這些方法沒有任何意義。

  • 有些書沒有圖像(所以我不希望實現getNofImages())
  • 有些書不具有ISBN碼
  • 有些書是買不來,所以他們沒有價格
  • 有些書不能打印
  • 有些書不能被轉換爲PDF

因爲我只有1個接口,我被迫實行家居所有書籍並返回0,返回「」或者在執行時不做任何事情,如果不相關的話。

另一種可能是在許多接口這些接口分裂,就像這樣:

class IBook 
    { 
    public: 
     string getAuthor() = 0; 
     string getTitle()  = 0; 
     size_t getNofPages() = 0; 
    }; 

class IISBNGetter 
    { 
    public: 
     string getISBNCode() = 0; 
    }; 

class IImagesGetter 
    { 
    public: 
     size_t getNofImages() = 0; 
    }; 

class IBuyable 
    { 
    public: 
     double getPrice()  = 0; 
    }; 

class IPrintable 
    { 
    public: 
     void printBook() = 0; 
    }; 

class IConvertible 
    { 
    public: 
     void convertToPdf() = 0; 
    }; 

書類則僅需要實現他們真的想支持的接口。

添加圖書的圖書館就變成這樣的:

bookid = myLibrary->addBook (myBook); 
myLibrary->setISBNGetter (bookid, myBook); 
myLibrary->setImageGetter (bookid, myBook); 
myLibrary->setBuyable  (bookid, myBook); 

具有不同接口的好處是,它明確了誰支持什麼樣的圖書館,它從來沒有叫什麼風險這根本不被支持。

但是,由於每本書都可以具有任何特性/功能的可能組合,因此我只用1種方法就可以獲得大量的接口。

沒有更好的方法來組織接口來獲得這樣的東西嗎?

我也在考慮使用Lambda表達式,但在屏幕後面,這與僅有1個方法的許多接口幾乎相同。

任何想法?

回答

8

我不得不的iBook實現每一個方法:

class IBook 
    { 
    public: 
     virtual ~IBook() {} 

     virtual string getAuthor() { return ""; } // or some other meaningful default value 
     virtual string getTitle() { return ""; } 
     virtual string getISBNCode() { return ""; } 
     virtual size_t getNofPages() { return 0; } 
     virtual size_t getNofImages() { return 0; } 
     virtual double getPrice() { return .0; } 
     virtual void printBook() {} 
     virtual void convertToPdf() {} 
    }; 

,因爲你的選擇是對我來說太亂,你有很多的困惑接口結束。其他方面,你可以檢查Visitor pattern是否可以在這裏應用。

2

一個解決方案可能是讓您的基礎接口保持純虛擬方法,但您的實際實現繼承自提供虛擬方法的默認實現的中間類。

該中間類的一個常見實現是在每個方法中拋出某種「MethodNotImplemented」異常,以便類的用戶可以根據具體情況來捕獲這些異常。

如Simone所發佈的那樣,在調用不存在的方法不會是「特殊」的情況下,對於這種情況來說,看作是異常代價有點昂貴,還有使這些方法爲空或返回默認值的方法。

2

我想你不需要去任何極端,但要選擇中間道路。 有一個接口不好,它破壞了另一方面Interface Segregation Principle (ISP)有這麼多的接口也會破壞你的代碼。我會留下一個核心IBook,並考慮其餘的問題。例如IPrintable和IConvertible(用於pdf轉換)可以在一個界面中進行。可能IBuyable和IISBNGetter將會改變。

1

另一種解決方案是保留接口,但要使用boost :: optional作爲可爲空的返回值。

class IBook 
    { 
    public: 
     virtual ~Ibook(){} 

     virtual string getAuthor() = 0; 
     virtual string getTitle()  = 0; 
     virtual string getISBNCode() = 0; 
     virtual size_t getNofPages() = 0; 
     virtual size_t getNofImages() = 0; 
     virtual boost::optional<double> getPrice()  = 0; // some have no price 
     virtual void printBook() = 0; 
     virtual void convertToPdf() = 0; 
    }; 
2

我認爲你應該畫實際上的ISBN,和執行查詢ISBN(在這種情況下,IBOOK)的接口之間的區別。

作爲「書」的定義的一部分,沒有理由不能說一本書「有可能找到它是否有ISBN,如果是的話」。

如果您不喜歡返回空值來表示「無」,這就夠公平了。在一些域中,空字符串是一個有效的值,所以這是不可能的。你可以有:

bool hasISBNcode(); 
string getISBNcode(); 

或:

std::pair<bool, string> getISBNcode(); 

或相似。

否則,你會發現你在IBook的任何函數調用IBook的任何函數前都會遇到dynamic_cast,而且你還會發現2^n個不同類型的書的不同類型。或者,在你的示例代碼中,你讓圖書館參與了一本書是否有ISBN的業務(這對我來說似乎是錯誤的 - 與圖書館無關,它僅僅是本書的一個屬性)。這些工作都不是特別有趣,而且在這裏似乎不是必要的。

如果這些東西看起來有必要,你可以使用策略。將ConcreteBook定義爲看起來對於使用某個幫助對象的ISBN而言,沒有書類知道如何執行搜索。然後插入不同的對象來實現這一目標,具體取決於某本書是否實際上有一個。不過,看起來有點矯枉過正,因爲某個地方可能只是某個數據庫中的可空列。

+0

完全一致,書自然想到的可能有書號和價格。鑑於一本特定的書,我想詢問它是否可購買,價格是多少,如果它有一個ISBN和那個ISBN值是什麼......這些操作語義上屬於'IBook'接口。從另一個角度看,擁有一個'ISBNGetter'接口似乎意味着不同的對象可能有ISBN,但只有書有它。沒有任何情況下你會在不是'IBook'的對象上使用額外的接口。 – 2010-11-19 12:39:53

+0

@dribeas:是的,雖然'IISBNGetter'從'IBook'派生出來可能比它的價值更麻煩,即使你使用額外的接口。不過,我認爲ISBN-13與UPC兼容。如果是這樣,那麼'IUPCGetter'會更普遍,'IBuyable'肯定是。 「IBuyable」可能是一個有價值的界面,無論它是否定義了所有的書籍都實現了它,或者只有一些。 – 2010-11-19 12:46:01

1

你可以爲每本書提供一個指向基接口單例對象的指針容器,如std::map<std::string, IBase>。然後你可以通過名字獲得接口,獲得指向它的指針(或者爲null),並且只需調用它的某個doDefault()(或者將指針上傳到IDerived,如果必須的話)。每個接口函數必須具有指向Book的指針作爲其第一個(甚至僅有)參數:doDefault(const Book*)

1

有兩個稍稍(非常輕微!)相關的問題:

  • 邏輯部分層次的抽象正確。
  • 空值的可能性。

其他人已經爲每個問題提供瞭解決方案。也就是說,關於第一個,不要去尋找所有的接口,不要去尋找單一接口的方法,而是嘗試對那裏的層次結構進行建模。關於後者,boost::optional,可能增加了對於數據項存在的單獨查詢方法。

我只是想強調一下,當我寫這篇文章的時候,可能並不明顯,因爲他們確實是兩個不同的問題。

關於風格(清晰度的另一個方面),這是什麼getSin Javaism的東西?

x = 2*getSin(v)/computeCos(v)

無厘頭在C++中,只寫sin。 :-)

乾杯&心連心,