什么时候和为什么要使用战略模式?

问题描述:

什么时候会使用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); 
    } 

} 
 

的第一部分代码是直接从*页面取。一个很大的区别是环境消失了,但它在这个例子中并没有做任何事情。也许有人有一个更好的例子,战略是有道理的。我通常喜欢设计模式,但这似乎增加了复杂性而不增加实用性。

+0

http://www.youtube.com/watch?v=MOEsKHqLiBM – BrunoLM 2014-09-06 02:53:05

玩具例子的问题在于,它很容易错过这一点。在这种情况下,代码确实可以像您所示的那样被实施。在战略模式中,主要价值在于能够针对不同情况切换不同的实施方式。

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

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

其余的应用程序代码可以使用而不必知道是否正在执行完整或小型图像渲染。

+0

这就是我一直在寻找的。谢谢! – User1 2010-01-03 20:43:15

+0

我们可以注入依赖与弹簧,如果其他部分? – Harshana 2015-06-26 17:41:26

+0

当我们选择正确的类的策略出现在switch语句中时,它叫做策略模式令我困扰。这更像是“界面策略”,因为它真的只是界面的一个练习。你懂!?很好的例子。 – 2017-01-23 11:34:56

您引用的代码片段有点欺骗性,因为它稍微偏离了上下文。在你的例子中,你在下面写的是战略模式 - 你刚才更简洁地重写了上面的例子。

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

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

领域浮现在脑海:

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

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

现在为什么会变成这样不是像更多有用:

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

的原因是有时你只想做决定一次,忘掉它。策略模式的另一个重要主题是,您需要从需要执行策略的代码中分离出使用哪种策略的决策。

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

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

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

我通常根据情况使用战略模式,当我有一堆不同的事情要做时。实质上,它是将一系列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()

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

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

我认为你的问题在这里很常见,但我几乎看到的每一个设计模式例子都是令人难以置信的设计,总是会让我像你一样提出问题 - 当用这种方式呈现时,他们总是看起来毫无用处。

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

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

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

Mac和iPhone上使用的Cocoa框架使用策略模式a lot,不同之处在于我们将其称为委托模式。以下是我们如何使用它:

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

所以是的,战略模式是我的最爱之一,我使用每一天。

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

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

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

策略模式是在您(或您的代码的用户)可能要改变计算在你的算法非常有用。我已经使用了战略模式的一个简单例子是在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); 
... 

启发式是可以更换的策略或计算。换句话说,改变搜索算法的启发式算法是一项简单的练习。