2014-12-01 140 views
3

我目前正在学习设计模式。在研究策略模式时,我发现了一些对我来说很陌生的事情。我寻找关于这种模式的讨论,但没有人回答我的问题......我如何实施战略模式,让它变得干净,保持封装并使添加新战略变得容易。在这里解释一下我的问题是,“规范”的战略格局:Java策略模式 - 我可以在Context类中委托策略实例吗?

public interface Strategy { 
    public void run(); 
} 

public class stratConcrt1 implements Strategy {/*run() implementation*/} 
public class stratConcrt2 implements Strategy {/*run() implementation*/} 

public class Context { 
    private Strategy strategy; 

    public Context(Strategy strat) { 
     this.strategy = strat; 
    } 

    public void runStrategy() { 
     this.strategy.run() 
    } 
} 


public class Client { 
    public void main(Strings[] args) { 
     Context cx; 

     cx = new Context(new stratConcrt1()) 
     cx.runStrategy(); 

     cx = new Context(new stratConcrt2()) 
     cx.runStrategy(); 
    } 
} 

我明白是怎么回事,但我觉得奇怪,让客户知道一些事可以应用不同的策略。对我来说,让上下文实例化不同的策略而不是客户端会更清洁,因为上下文涉及的策略应该(至少在我的脑海里)是唯一能够实例化策略的策略。

我实现了使用JavaFX与上面的代码一定的差异一个小例子:

我有一个类字段实例坐标列表,这个类对列表进行排序的方法。排序坐标列表的方法是有几种策略的方法。

public class Field { 

    // a field contains rectangles described in a list through their coordinates 
    private ObservableList<Coordinate> mpv_coordinateList = FXCollections 
        .observableArrayList(); 

    private Context mpv_sortContext; 

    // Constructor 
    public Field() { 
     //the rectangles are randomly created 
     Random rd = new Random(); 
     for (int i = 0; i < 100; i++) { 
      mpv_coordinateList.add(new Coordinate(rd.nextInt(490), rd.nextInt(490))); 
     } 

     //a context to dynamically modify the sort algorithm 
     mpv_sortContext = new Context(); 
    } 

    //returns the list with all rectangle 
    public ObservableList<Coordinate> getField() { 
     return this.mpv_coordinateList; 
    } 

    //sort elements (depending on different algorithms) 
    public ObservableList<Coordinate> sortElements(String p_sortToApply) { 
     return mpv_sortContext.launchSort(p_sortToApply, 
         this.mpv_coordinateList); 
    } 
} 

由于模型说,我创建了一个接口,让从这个接口继承了具体的策略:

public interface SortStrategy { 
    ObservableList<Coordinate> sort(ObservableList<Coordinate> p_listToSort); 
} 

public class EvenSort implements SortStrategy { 
    @Override 
    public ObservableList<Coordinate> sort(
       ObservableList<Coordinate> p_listToSort) { 

     ObservableList<Coordinate> oddCoordList = FXCollections 
         .observableArrayList(); 

     for (Coordinate coord : p_listToSort) { 
      if (coord.x % 2 == 0) { 
       oddCoordList.add(coord); 
      } 
     } 
     return oddCoordList; 
    } 
} 

public class OddSort implements SortStrategy { 
    @Override 
    public ObservableList<Coordinate> sort(
       ObservableList<Coordinate> p_listToSort) { 

     ObservableList<Coordinate> oddCoordList = FXCollections 
         .observableArrayList(); 

     for (Coordinate coord : p_listToSort) { 
      if (coord.x % 2 == 1) { 
       oddCoordList.add(coord); 
      } 
     } 
     return oddCoordList; 
    } 
} 

具体的类只返回一个包含所有具有偶数或奇数X坐标列表坐标。

,然后我创建了一个类背景:

public class Context { 
    //private SortStrategy mpv_sortStrategy; //never used 

    private EvenSort mpv_evenSort = new EvenSort(); 
    private OddSort mpv_oddSort = new OddSort(); 
    private StandardSort mpv_standardSort = new StandardSort(); 

