我目前正在学习自己的设计模式。当我研究战略模式时,我发现了一些对我来说很奇怪的东西。我寻找有关这种模式的讨论,但没有人回答我的问题......我怎样才能实现策略模式,让它变得干净,保持封装并轻松添加新策略。在这里解释我的问题是“规范”战略模式:
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更干净并且客户知道这些类。
好吧......我完全错了吗?在“规范”战略模式中是否有一些我错过的东西。 “规范”方式是实施战略模式的唯一方式吗?或者是否有更好的方法来实现这种模式,只有那些应该知道的类才能知道策略实例,并且可以轻松地添加新策略?答案 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()
在构造上接受一个,但对其中任何一个都没有。从这个角度来看,Strategy
是Context
模式的客户端,但Context
本身并不多。Strategy
是一个能够存储信息和执行操作的经典类。但是,其中一个操作可以用几种不同的方式执行(Context
s)。除此之外,Strategy
并不知道存在多少种不同的策略或它们如何运作。实际上,如果出现新的Context
,则根本不需要修改DoesntKnowAboutStrategiesNorNeedsTo
(如果其接口的原始设计很好)。Strategy
与Context
完全隔离,以及选择一个。{li> doSomething()
。它只知道Strategy
的接口,而不是它们的实现。即使DoesntKnowAboutStrategiesNorNeedsTo
最初是在没有Context
模式的情况下实施的,Strategy
也不需要在更改后进行修改。这是{{1}}的主要客户,但根本不是{{1}}模式。答案 3 :(得分:0)
在我看来,您正在寻找一种设计模式,其主要关注点是Strategy
的选择,而不是具有可以配置一个的对象。如果是这种情况,那么最充分的模式是Chain of Responsibility
。它类似于你的第二个例子。但是,它更加模块化和可扩展,因为每个Handler
(此模式中的等价物到Strategy
)都有机会决定(单独)它是否可以应用于数据。
现在,如果目标是决定最佳 Strategy
(而不是可以对给定数据进行操作的第一个),那么我使用的是一个相对简单的变体(和肯定很多人在我之前做的)是定义一个成本标准。执行时间,要使用的最大内存或两者的组合都是很好的例子。每个Handler
/ Strategy
都需要知道如何快速估算给定数据的成本。在定义了这个基本概念之后,有两个主要的实现选择:
在第二个示例中定义一个类似于Context
的中央控制器,其目录为Handler
/ Strategy
s。可能是预定义的,也可能以某种方式配置。该控制器每隔Handler
评估数据并产生成本估算,并调用具有最小值的那个来进行实际处理。这实际上偏离了Chain of Responsability
的精神,认为它是一种变体,但是由于它与原始代码的相似性而想要指出它。另一种变体是让控制器估算每个Handler
的成本,但在这种情况下,问题以错误的方式分离,组件之间的耦合导致巨大的。
让Handler
进行协作,找不到可以处理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);
}
}