使用战略模式的好处在哪里?

时间:2008-10-05 10:00:00

标签: design-patterns strategy-pattern

我查看了this explanation on Wikipedia,特别是C ++示例,并且未能识别仅定义3个类,创建实例和调用它们之间的区别,以及该示例。我看到的只是将其他两个类放入流程中,无法看到哪里会有好处。现在我确定我错过了一些明显的东西(树木) - 有人可以用一个明确的现实世界的例子来解释它吗?


到目前为止,我可以从答案中得到什么,在我看来,这只是一种更为复杂的方式:

have an abstract class: MoveAlong with a virtual method: DoIt()
have class Car inherit from MoveAlong, 
     implementing DoIt() { ..start-car-and-drive..}
have class HorseCart inherit from MoveAlong, 
     implementing DoIt() { ..hit-horse..}
have class Bicycle inherit from MoveAlong, 
     implementing DoIt() { ..pedal..}
now I can call any function taking MoveAlong as parm 
passing any of the three classes and call DoIt
Isn't this what Strategy intents? (just simpler?)

[编辑更新] 我在上面引用的函数被另一个类替换,其中MoveAlong属于属性,根据需要根据在这个新类中实现的算法设置。 (与接受的答案中的内容相似。)


[编辑更新] 结论

策略模式有它的用途,但我是KISS的坚定信徒,并倾向于更直接和更少混淆的技术。主要是因为我想传递易于维护的代码(并且'我很可能是那些必须进行更改的人!)。

8 个答案:

答案 0 :(得分:21)

重点是将算法分成可在运行时插入的类。例如,假设您有一个包含时钟的应用程序。您可以通过许多不同的方式绘制时钟,但在大多数情况下,底层功能是相同的。所以你可以创建一个时钟显示界面:

class IClockDisplay
{
    public:
       virtual void Display( int hour, int minute, int second ) = 0;
};

然后你的Clock类连接到一个定时器并每秒更新一次时钟显示。所以你会有类似的东西:

class Clock
{
   protected:
      IClockDisplay* mDisplay;
      int mHour;
      int mMinute;
      int mSecond;

   public:
      Clock( IClockDisplay* display )
      {
          mDisplay = display;
      }

      void Start(); // initiate the timer

      void OnTimer()
      {
         mDisplay->Display( mHour, mMinute, mSecond );
      }

      void ChangeDisplay( IClockDisplay* display )
      {
          mDisplay = display;
      }
};

然后在运行时使用正确的显示类来实例化您的时钟。即你可以使用ClockDisplayDigital,ClockDisplayAnalog,ClockDisplayMartian来实现IClockDisplay接口。

因此,您可以稍后通过创建新类来添加任何类型的新时钟显示,而不必乱丢您的Clock类,并且无需覆盖维护和调试可能很麻烦的方法。

答案 1 :(得分:10)

在Java中,您使用密码输入流进行解密,如下所示:

String path = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), ???);

但是密码流不知道您打算使用什么加密算法或块大小,填充策略等......新算法将一直添加,因此硬编码不实用。相反,我们传入一个Cipher 策略对象来告诉它如何执行解密......

String path = ... ;
Cipher strategy = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), strategy);

一般情况下,只要有任何对象知道 需要做什么而不是如何,就可以使用策略模式。另一个很好的例子是Swing中的布局管理器,虽然在这种情况下它并没有很好地工作,请参阅Totally GridBag以获得一个有趣的插图。

注意:这里有两种模式,因为流中的流包裹是Decorator的一个例子。

答案 2 :(得分:5)

策略与决策/选择之间存在差异。大多数情况下,我们将在代码中处理决策/选择,并使用if()/ switch()构造实现它们。当需要将逻辑/算法与使用分离时,策略模式很有用。

作为一个例子,想想一个轮询机制,不同的用户会检查资源/更新。现在,我们可能希望通过更快的周转时间或更多详细信息通知一些受益用户。 Essentailly使用的逻辑基于用户角色而改变。策略从设计/架构的角度来看是有意义的,在较低的粒度级别,它应该始终受到质疑。

答案 3 :(得分:4)

