是否可以在没有明显分支的情况下实施策略模式?


14

策略模式很好地避免了if ... else庞大的构造,并使添加或替换功能更加容易。但是,我认为这仍然存在一个缺陷。似乎在每个实现中仍然需要一个分支构造。它可能是工厂或数据文件。以订购系统为例。

厂:

// All of these classes implement OrderStrategy
switch (orderType) {
case NEW_ORDER: return new NewOrder();
case CANCELLATION: return new Cancellation();
case RETURN: return new Return();
}

此后的代码无需担心,现在只有一个地方可以添加新的订单类型,但是此部分代码仍不可扩展。将其拉出到数据文件中有助于提高可读性(我知道这值得商bat):

<strategies>
   <order type="NEW_ORDER">com.company.NewOrder</order>
   <order type="CANCELLATION">com.company.Cancellation</order>
   <order type="RETURN">com.company.Return</order>
</strategies>

但这仍然增加了样板代码来处理数据文件-授予的,更容易进行单元测试和相对稳定的代码,但是仍然增加了复杂性。

而且,这种结构不能很好地进行集成测试。现在每个单独的策略可能更容易测试,但是您添加的每个新策略都增加了测试的复杂性。它比没有使用模式时要少,但是它仍然存在。

有没有办法实现减轻这种复杂性的战略模式?还是这只是简单而已,而尝试更进一步只会增加另一层抽象,却几乎没有好处?


Hmmmmm ....可能用eval... 简化某些事情,例如...可能无法在Java中工作,但可能会在其他语言中工作?
FrustratedWithFormsDesigner 2012年

1
@FrustratedWithFormsDesigner反射是Java中的神奇词
棘手怪胎

2
您仍然需要在某些地方满足这些条件。通过将它们推送到工厂,您只是在遵守DRY,因为否则if或switch语句可能出现在多个位置。
丹尼尔·B

1
您提到的缺陷通常称为违反开放原则
k3b 2012年

相关问题中接受的答案建议词典/地图替代if-else和switch
gnat 2012年

Answers:


16

当然不是。即使您使用IoC容器,也必须在某处具有条件,从而确定要注入的具体实现。这就是策略模式的本质。

我真的不明白为什么人们会认为这是一个问题。在一些书中有一些声明,例如Fowler的Refactoring,如果您在其他代码中间看到一个开关/盒或if / els链,则应该考虑一种气味并将其移至自己的方法。如果每种情况下的代码多于一行,也许是两行,那么您应该考虑将该方法设置为Factory方法,并返回Strategies。

有人认为这表示开关/外壳不好。不是这种情况。但是,如果可能的话,它应该独立存在。


1
我也不认为这是一件坏事。但是,我一直在寻找减少接触它的代码量的方法,这引发了问题。
Michael K

switch语句(以及长的if / else-if块)很糟糕,应尽可能避免使用,以保持代码的可维护性。表示“如果可能的话”表示,在某些情况下必须存在该开关,在这种情况下,请尝试将其保持在单个位置,这样可以减少维护工作量(这样做很容易如果未正确隔离,则会意外错过代码中需要保持同步的5个位置中的1个)。
Shadow Man

10

是否可以在没有明显分支的情况下实施策略模式?

是的,使用每个策略实现在其注册的哈希图/字典。工厂方法将变成类似

Class strategyType = allStrategies[orderType];
return runtime.create(strategyType);

每个策略实现都必须使用其orderType和一些有关如何创建类的信息来注册工厂。

factory.register(NEW_ORDER, NewOrder.class);

如果您的语言支持,则可以使用静态构造函数进行注册。

register方法只不过向哈希映射添加了一个新值:

void register(OrderType orderType, Class class)
{
   allStrategies[orderType] = class;
}

[2012年5月4日更新]
此解决方案比我大多数时候都希望使用的原始“交换机解决方案”复杂得多。

但是,在策略经常变化的环境中(即,价格计算取决于客户,时间等),将此哈希图解决方案与IoC容器结合使用可能是一个很好的解决方案。


3
因此,现在您有了整个策略的哈希表,可以避免切换/案例?几行factory.register(NEW_ORDER, NewOrder.class);清洁器比几行清洁器(或更少违反OCP)到底如何case NEW_ORDER: return new NewOrder();
pdr 2012年

