Java策略模式 - 我可以在Context类中委派策略实例化吗?

时间:2014-12-01 16:29:48

标签: java design-patterns strategy-pattern

我目前正在学习自己的设计模式。当我研究战略模式时,我发现了一些对我来说很奇怪的东西。我寻找有关这种模式的讨论,但没有人回答我的问题......我怎样才能实现策略模式,让它变得干净,保持封装并轻松添加新策略。在这里解释我的问题是“规范”战略模式:

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

我理解发生了什么,但让客户知道可以应用的不同策略,我感到很奇怪。对我来说,让Context实例化不同的策略而不是Client是更清晰的,因为Context处理的策略应该(至少在我看来)是唯一能够实例化策略的策略。

我使用JavaFx实现了一个小例子,但与上面的代码存在一些差异:

我有一个类Field,它实例化一个坐标列表,这个类有一个方法来对列表进行排序。对坐标列表进行排序的方法是有多种策略的方法。

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中使用此字符串来选择用户想要应用的策略实例。

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

我知道在我实现的实现中也不是那么好,因为Context包含每个可能策略的实例,因此我不需要对接口的引用,但我觉得它比让Field更干净并且客户知道这些类。

好吧......我完全错了吗?在“规范”战略模式中是否有一些我错过的东西。 “规范”方式是实施战略模式的唯一方式吗?或者是否有更好的方法来实现这种模式,只有那些应该知道的类才能知道策略实例,并且可以轻松地添加新策略?

4 个答案:

答案 0 :(得分:2)

你在堆中持有所有策略 - 这不好。首先,策略模式通常提供长期功能,甚至是应用程序运行的所有时间。因此,除了选择之外,您不需要任何其他策略。所以,如果你有非常多的非常大的策略,那么你将会堆积很多你不需要的对象。

另外请不要忘记,您可以使用不同的参数启动策略,在您遇到冻结对象的情况下,您无法修改它们。

但是不要把每个模式都视为公理。您可以根据需要修改和使用它,以及您的需求。模式是主要模型,良好实践等,但每个模式都不适合所有解决方案。

答案 1 :(得分:2)

  

我寻找有关这种模式的讨论,但没有人回答我的问题......我怎样才能实施策略模式让它变得干净

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

然而,更常见的是,该策略用于提供一种方法,使逻辑在某些上下文中可配置,或允许根据特定用途定制公共上下文。它还具有根据需要从客户端隐藏内部结构的好处。通常,要使用的策略将在内部配置到上下文。这可以通过以下方式提供:

  • 根据要处理的数据确定策略的算法
  • 基于系统状态或约束确定策略的算法
  • 一个配置文件,可以加载实现(Class.getResourceAsStream)。这是Context类地图的扩展(即从众所周知的位置加载地图)。这里的一个示例是您可以提供“表示要使用的默认实现的策略,但允许将新实现作为替代策略提供 - 例如defaultXMLParser
  • 数据本身的控制器 - 例如对象类型可以规定使用某种策略来计算其值。

对于前面的前两点,您可以考虑使用工厂来推导出正确的策略。这将使实现选择关注本地化。

  好吧......我完全错了吗?在“规范”战略模式中是否有一些我错过的东西。 “规范”方式是实施战略模式的唯一方式吗?或者是否有更好的方法来实现这种模式,只有那些应该知道的类才能知道策略实例,并且可以轻松地添加新策略?

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

另请注意,此模式背后的目标是在保留替代实现的同时删除条件逻辑。因此,如果您的策略导致您执行大量条件逻辑,那么您可能需要重新考虑它是否澄清了您的代码。

</warandpeace>

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

通过这种方式,我们明确区分了角色和责任:

  • main()根据用户输入选择Strategy(或者稍后根据属性文件选择Strategy;它将很容易更改)。它知道不同的可用Context并且main()在构造上接受一个,但对其中任何一个都没有。从这个角度来看,StrategyContext模式的客户端,但Context本身并不多。
  • Strategy是一个能够存储信息和执行操作的经典类。但是,其中一个操作可以用几种不同的方式执行(Context s)。除此之外,Strategy并不知道存在多少种不同的策略或它们如何运作。实际上,如果出现新的Context,则根本不需要修改DoesntKnowAboutStrategiesNorNeedsTo(如果其接口的原始设计很好)。
  • StrategyContext完全隔离,以及选择一个。{li> doSomething()。它只知道Strategy的接口,而不是它们的实现。即使DoesntKnowAboutStrategiesNorNeedsTo最初是在没有Context模式的情况下实施的,Strategy也不需要在更改后进行修改。这是{{1}}的主要客户,但根本不是{{1}}模式。

答案 3 :(得分:0)

在我看来,您正在寻找一种设计模式,其主要关注点是Strategy选择,而不是具有可以配置一个的对象。如果是这种情况,那么最充分的模式是Chain of Responsibility。它类似于你的第二个例子。但是,它更加模块化和可扩展,因为每个Handler(此模式中的等价物到Strategy)都有机会决定(单独)它是否可以应用于数据。

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

  1. 在第二个示例中定义一个类似于Context的中央控制器,其目录为Handler / Strategy s。可能是预定义的,也可能以某种方式配置。该控制器每隔Handler评估数据并产生成本估算,并调用具有最小值的那个来进行实际处理。这实际上偏离了Chain of Responsability的精神,认为它是一种变体,但是由于它与原始代码的相似性而想要指出它。另一种变体是让控制器估算每个Handler的成本,但在这种情况下,问题以错误的方式分离,组件之间的耦合导致巨大的。

  2. Handler进行协作,找不到可以处理Request(数据)的第一个,但根据成本估算找到最佳

  3. 以下是备选方案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);
        }
    }