何时以及为什么要使用战略模式?

时间:2009-11-10 20:04:45

标签: design-patterns strategy-pattern

何时使用Strategy Pattern

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


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);
    }

}

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

10 个答案:

答案 0 :(得分:55)

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

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

interface GraphStrategy {
    Image renderGraph(Data graphData);
}

class BigGraphStratedy implements GraphStrategy {
    ...
}

class SmallGraphStrategy implements GraphStrategy {
    ...
}

然后在其他一些代码中:

GraphStrategy graphStrategy;

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

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

答案 1 :(得分:8)

想到的地方:

  • 资源分配器。在手动资源管理中,这可能会最大限度地缩短资源分配所需的时间,或最大限度地减少碎片。这里的每个策略都有一个“Allocate”方法,该方法具有相同的界面,用户根据他们试图优化的内容决定使用哪种策略。
  • 连接和发送网络数据的方法。也许在某些情况下,您更愿意连接并发送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。

答案 2 :(得分:3)

您引用的代码段有点具有欺骗性,因为它(稍微)脱离了上下文。您在下面的示例中所写的内容也是策略模式 - 您只是简单地重写了上面的示例。

示例中的要点是数学运算的细节是从调用者中抽象出来的。通过这种方式,调用者可以通过创建新的ConcreteStrategy来处理任何二元运算符,例如

  int mod = new ConcreteStrategy(){ 
         public int execute(int a, int b){ return a %b; }
  }.execute(3,4);

答案 3 :(得分:2)

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

我认为你的问题是一个常见的问题,但是,我所看到的几乎每一个设计模式都是令人难以置信的设计,并且总是让我像你一样提出问题 - 当这样呈现时它们似乎总是无用的。

答案 4 :(得分:2)

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

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

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

答案 5 :(得分: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);
...

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

答案 6 :(得分:0)

根据具体情况,当我有许多不同的事情要做时,我通常会使用策略模式。从本质上讲,它是一种将一长串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();

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

即:

moo

会执行MooingStrategy()

答案 7 :(得分:0)

Context知道如何做一些复杂的事情,给你一些操作。操作很简单(添加两个数字,乘以两个数字等)。在一个非平凡的例子中,Context可能需要任意数量的不同行为(不仅仅是一个),这些行为最好被理解为“策略”(因为尝试子类Context会创建一个子类的组合爆炸)。

答案 8 :(得分:0)

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

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

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

答案 9 :(得分:0)

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

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

public int executeStrategy(int baseValue, int exponentFactor)
{
    return (int) Math.Pow(baseValue, this.Strategy.Calculate(2, exponentFactor));
}