34

什么时候使用策略模式

我看到这样的客户端代码片段:


class StrategyExample {

    public static void main(String[] args) {

        Context context;

        // Three contexts following different strategies
        context = new Context(new ConcreteStrategyAdd());
        int resultA = context.executeStrategy(3,4);

        context = new Context(new ConcreteStrategySubtract());
        int resultB = context.executeStrategy(3,4);

        context = new Context(new ConcreteStrategyMultiply());
        int resultC = context.executeStrategy(3,4);

    }

}

看起来您可以将其重构为:


class StrategyExample {

    public static void main(String[] args) {
         // Three contexts following different strategies
        int resultA =new ConcreteStrategyAdd().execute(3,4);
        int resultB =new ConcreteStrategySubtract().execute(3,4);
        int resultC =new ConcreteStrategyMultiply().execute(3,4);
    }

}

第一部分代码直接取自维基百科页面。一个很大的区别是上下文消失了,但无论如何它在示例中没有做任何事情。也许有人有一个更好的例子来说明策略是有意义的。我通常喜欢设计模式,但这似乎增加了复杂性而没有增加实用性。

4

10 回答 10

72

诸如此类的玩具示例的问题在于,通常很容易错过重点。在这种情况下,代码确实可以如您所展示的那样实现。在策略模式中,主要价值在于能够针对不同的情况切换不同的实现。

您拥有的示例仅说明了模式中的对象以及它们之间的交互。想象一下,您有一个组件可以根据另一端是台式机还是智能手机来为网站呈现图形,您将有一些代码可以检测创建的​​浏览器类型并在另一个组件上设置策略在一些不需要复制的复杂代码中使用策略对象,并且在两种情况下都可以完成工作,而将实际绘制图形的细节留给适当的策略对象:

interface GraphStrategy {
    Image renderGraph(Data graphData);
}

class BigGraphStrategy implements GraphStrategy {
    ...
}

class SmallGraphStrategy implements GraphStrategy {
    ...
}

然后在其他一些代码中:

GraphStrategy graphStrategy;

if (phoneBrowser == true) { 
    graphStrategy = new SmallGraphStrategy();
} else {
    graphStrategy = new BigGraphStrategy();
}

然后,您的应用程序代码的其余部分可以直接使用graphStrategy.renderGraph(),而无需知道是否正在执行完整或小图像渲染。

于 2009-11-10T20:15:30.893 回答
9

想到的领域:

  • 资源分配器。在手动资源管理中,这可能是最小化分配资源所需的时间,或最小化碎片。这里的每个策略都有一个具有相同界面的“分配”方法,用户根据他们尝试优化的内容来决定使用哪种策略。
  • 一种连接和发送网络数据的方法。也许在某些情况下,您更喜欢连接和发送 UDP 数据报,也许在其他情况下,性能不是您使用 TCP/IP 发送的一个因素。
  • 数据格式化/序列化策略。允许代码决定一个对象应该使用 Json 还是使用 Xml 序列化。也许一个用于机器,另一个用于人类可读的情况。这两种策略都有一个接受对象的“序列化”方法。每个序列化不同。

主题是决定是否以一种方式或另一种方式做某事取决于情境因素,您或您的代码将根据情况选择正确的策略。

现在为什么这比类似的东西更有用:

void DoIt()
{
    if (... situation1...)
    {
       DoA()
    }
    else
    {
       DoB();
    }
}

原因是有时你只想做一次决定然后忘记它。策略模式的另一个重要主题是将关于使用哪种策略的决定与需要执行策略的代码分离。

DoItStrategy MakeDoItStrategy()
{
     if (... situation1...)
     {
           return new DoItStrategyA();
     }
     else
     {
           return new DoItStrategyB();
     }
}

在最后一个示例中,您可以只存储策略,将其作为实现策略接口的另一个对象传递。对于那些执行策略的人来说,他们只是有一种执行行动的方法。他们不知道幕后的内部运作是什么,只知道界面会得到满足。该策略的用户不需要知道我们做出决定的原因。他们只需要做一个动作。我们做出一次决定并将策略传递给使用该策略的类。

例如,考虑我们根据给定的网络配置做出程序范围的决策以使用 UDP 连接并向远程主机发送数据的情况。我们可以预先创建 UDP 策略并将其传递给需要发送网络数据的每个人,而不是网络接口的每个用户都需要知道做出决定的逻辑(上面的“DoIt”函数)。然后,该策略实现了一个具有相同最终结果的简单接口——数据从 A 到 B。

于 2009-11-10T20:10:02.550 回答
4

