我有一个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;
};
不幸的是,对所有类型的书实施所有这些方法都没有意义。
因为我只有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表达式,但在屏幕后面这几乎与只有一种方法的许多接口相同。
有什么想法吗?
答案 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。,