C ++类设计,为每个不同的行为提供多个接口

时间:2011-08-06 08:40:01

标签: c++ oop design-patterns

这是我的第一篇文章,所以请善待。这是我最近得到的一个面试问题,但我在搜索后找不到答案(谷歌,C ++ FAQ等)。

接口I1具有行为b1()。有A,B,C三个等级。所有这些类都通过覆盖b1()来实现接口I1。存在第四类D,其具有在界面I1中定义的行为(b1)和额外行为b2

问题是你如何设计D类。

我的回答是创建另一个接口I2,它定义了行为b2(),并通过重写b1()和b2()

使类D实现I1和I2(C ++中的多重继承)

采访者同意这个解决方案,但询问如果将来有新的行为出现的新课程,我们将如何处理

我只能想到添加更多接口(I3,I4等)并进行多重继承,但我知道你最终会在大量具有相应接口的派生类中

面试官似乎期待更好的解决方案,但他没有透露答案。我很想知道这里的专家如何解决这个设计问题。

PS:经过认真思考后,我认为答案可能在于使用设计模式,但看看常见的设计模式,我找不到任何与此问题相符的内容

编辑1:我有更多的问题和说明,所以在这里编辑帖子,不确定这是否正确,或者我需要发布这个作为我自己问题的答案。

首先让我感谢@Nawaz,@ Alexand和@Sjoerd的宝贵意见。我刚刚开始学习C ++ /设计模式中的设计方面,所以请原谅我对这个主题的无知。

@Nawaz的Vistor模式的例子非常有帮助,但我想这只是采访者提出的原始问题的一个特例。 @Alexandre正确地指出了这里的场景。

让我解释一下这个问题的另一个方面。 当我们谈论行为时,我们需要根据

对它们进行分组

1)与一组对象或对象相关的常见行为。 (这很直观,或者可以在现实世界中观察到) 例如:Dude的行为(以@Nawaz为例) - 我,走路,吃饭,学习等等。

2)与小组相关的不常见或非常特殊的行为(这是违反直觉的) 例如:只是为了争论,考虑一个组成音乐的家伙(我知道这个例子并不完美)

3)与该组完全无关的行为。我想不出一个例子,但我的观点是,我们可以说出一些奇怪的原因,我们需要给对象这种行为。

所以我认为访客模式可以解决1)中的问题,但我怀疑它不适用于2)和3)。

以IDude为例,我们需要进行以下更改才能成为一个可以创作音乐的家伙。

    class IComposerBehavior;

    class IComposer
    {
       public:
          virtual ~IComposer() {}
          virtual void accept(IComposerBehavior *behaviour) = 0 ;
    };

    class IComposerBehavior
    {
       public:
          virtual ~IComposerBehavior() {}
          virtual void visit(IComposer * ) = 0;
    };

    class Dude : public IDude, public IComposer
    {
        public:
          virtual void accept(IDudeBehavior *behaviour)
          {
              behaviour->visit(this);
          }
          virtual void accept(IComposerBehavior *behaviour)
          {
              behaviour->visit(this);
          }
    };

    class SymphonyComposerBehavior : public IComposerBehavior
    {
      public:
         virtual void visit(IComposer *dude) { cout << "Dude is composing a symphony" << endl;   }
    };

同样,我们还需要更改客户端代码以解释SymphonyComposerBehavior。

所以基本上我们最终改变了Dude类代码和客户端代码,否定了模式的影响。

我认为面试官询问的是新行为,这些行为不能归入以前确定的一组相关行为中。所以在这种情况下,即使类被修复,访问者模式也能解决为@Alexandre指出的吗?

让我举一个例子来说明我的头脑(不确定这是否是代表问题的正确例子)。让我们说我需要为机器人制造公司设计一个应用程序。

的要求逐渐增加
- Initially We are only producing Toy Robots
- Then Human helper Robots
- Then Self Healing Robots (would just correct itself when defective)
- Then Humanoid Robots
- Then machine Robots (that are not human like but as a substitute for any machine you can think of) . I have deliberately put this here even though its place should be before with a correct evolution scheme.
- finally Humanoid Robots with life (atleast we can dream :-) )

因此,如果我们在设计应用程序之前知道完整的机器人列表,我们可以提供更好的设计, 但是我们如何设计何时以上述顺序依次引入每种新类型。我的观点是,我们知道机器人具有某些行为或特征,但是当以后必须引入不常见的特征(如自我修复,机器人机器人)时,我们该怎么办?

感谢。

2 个答案:

答案 0 :(得分:6)

我认为面试官期待你谈论访客模式。