您引用的代码片段有点欺骗性,因为它(稍微)脱离了上下文。你在下面的例子中写的也是策略模式——你只是更简洁地重写了上面的例子。

示例中的要点是数学运算的细节是从调用者那里抽象出来的。这样,调用者可以通过创建一个新的 ConcreteStrategy 来使用任何二元运算符,例如

  int mod = new ConcreteStrategy(){ 
         public int execute(int a, int b){ return a %b; }
  }.execute(3,4);
于 2009-11-10T20:08:20.393 回答
2

是的,这个例子很蹩脚,但是具有不同实现策略的委托的概念是一种基本的、非常古老的设计模式,我在很多应用程序中都使用过。

我认为您的问题在这里很常见,但是,几乎我见过的每个设计模式示例都令人难以置信的做作,并且总是使我像您一样提出问题-以这种方式呈现时它们似乎总是无用的。

于 2009-11-10T20:11:56.453 回答
2

Mac 和 iPhone 上使用的 Cocoa 框架大量使用了策略模式,只不过我们称之为委托模式。以下是我们如何使用它:

我们有一个具体的对象,比如说一个NSTableView。表格视图需要知道它有多少行,每一行,每一列等。因此,我们提供了一个“委托”对象,而不是对表格视图进行子类化以提供该信息。该委托对象实现了某个接口(Objective-C 中的“协议”)。然后,tableview 可以简单地询问它的委托对象在某些情况下它应该做什么(“我有多少行?”“这个单元格中有什么?”“用户是否可以选择这一行?”)。我们可以在运行时简单地通过分配一个符合NSTableViewDelegate协议的新对象来交换委托对象。

所以,是的,策略模式是我最喜欢的模式之一,也是我每天都在使用的模式。

于 2009-11-10T20:16:25.367 回答
1

当我有很多不同的事情要做时,我通常会根据情况使用策略模式。从本质上讲,这是一种将一长串 if/else 语句转换为几行的方法。

一种方法(在 Java 中):

Map<String, Strategy> strategyMap = new HashMap<String, Strategy>();
strategyMap.put("bark", new BarkingStrategy());
strategyMap.put("meow", new MeowingStrategy());
strategyMap.put("moo", new MooingStrategy());
strategyMap.put("giraffeSound", new UnknownStrategy());

你首先建立某种形式的策略库。

之后...

String command = //...some form of input
strategyMap.get(command).execute();

这样,您可以“一般地”处理许多不同的情况。

IE:

moo

将执行MooingStrategy()

于 2009-11-10T20:10:02.690 回答
1

在您(或您的代码的用户)可能想要更改算法中的计算的情况下,策略模式很有用。我使用策略模式的一个简单示例是在 A* 搜索中对启发式建模。A* 使用启发式算法,如果在通向目标节点 (Ng) 的路径上选择某个节点 (Ni),则使用简单计算来估计剩余成本。我的界面看起来像这样:

class Heuristic {
public:
    virtual int estimateRemainingCost(const node &Ni, const node &Ng) const = 0;
};

它们是这样使用的:

...
// for each node (Ni) that is adjacent to the current node Nc
int node_priority = cost(Ni)/* the cost of choosing this node on the path */
                    + heuristic->estimateRemainingCost(Ni, Ng);
unsearched_nodes_.queue(node_priority, Ni);
...

启发式是可以替换的策略或计算。换句话说,改变搜索算法的启发式是一项微不足道的练习。

于 2009-11-10T20:44:27.107 回答
0

Context鉴于您提供给它的一些操作,它知道如何做一些复杂的事情。操作很简单(两个数字相加、两个数字相乘等)。A Context,在一个重要的例子中,可能需要任意数量的不同行为(不仅仅是一个),最好将其分解为“策略”(因为尝试子类Context化会产生子类的组合爆炸)。

于 2009-11-10T20:11:03.483 回答
0

当上下文对象具有更多职责并且策略抽象将这些职责与操作的某些方面解耦时,它更有意义。一个示例(在 C# 中)是 IComparer 接口:

interface IComparer<T>
{
    int Compare(T a, T b);
}

可以将其传递给排序算法。

于 2009-11-10T20:12:14.317 回答
0

主要区别在于,在第二个示例中,策略是算法(因此没有模式)。在第一个示例中,您正在抽象/隔离算法的一部分。

例如,的实现Context.executeStrategy()可能是:

public int executeStrategy(int baseValue, int exponentFactor)
{
    return (int) Math.Pow(baseValue, this.Strategy.Calculate(2, exponentFactor));
}
于 2009-11-10T20:17:58.173 回答