有一个接口有很多虚拟方法?或者只有一个虚拟方法有很多接口?

时间:2010-11-19 11:37:22

标签: c++ design-patterns interface

我有一个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;
   };

Book类只需要实现他们真正想要支持的接口。

将书籍添加到库中会变成这样:

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

拥有不同界面的优势在于,对于支持内容的库来说很明显,并且它永远不会有调用不受支持的东西的风险。

然而,由于每本书都可以具有任何可能的特征/功能组合,我最终只有一种方法的大量接口。

是不是有更好的方法来组织接口来获得这样的东西?

我也在考虑使用Lambda表达式,但在屏幕后面这几乎与只有一种方法的许多接口相同。

有什么想法吗?

7 个答案:

答案 0 :(得分: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是否可以在这里应用。

答案 1 :(得分:2)

解决方案可以是使用纯虚方法保持基本接口,但让实际实现继承自提供虚方法默认实现的中间类。

该中间类的一个常见实现是在每个方法中抛出某种“MethodNotImplemented”异常,因此该类的用户可以根据具体情况捕获这些异常。

对于调用不存在的方法不是“例外”的情况来看,异常看起来有点贵,还有像Simone发布的那样让这些方法为空或返回默认值的方法。

答案 2 :(得分:2)

我想你不需要达到任何极端,只能选择中间路线。 有一个接口不好,另一方面它会打破Interface Segregation Principle (ISP),因为有这么多的接口也会破坏你的代码。我会留下一个核心的IBook并考虑其余部分。例如,IPrintable和IConvertible(用于pdf转换)可以在一个接口中。可能IBuyable和IISBNGetter也将继续发展。

答案 3 :(得分:2)

我认为你应该区分实际拥有 ISBN,以及实现查询ISBN的界面(在本例中为IBook)。

作为“书籍”定义的一部分,你没有理由不能说一本书“有可能找到它是否有ISBN,如果有的话”。

如果你不喜欢返回空值来表示“无”,这是公平的。在某些域中,空字符串是有效值,因此甚至不可能。你可以:

bool hasISBNcode();
string getISBNcode();

或:

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

或类似。

否则,在调用dynamic_cast的任何函数之前,您会发现自己处于IBook,并且您还会发现自己有2 ^ n个不同的具体类别用于不同的书籍类型。或者,在您的示例代码中,您参与图书馆的业务是一本书是否有ISBN(这对我来说似乎不对 - 它与图书馆没有任何关系,它只是本书的一个属性)。这些都不是特别有趣,在这里似乎没有必要。

如果这些事情看起来很有必要,你可以使用策略。使用某个帮助程序对象定义ConcreteBook能够查找的ISBN,而不会让书籍类知道如何执行搜索。然后插入不同的对象来进行查看,具体取决于某本书是否真的有一本书。但是,似乎有点矫枉过正,因为在某个地方可能只是一个可以为空的列。

答案 4 :(得分: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;
   };

答案 5 :(得分:1)

你可以为每本书提供一个指向基本接口单例对象的指针的容器,类似于std::map<std::string, IBase>。然后你需要按名称接口,获取指向它的指针(或null),并在其上调用一些doDefault()(或者如果必须的话,将指针向上转换为IDerived)。每个接口函数都必须有指向Book的指针作为其第一个(甚至是唯一的)参数:doDefault(const Book*)

答案 6 :(得分:1)

有两个只是轻微(有点轻微!)相关的问题:

  • 正确抽象逻辑部分层次结构。
  • 空值的可能性。

其他人已经为每个问题提供了解决方案。也就是说,关于第一个,不要去一切接口,不要去单个方法的每个接口,而是尝试模拟那里的层次结构。关于后者boost::optional,可能使用单独的查询方法来增加数据项的存在。

我只想强调,从我写这篇文章的答案中可能并不明显,这些答案确实是两个不同的问题。

关于风格(清晰度的另一个方面),所有这些getSin Javaism的东西是什么?

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

在C ++中没有意义,只需编写sin即可。 : - )

干杯&amp;第h。,