12

在 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?)

[Edit-update] 我上面提到的函数被替换为另一个类,其中 MoveAlong 将是基于在这个新类中实现的算法根据需要设置的属性。(类似于接受的答案中展示的内容。)


[编辑更新]结论

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

4

8 回答 8

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 类,也不必重写可能难以维护和调试的方法。

于 2008-10-05T10:37:32.143 回答
10

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

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

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

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

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

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

于 2008-10-05T10:08:08.600 回答
5

战略和决策/选择之间是有区别的。大多数时候,我们会在代码中处理决策/选择,并使用 if()/switch() 构造来实现它们。当需要将逻辑/算法与使用分离时,策略模式很有用。

例如,考虑一种轮询机制,不同的用户将检查资源/更新。现在,我们可能希望以更快的周转时间或更多详细信息通知某些特权用户。本质上,所使用的逻辑会根据用户角色而变化。从设计/架构的角度来看,策略是有意义的,在较低的粒度级别上,它应该始终受到质疑。

于 2008-10-05T13:22:01.297 回答
4

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

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

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

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

推荐阅读:

于 2008-10-05T10:28:43.847 回答
2

这种设计模式允许将算法封装在类中。

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

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

于 2008-10-05T10:20:34.720 回答
2

一种看待这种情况的方法是,当您有各种想要执行的操作并且这些操作是在运行时确定的。如果您创建一个哈希表或策略字典,您可以检索与命令值或参数对应的那些策略。选择子集后,您可以简单地迭代策略列表并连续执行。

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

于 2008-10-07T23:35:03.477 回答
1

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

策略模式的一个典型例子是文件在 Unix 中的工作方式。给定一个文件描述符,你可以读取它、写入它、轮询它、寻找它、发送ioctls 给它等等,而不必知道你是否在处理文件、目录、管道、套接字、设备等。(当然,某些操作,如查找,不适用于管道和套接字。但在这些情况下,读取和写入将正常工作。)

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

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

于 2008-10-05T10:07:32.483 回答
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();
于 2017-02-21T05:34:33.527 回答