2010-11-19 60 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。 :-)

干杯&心连心,