    private HashMap<String, SortStrategy> mpv_HashMapStrategies; 


    public Context() { 

     //creation of a dictionary with all possible strategies 
     mpv_HashMapStrategies = new HashMap<String, SortStrategy>(); 
     mpv_HashMapStrategies.put("Even Sort", mpv_evenSort); 
     mpv_HashMapStrategies.put("Odd Sort", mpv_oddSort); 
     mpv_HashMapStrategies.put("Standard Sort", mpv_standardSort); 
    } 

    public ObservableList<Coordinate> launchSort(String p_sortToApply, ObservableList<Coordinate> p_listToSort){ 
     return mpv_HashMapStrategies.get(p_sortToApply).sort(p_listToSort); 
    } 
} 

通过GUI用户可以选择他想要使用排序列表中的策略。用户可以点击按钮启动排序。通过主类(未显示)完成呼叫到inst_field.mpv_sortContext.sortElements(a_string),并使用字符串作为描述要使用的策略的参数。然后在sortElements中使用此字符串来选择用户想要应用的策略的实例。

在我的实现中,我一方面是客户端,另一方是处理策略(上下文,接口和具体类)的所有代码。如果我想添加一个新策略,我只需要在Context类中添加一个新策略的实例,并在gui中描述这个新策略,以便让用户知道它。

我知道,在我做的实现也不是很好,因为上下文包含每个可能的策略的实例,因为我不需要对接口的引用,但我发现它比让Field和客户知道这些类。

嗯......我完全错了吗?我在“规范”战略模式中错过了什么? “规范”方式是实施战略模式的唯一途径吗?还是有更好的方式来实现这种模式,只有那些应该知道的类能够意识到策略实例,并且可以轻松地添加新策略?

回答

1

我看了有关这种模式的讨论,但没有回答我的问题......这是怎样实现的策略模式,让它干净

你的“战略”并不一定是不洁,正如你所描述的那样,我认为你可能会因为谁是客户的想法而陷入困境。你的客户正在提供使用的实现,但这可能是一个必要的实现细节。例如,java RMI tutorial's ComputeEngine基本上只使用这种模式。 “计算”实现由客户端传递 - 因为只有客户端知道要执行的计算。

但是,更常见的是,该策略用于提供一种方法,使逻辑可以在某种情况下进行配置,或者允许针对特定用途量身定制公共上下文。它还具有根据需要隐藏客户内部结构的好处。通常要做到这一点,使用的策略将在内部配置到上下文中。这可以通过提供:

  • 确定要被处理
  • 基于系统状态或限制其确定策略的算法基于所述数据的策略算法
  • 一个配置文件使实施被加载(Class.getResourceAsStream)。这是Context班级地图的扩展(即从知名地点加载地图)。这里的一个例子是,你可以提供一个“代表要使用的默认实现的策略,但允许提供新的实现作为替代策略 - 例如,对于数据本身的控制器,例如defaultXMLParser
  • 。例如,对象类型可以规定:一定的策略来计算其价值。

对于上面的第一个两分,你可以考虑使用一个工厂来获得正确的策略,这将保持本地化实施的选择问题。

好......我完全错了吗?我在“规范”战略中错过了什么埃及模式。 “规范”方式是实施战略模式的唯一途径吗?还是有更好的方式来实现这种模式,只有那些应该知道的类能够意识到策略实例,并且可以轻松地添加新策略?

我会说你没有错。这实际上取决于战略使用背后的目的。如果这是一个内部系统问题,那么一些规则应该推动选择(在工厂后面)。如果它是可配置的,那么它应该由配置和隐藏在上下文中的管理(管理使用该策略的整体逻辑的类)驱动。但是,如果它依赖于用户数据或行为,那么数据会在内部驱动选择,或者您必须接受客户将不得不通过您的策略。

还要注意,这种模式背后的目标是在保留替代实现的同时删除条件逻辑。所以,如果你的策略导致你做了很多条件逻辑,那么你可能需要重新考虑它是否澄清了你的代码。

