类接口:基本还是复杂?

时间:2011-03-16 14:11:41

标签: c++ oop interface

我正在写一个充满乐趣和教育的容器类。以前在编写容器类时,我只限于几个非常基本的方法:GetValueSetValueGetSizeResize。我这样做是为了避免“代码意大利面”,所以我的课程将更容易调试。

然而,在我看来,班级的用户可能想要做的不仅仅是简单的替换。所以我添加了一些方法:

void Replace(const std::size_t Start, const std::size_t End, const T Value);
void Replace(const std::size_t Start, const std::size_t End, const MyClass Other);
void Insert(const std::size_t Index, const T Value);
void Insert(const std::size_t Index, const MyClass Other);
void Delete(const std::size_t Index);
void Delete(const std::size_t Start, const std::size_t End);

一般来说,类应该只提供最基本的接口,让类的用户自己做功能来做复杂的事情吗?或者是否应该以可维护性为代价内置复杂的东西?

5 个答案:

答案 0 :(得分:2)

类应该只提供成员函数的基本/最小接口(最好没有数据!)。然后,您可以将便捷方法添加为非朋友非成员功能。但是,根据接口原理,这些函数仍然是类接口的一部分。

您已经为此命名了主要原因:它使类更容易维护。此外,实施“convienence”方法部分将是一个很好的测试,看看你的界面是否足够好。

请注意,容器的成员函数部分通常应该非常通用且功能强大,而且不仅仅是维护类不变量。

就我所知,这是关于这个问题的最现代意见。它在Scott Meyer的“Effective C ++”(最新的第3版)以及Sutter和Alexandrescu的“C ++编码标准”中得到了突出的提倡。

答案 1 :(得分:2)

问题是,只要你编写另一个容器类(它们中有很多容器类,你可能需要不同类型),你会发现你的设计正方形为O(N * M),其中N是容器类的数量和M算法的数量。

解决方案是将容器与算法分离,这就是在STL中引入迭代器的原因。

有迭代器的替代品,例如使用。多态性。您可以在抽象公共基类中分解遍历接口,并根据它来实现算法。

简而言之,保持容器类中最多的逻辑。

答案 2 :(得分:2)

您应该尝试保持您的界面精简,特别是如果您可能想要实现不同的容器类型,例如基于数组和链表。如果在所有容器中提供一些基本方法,则可以创建执行某些任务但可以在所有容器上运行的外部算法:

 void Replace(const std::size_t Start, const std::size_t End, const T Value);

可能会成为

 template<class ContainerType>
 void ReplaceAllElementsInContainer(ContainerType& Container, const std::size_t Start, const std::size_t End, const T Value);

课外。如果你不这样做,你必须在所有容器中写下所有这些方法。

另一种可能性是使用模板方法模式(与C ++模板无关)并在基类中编写所有这些方法(将基本方法定义为纯虚拟,并从实现的方式调用它们&#34;方便&#34 ; 方法)。这导致可能出现许多虚函数调用,出于性能原因,这些调用可能在容器类中是不可取的。

答案 3 :(得分:1)

我有类似的情况。我的建议是,你有2个“基类”或“超类”。

第一个类,非常通用的类,代表所有容器类的“概念根”,几乎不是类似于接口的方法,应该是:


containers.hpp

class Container
{
protected:
   int GetValue();
   void SetValue(int newValue);

   size_t GetSize();

   void Resize(size_t);
};

第二节课开始变得不那么概念化,更“现实世界”:


mcontainers.hpp

#include "containers.hpp";

class MethodContainer: public Container
{
protected:
  void Replace(const std::size_t Start, const std::size_t End, const T Value);
  void Replace(const std::size_t Start, const std::size_t End, const MyClass Other);
  void Insert(const std::size_t Index, const T Value);
  void Insert(const std::size_t Index, const MyClass Other);
  void Delete(const std::size_t Index);
  void Delete(const std::size_t Start, const std::size_t End);

}

最后,一些具体的课程:


stacks.hpp

#include "containers.hpp";
#include "mcontainers.hpp";

#define pointer void*

class Stack: public MethodContainer
{
public:
  // these methods use "mcontainer::Insert", "mcontainer::Replace", etc
  void Push(pointer Item);
  void Pop();
  pointer Extract();
}

<@> AS @Chris提到,有几个库用于执行此操作,但规则总是有例外,如果需要,您可能需要“重新发明轮子”。

我有一个带有一组库的应用程序,其中包含一些容器/集合。它是在另一个程序中制作的。 langr。并需要它将其迁移到C ++。 Altought,我还检查了c ++标准库,我结束了将库迁移到C ++,因为我有几个库调用我的容器库,需要它快速完成。

使用“基类”时,您可能希望“保护”其成员,并在子类中“将其带到公众”。除非必要,否则我通常不会制作“私人”字段或方法。

总结:一些非常常见的复杂内容(如内存分配或存储)可能会在您的基类中完成,但是,大部分复杂性应该留给子类。

答案 4 :(得分:0)

如果您的代码中仅由您使用此容器,并且您的界面方法足以满足特定目的,则可以通过这种方式进行。

但是,只要其他人打算使用容器,或者您计划在其他区域使用它,我建议添加使用迭代器类型的接口方法,那么您的容器更加开放,可以与其一起使用stdlib容器和算法。以stdlib容器的接口为例。