是的,访问者模式允许您将新的行为添加到现有的类结构中,而无需为结构添加/派生类/接口。所有它只需要您实现行为,访问者模式允许您将此行为添加到类的结构中。

阅读此维基条目;它解释了模式:

以下是访问者模式的一个简单实现:

class IDudeBehavior;

class IDude
{
   public:
      virtual ~IDude() {}
      virtual void accept(IDudeBehavior *behaviour) = 0 ;
};

class IDudeBehavior
{
   public:
      virtual ~IDudeBehavior() {}
      virtual void visit(IDude * ) = 0;
};

class Dude : public IDude
{
    public:
      virtual void accept(IDudeBehavior *behaviour)
      {
          behaviour->visit(this);
      }
};

class LaughDudeBehavior : public IDudeBehavior
{
  public:
     virtual void visit(IDude *dude) { cout << "Dude is Laughing" << endl; }
};

class WalkDudeBehavior : public IDudeBehavior
{
  public:
     virtual void visit(IDude *dude) { cout << "Dude is Walking" << endl; }
};
int main() {
        IDude *dude = new Dude();
        dude->accept(new LaughDudeBehavior());
        dude->accept(new WalkDudeBehavior());
        return 0;
}

在线演示:http://ideone.com/Kqqdt

截至目前,班级Dude只有两种行为,即LaughDudeBehaviorWalkDudeBehavior,但由于它是访问者模式,因此您可以向{{1}添加任意数量的行为},而不编辑类Dude。例如,如果您要添加DudeEatDudeBehavior,那么您需要实现StudyCplusCplusDudeBehavior所需的全部内容:

IDudeBehavior

然后你需要接受这些行为:

class EatDudeBehavior : public IDudeBehavior
{
  public:
     virtual void visit(IDude *dude) { cout << "Dude is Eating" << endl; }
};

class StudyCplusCplusDudeBehavior : public IDudeBehavior
{
  public:
     virtual void visit(IDude *dude) { cout << "Dude is Studying C++" << endl; }
};

添加这些行为后的演示:http://ideone.com/9jdEv


避免内存泄漏

上述代码存在一个问题。一切看起来都很好,除了泄漏记忆。该程序使用dude->accept(new EatDudeBehavior ()); dude->accept(new StudyCplusCplusDudeBehavior ()); 创建了许多类的实例,但它从未使用new解除分配它们。所以你也需要考虑这个问题。

内存泄漏可以很容易地修复为:

delete

No memory leak now。 :-)

答案 1 :(得分:2)

  

(...)将来会出现一系列新行为的新课程

这里有两件不同的事情。新的和新的行为。让我们依次考虑每一个。


如果不添加新类,这意味着您有一组固定的类和一大堆行为:这使得访问者模式成为可能的候选者。访问者模式的目标是将行为转换为,并允许它在其所作用的层次结构的类上模式匹配(不进行转换)。 / p>

但是,访问者实现起来很麻烦,如果层次结构具有非常简单的结构(即,您只想区分两个主要分支),那么您最好将行为实现为自由函数并使用dynamic_cast选择对象所采用的层次结构属于哪个分支。有关dynamic_cast的合理用例,请参阅there

使用Visitor(或适用时的简单dynamic_cast分派)的真正优势在于,与行为相关的完整代码仅在一个地方维护。接口不是这种情况,接口的每个实现都可能分散在各种实现文件中。


现在,如果必须添加一堆新类,并修复了一系列行为,那么使用和滥用接口是正确的方法。接口对抽象行为非常有用。但是,一旦行为数量增加,维护就很麻烦,因为代码在各种类实现文件中变得混乱。

另请参阅模板方法模式,该模式可在此处应用。

另请参阅this question有关界面令人难以置信的实用性。


如果行数行为数量增加怎么办?我担心没有好问题的独立解决方案。你唯一的赌注是巧妙地抽象,以便不同的行为不必关心他们所采取的行动。

考虑以下示例。你有n个容器类(vector,list,deque,set等)和m算法(find_if,count,copy,for_each等)。

您无法在每个容器类中实现每个算法:这意味着您必须编写O(nm)代码。标准库保留的解决方案(它比[需要引用]更早)是抽象结构的遍历:每个容器类都公开一对迭代器,和算法作用于迭代器对。这允许人们编写O(n + m)代码。


总之,在存在越来越多的状态类越来越多的行为的情况下,你必须找到使行为真正独立的抽象的状态类。没有设计模式:你必须使用你的大脑。

在存在固定数量的状态类和越来越多的行为时,无论是抽象的还是最后的,都要使用访问者。

在存在越来越多的状态类和固定数量的行为的情况下,使用接口:这就是它们的用途。