策略模式允许您在不扩展主类的情况下利用polimorphism。实质上,您将所有可变部分放在策略接口和实现中,主类委托给它们。如果您的主对象只使用一个策略,那么它几乎与拥有抽象(纯虚拟)方法和每个子类中的不同实现相同。

策略方法提供了一些好处:

  • 您可以在运行时更改策略 - 将其与运行时更改类类型进行比较,这对于非虚拟方法来说更难,编译器特定且不可能
  • 一个主要类可以使用多个策略,允许您以多种方式重新组合它们。考虑一个基于每个节点和当前结果遍历树并评估函数的类。你可以有一个步行策略(深度优先或广度优先)和计算策略(一些仿函数 - 即'计数正数'或'总和')。如果您不使用策略,则需要为每个步行/计算组合实现子类。
  • 代码更易于维护,因为修改或理解策略不需要您理解整个主对象

缺点是在许多情况下,策略模式是一种过度杀伤 - 开关/案例操作符是有原因的。考虑从简单的控制流语句(switch / case或if)开始,然后仅在必要时移动到类层次结构,如果您有多个可变性维度,则从中提取策略。函数指针落在这个连续体的中间位置。

推荐阅读:

答案 4 :(得分:2)

此设计模式允许在类中封装算法

使用策略的类(客户端类)与算法实现分离。您可以更改算法实现,或添加新算法而无需修改客户端。这也可以动态完成:客户端可以选择他将使用的算法。

例如,想象一下需要将图像保存到文件的应用程序;图像可以以不同的格式保存(PNG,JPG ...)。编码算法将全部在共享相同接口的不同类中实现。客户端类将根据用户首选项选择一个。

答案 5 :(得分:2)

查看此内容的一种方法是,您希望执行各种操作,并在运行时确定这些操作。如果创建哈希表或策略字典,则可以检索与命令值或参数对应的策略。选择子集后,您可以简单地迭代策略列表并连续执行。

一个具体的例子是计算订单的总和。您的参数或命令将是基本价格,地方税,城市税,州税,地面运费和优惠券折扣。当您处理订单变化时,灵活性会发挥作用 - 某些州不会征收销售税,而其他订单则需要申请优惠券。您可以动态分配计算顺序。只要您考虑了所有计算,就可以适应所有组合而无需重新编译。

答案 6 :(得分:1)

在Wikipedia示例中,这些实例可以传递给一个函数,该函数不必关心这些实例属于哪个类。该函数只是在传递的对象上调用execute,并且知道正确的事情会发生。

策略模式的典型示例是文件在Unix中的工作方式。给定一个文件描述符,您可以从中读取,写入,轮询,搜索,发送ioctl等,而不必知道您是否正在处理文件,目录,管道,套接字,设备等(当然,某些操作,如搜索,不适用于管道和套接字。但在这些情况下,读写操作会很好。)

这意味着您可以编写通用代码来处理所有这些不同类型的“文件”,而无需编写单独的代码来处理文件与目录等.Unix内核负责将调用委托给正确的代码。

现在,这是内核代码中使用的策略模式,但您没有指定它必须是用户代码,只是一个真实的例子。 : - )

答案 7 :(得分:0)

策略模式适用于简单的想法,即“赞成组合而不是继承”,以便可以在运行时更改策略/算法。为了说明,我们举一个例子,我们需要根据其类型加密不同的消息,例如: MailMessage,ChatMessage等。

class CEncryptor
{
    virtual void encrypt () = 0;
    virtual void decrypt () = 0;
};
class CMessage
{
private:
    shared_ptr<CEncryptor> m_pcEncryptor;
public:
    virtual void send() = 0;

    virtual void receive() = 0;

    void setEncryptor(cost shared_ptr<Encryptor>& arg_pcEncryptor)
    {
        m_pcEncryptor =  arg_pcEncryptor;
    }

    void performEncryption()
    {
        m_pcEncryptor->encrypt();
    }
};

现在,在运行时,您可以使用不同的加密器(如CDESEncryptor:public CEncryptor)实例化从CMessage继承的不同消息(如CMailMessage:public CMessage)

CMessage *ptr = new CMailMessage();
ptr->setEncryptor(new CDESEncrypto());
ptr->performEncryption();