</warandpeace>

+0

我认为你是对的,“我陷入了谁的客户是谁的想法”......感谢真正有趣的链接 – 2014-12-08 07:04:06

2

你把所有的策略堆在一起 - 它不好。首先,战略模式通常提供长期的功能,甚至是所有时间的应用程序都在运行。所以除了选择之外,你不需要任何其他策略。所以,如果你有非常多的非常大的策略,你会堆积很多对象,你不需要。

另外不要忘记,你可以用不同的参数初始化你的策略,在你的情况下你有冻结的对象,你不能修改它们。

但是不要把每个模式看作是公理。你可以修改和使用它你想要什么,你需要什么。模式是主要模式,良好的做法等,但每个人都不能完美的解决所有问题。

0

你的第一个实施与模式的定义完全符合(按照经典的设计模式 - 可复用面向对象软件的元素),恕我直言,你都归因于它的目标,它只是从未有过的。您可能会丢失的关键点是Context包含(或能够从环境中获取)传递到封装的ConcreteStrategy的数据,而无需客户端的协作或了解。换句话说,客户知道它希望应用什么Strategy,但不知道Context中的数据。这是为了简单分离问题/解耦。

适应这些想法你的第一个例子,它可能已经阅读:

public interface Strategy { 
    public void runOn(Context context); 
} 
public class ConcreteStrat1 implements Strategy { 
    public void runOn(Context context) { ... } 
} 
public class ConcreteStrat2 implements Strategy { 
    public void runOn(Context context) { ... } 
} 
public class Context { 
    private Strategy strategy; 
    private InformationPiece1 ip1; 
    private InformationPiece2 ip2; 
    private InformationPiece3 ip3; 
    ... 
    // These are the "ContextInterface()" methods: ways for the Strategy's and other clients to interact with the Context 
    public InformationPiece1 getIP1() { return this.ip1 ; } 
    public void setIP1(InformationPiece1 ip1) { this.ip1= ip1; } 
    public InformationPiece2 getIP2() { return this.ip2 ; } 
    public void setIP2(InformationPiece2 ip2) { this.ip2= ip2; } 
    public InformationPiece3 getIP3() { return this.ip3 ; } 
    public void setIP3(InformationPiece3 ip3) { this.ip3= ip3; } 
    ... 
    public Context(Strategy strategy){ 
     this.strategy= strategy ; 
    } 
    // This operation can be carried out according to a configurable Strategy 
    public void doSomething() { 
     this.strategy.runOn(this); 
    } 
    // This other doesn't. Or maybe it does, but with a second category of configurable Strategy's 
    public void doAnotherThing() { 
     ... 
    } 
} 
public class Client { 
    public void main(Strings[] args) { 
     Context cx; 
     // Decide with what Strategy to "configure" cx. 
     if(args[0].equalsIgnoreCase("A")) 
      cx= new Context(new ConcreteStrat2()); 
     else 
      cx= new Context(new ConcreteStrat1()); 
     // Populate cx. 
     new CIBuilder(cx).buildFrom("Maybe a file name? User interaction anyone?") ; 
     // Pass cx to another client, which would eventually call cx.doSomething(). 
     // This client doesn't need to know what Strategy will be called in turn by cx.doSomething(). 
     // In fact, it doesn't need to know that cx.doSomething() is implemented using the Strategy Pattern at all! 
     new DoesntKnowAboutStrategiesNorNeedsTo().process(cx) ; 
    } 
} 