5
一点都不干净。这是违反直觉的。它牺牲了保持简单愚蠢的原理来改进开放-封闭原则。控制反转和依赖注入同样如此:比简单的解决方案更难理解。问题是“没有明显分支的实施...”而不是“如何创建更直观的解决方案”
k3b

1
我也看不到您也避免了分支。您刚刚更改了语法。
pdr 2012年

1
使用在需要时才加载类的语言的这种解决方案是否没有问题?静态代码要等到类加载后才能运行,并且永远不会加载该类,因为没有其他类引用它。
凯文·克莱恩

2
我个人喜欢这种方法,因为它使您可以将策略视为可变数据,而不是将它们视为不可变的代码。
Tacroy 2012年

2

“策略”是关于必须在替代算法之间至少选择一次,而不是更少。在程序的某个地方,必须要做出决定-可能是用户还是您的程序。如果使用IoC,反射,数据文件评估程序或开关/案例构造来实现,则不会改变这种情况。


我不同意你的一次陈述可以在运行时交换策略。例如,在无法使用标准ProcessingStrategy处理请求之后,可以选择VerboseProcessingStrategy并重新运行处理。
dmux

@dmux:当然,相应地编辑了我的答案。
布朗

1

在指定可能的行为时使用策略模式,在启动时指定处理程序时最好使用策略模式。通过介体,您的标准IoC容器,您描述的某些Factory来指定要使用的策略实例,或者仅通过基于上下文使用正确的实例即可(通常,该策略是广泛使用的一部分)包含它的类)。

对于这种行为,需要根据数据调用不同的方法,那么我建议使用手头语言提供的多态结构。不是策略模式。


1

在与我工作的其他开发人员交谈时,我发现了另一个有趣的解决方案-特定于Java,但是我确信该想法可以在其他语言中使用。使用类引用构造枚举(或映射,不要紧),并使用反射实例化。

enum FactoryType {
   Type1(Type1.class),
   Type2(Type2.class);

   private Class<? extends Type> clazz;

   private FactoryType(Class<? extends Type> clazz) {
      this.clazz = clazz;
   }

   public Class<? extends Type> getTypeClass() {
      return clazz;
   }
}

这大大减少了工厂代码:

public Type create(FactoryType type) throws Exception {
   return type.getTypeClass().newInstance();
}

(请忽略不良的错误处理-示例代码:)

它不是灵活的,因为仍然需要构建...但是它将代码更改减少到一行。我喜欢将数据与工厂代码分开。您甚至可以通过使用抽象类/接口提供通用方法,或创建编译时注释以强制特定的构造函数签名,来进行诸如设置参数之类的操作。


不知道是什么原因导致此问题回到主页,但这是一个很好的答案,在Java 8中,您现在可以创建没有反射的构造方法引用。
JimmyJames

1

通过使每个类定义其自己的订单类型,可以提高可扩展性。然后您的工厂选择一个匹配的对象。

例如:

public interface IOrderStrategy
{
    OrderType OrderType { get; }
}

public class NewOrder : IOrderStrategy
{
    public OrderType OrderType { get; } = OrderType.NewOrder;
}

public class OrderFactory
{
    private IEnumerable<IOrderStrategy> _strategies;

    public OrderFactory(IEnumerable<IOrderStrategy> strategies) // Injected by IoC container
    {
        _strategies = strategies;
    }

    public IOrderStrategy Create(OrderType orderType)
    {
        IOrderStrategy strategy = _strategies.FirstOrDefault(s => s.OrderType == orderType);

        if (strategy == null)
            throw new ArgumentException("Invalid order type.", nameof(orderType));

        return strategy;
    }
}

1

我认为应该限制多少个分支。

例如,如果我的switch语句中包含八个以上的案例,那么我将重新评估代码并寻找可以重构的内容。我经常发现某些案例可以分组到单独的工厂中。我在这里的假设是,有一家工厂可以制定战略。

无论哪种方式,您都无法避免这种情况,因此必须在某个地方检查正在使用的对象的状态或类型。然后,您将为此制定策略。好旧的关注点分离。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.