36
struct InkPen
{
  void Write()
  {
    this->WriteImplementation();
  }

  void WriteImplementation()
  {
    std::cout << "Writing using a inkpen" << std::endl;
  }

};

struct BoldPen
{
  void Write()
  {
    std::cout << "Writing using a boldpen" << std::endl;
  }
};

template<class PenType>
class Writer : public PenType
{
public:
  void StartWriting()
  {
    PenType::Write();
  }
};

int main()
{
  Writer<InkPen> writer;
  writer.StartWriting();
  Writer<BoldPen> writer1;
  writer1.StartWriting();
  return 0;
}

我将上述代码作为基于学习策略的设计的一部分编写。我对上面的代码有几个问题

1 - 这个实现看起来正确吗?我的意思是:它真的看起来像基于策略的设计吗?

2 - 我现在可以将任何类型的笔挂到作家身上。但是当我得到一支没有默认构造函数(只有参数化构造函数)的笔时,我该怎么办?我将如何处理这种情况?

template<class PenType>
class Writer : public PenType
{
public:
  void StartWriting()
  {
    PenType::Write();
  }
};

3 - 当使用上述代码时

Writer<InkPen> writer;

我猜编译器会将PenType替换为InkPen。如果是,为什么我不能只从StartWriting()调用Write( )而不是为基类名称添加前缀(PenType::Write())?

4 - 我认为基于策略的设计迫使您从语义上无效的类中派生。在上面的代码中,writer 是从 pen 派生的,只是因为 writer 使用的是 pen。但是说作家是一支笔在语义上是无效的。有没有其他更好的方法来解决这个问题,或者我在这里遗漏了什么?

有什么想法吗?

4

4 回答 4

29

以下是我将如何实现该类:

template<class PenType>
class Writer
{
public:
  Writer(const PenType& pen = PenType()) : pen(pen) {}

  void StartWriting()
  {
    pen.Write();
  }

private:
  PenType pen;
};

这允许用户将特定的 Pen 对象传递给构造函数,如果它没有默认构造函数,或者您不希望使用它,其次,它仍然允许您省略 PenType 对象,如果您'很高兴让它使用默认构造函数创建一个。C++ 标准库在许多类中做同样的事情(例如,考虑容器类的分配器)。

我删除了继承。它似乎并没有真正添加任何东西(并且可能会导致问题。您可能不希望 Writer 类的用户直接调用 PenType::Write 函数。您可以改用私有继承,但通常,组合是一个更简单和更传统的设计。

一般来说,基于策略的设计不需要继承。将其添加为成员也同样有效。如果您确实要继承,请将其设为私有,这样您就不会遇到您在#4 中提到的问题。

于 2009-05-16T16:41:39.770 回答
17

这看起来像是基于策略的智能指针实现的一个很好的例子:链接Andrei Alexandrescu在他的一本书中描述了基于策略的智能指针实现。至于你现在的问题。我在这方面有一些经验,但不足以认为我的话是理所当然的:

广告 1 和 4。我猜基于策略的设计更多是关于模板而不是继承。您编写一个模板类,模板参数是策略类,如下所示:

template<class FooPolicy, class BarPolicy>
class Baz {
    // implementation goes here
};

然后你在你的类中使用策略类的方法:

void Baz::someMethod(int someArg) {
    FooPolicy::methodInit();
    // some stuff
    BarPolicy::methodDone();
}

我在此示例中使用静态方法,因为策略通常不需要任何状态。如果是这样,您可以通过组合而不是继承来合并策略的状态:

template<class FooPolicy, class BarPolicy>
class Baz {
  private:
    FooPolicy::State fooState; // might require 'typename' keyword, I didn't
                               // actually tried this in any compiler
    // rest of the Baz class
};

广告 2. 您可以编写模板专业化 - 对于主类及其策略的特定组合,您可以编写任何方法或构造函数的特殊版本,AFAIK:

template <>
Baz<SomeConcreteFooPolicy, SomeConcreteBazPolicy>::Baz(someArgument)
   : fooState(someArgument)
{
    // stuff here
}

希望对你有点帮助

麦克风

于 2009-05-16T16:22:39.067 回答
6

我知道这个帖子很旧,但在最初的帖子中有一个重大缺陷,这个帖子是谷歌的最佳结果之一......所以:

不要将public继承用于基于策略的设计!这会说“is-a”而不是“has-a”/“uses-a”。因此,您应该使用private继承!

于 2013-06-17T19:13:26.700 回答
5

1 - 这个实现看起来正确吗?我的意思是它真的看起来像一个基于策略的设计吗?

策略类从组合行为以产生丰富多样的组合中获得有用性。当你有一个这样的模板参数时,它就不是一个策略类。

2 - 我现在可以将任何类型的笔挂到作家身上。但是当我得到一支没有默认构造函数(只有参数化构造函数)的笔时,我该怎么办?我将如何处理这种情况?

同样,这是策略类的一个奇怪示例。但是,要直接回答您的问题,您可以提供一个接受 PenType 的构造函数。您可能还应该避免从 PenType 继承并将其存储为成员(无需将您的策略​​类与其策略紧密耦合)。

我猜编译器会将 PenType 替换为 InkPen。如果是,为什么我不能只从 StartWriting() 调用 Write() 而不是在基类名称前添加前缀 (PenType::Write())?

从类模板继承时,必须指定 this->member 或 BaseClass::member。

4 - 我认为基于策略的设计迫使您从语义上无效的类中派生。在上面的代码中,writer 是从 pen 派生的,只是因为 writer 使用的是 pen。但是说作家是一支笔在语义上是无效的。有没有其他更好的方法来解决这个问题,或者我在这里遗漏了什么?

如上所述,将 PenType 存储为成员。总是喜欢组合而不是继承,因为它避免了继承的紧密耦合关系。

于 2010-06-29T02:32:29.890 回答