通过这种方式,我们已经清楚地分隔的角色和responsabilities:

  • main()基于用户输入选择Strategy (或者也许以后基于一个属性文件;它会很容易改变)。它知道不同的可用Strategy的和Context接受建设,但没有其他很多关于。从这个角度来看,main()Strategy模式的客户端,但本身并没有太多Context
  • Context是一个经典的类能够存储信息和执行操作。但是,这些操作之一可以通过几种不同的方式执行(Strategy)。除此之外,Context不知道存在多少种不同的策略或它们如何运作。实际上,如果出现新的Strategy,则根本不需要修改Context(尽管它的接口的原始设计是好的)。
  • DoesntKnowAboutStrategiesNorNeedsToStrategy完全隔离,以及选择一个的过程。它只知道Context的接口,而不是它们的实现。即使doSomething()最初在没有Strategy模式的情况下实施,DoesntKnowAboutStrategiesNorNeedsTo在更改后也不需要修改。这是Context的主要客户,但根本不是Strategy模式。
0

它看起来对我说,你正在寻找其中主要关注的是相对于具有能够与一个配置对象Strategy选择的设计模式。如果这是这种情况,则最适合的模式是Chain of Responsibility。它与你的第二个例子类似。然而,考虑到每个Handler(与Strategy相同的模式)都有机会自行决定它是否可以应用于数据,所以它更加模块化和可扩展。现在

,如果目标是决定最好Strategy(相对于可在给定的数据操作第一),我都用过,一个相对简单的变型(和我在我之前肯定很多人一样)是定义一个成本标准。执行时间,要使用的最大内存或两者的组合都是很好的例子。每个Handler/Strategy需要知道如何快速估算给定数据的成本。定义该基本概念后,有两个主要的实现方案:

  1. 定义类似于Context在第二示例中利用目录的Handler/Strategy秒的中央控制器。可能预定义或可能以某种方式配置。该控制器每Handler评估数据和产生成本估计,并调用最小的做真正的处理。这实际上与Chain of Responsability的精神有很大差异,因此认为它是一个变体,但是由于它与原始代码的相似性,所以想指出它。另一个变体是使控制器估计每个Handler的成本,但是在这种情况下,问题以错误的方式分离,并且组件之间的耦合结果巨大。

  2. Handler S IN没有找到能够处理Request(数据)的第一个合作,但是根据成本估算找到最好

下面是替代2的一般代码,其中介绍的Chain of Responsibility多种利益(以及其相对较少的缺点)。

public abstract class Handler { 
    private Handler successor ; 
    public void setSuccessor(Handler successor) { this.successor= successor ; } 
    public abstract double estimateCostFor(Information info) ; 
    public abstract void doProcess(Information info) ; 
    public boolean process(Information info) { 
     return this.processIfBetterThan(Double.MAX_VALUE,info); 
    } 
    public boolean processIfBetterThan(double callersCost,Information info) { 
     double myCost= this.estimateCostFor(info) ; 
     double minCostSoFar= Math.min(callersCost,myCost) ; 
     boolean informationProcessed= false ; 
     if(this.successor != null) 
      informationProcessed= this.successor.processIfBetterThan(minCostSoFar,info) ; 
     if(! informationProcessed && myCost <= minCostSoFar) { 
     // In cases like this, I prefer <= to == especially when dealing with floating point variables. 
     // Much safer! 
      this.doProcess(info); 
      informationProcessed= true ; 
     } 
     return informationProcessed ; 
    } 
} 
public class ConcreteHandler1 implements Handler { 
    public double estimateCostFor(Information info) { ... } 
    public void doProcess(Information info) { ... } 
} 
public class ConcreteHandler2 implements Handler { 
    public double estimateCostFor(Information info) { ... } 
    public void doProcess(Information info) { ... } 
} 
public class ConcreteHandler3 implements Handler { 
    public double estimateCostFor(Information info) { ... } 
    public void doProcess(Information info) { ... } 
} 
public class Client { 
    public void main(Strings[] args) { 
     // Setup chain of responsibility 
     Handler startOfChain= new ConcreteHandler1() ; 
     Handler h2= new ConcreteHandler2() ; 
     startOfChain.setSuccessor(h2); 
     Handler h3= new ConcreteHandler3() ; 
     h2.setSuccessor(h3); 
     // Obtain Information to process 
     Information myInfo= ... ; 
     // Process it with the best Handler/Strategy 
     startOfChain.process(info); 
    } 
}