繁体   English   中英

不同参数的策略模式

[英]Strategy Pattern with different parameters

我在使用策略模式时遇到了一个问题。 我正在实施一项用于创建任务的服务。 此服务还解决了此任务的负责文员。 解决职员问题是通过使用策略模式来完成的,因为有不同的方法可以做到这一点。 关键是每种策略都可能需要不同的参数来解决文员问题。

例如:

interface ClerkResolver {
    String resolveClerk(String department);
}

class DefaultClerkResolver implements ClerkResolver {

    public String resolveClerk(String department) {
        // some stuff
    }
}

class CountryClerkResolver implements ClerkResolver {

    public String resolveClerk(String department) {
        // I do not need the department name here. What I need is the country.
    }

}

问题是每个解析器可能依赖不同的参数来解析负责的文员。 对我来说,这听起来像是我代码中的一个设计问题。 我还尝试使用一个类作为参数来保留策略可能需要的所有值,例如:

class StrategyParameter {

   private String department;
   private String country;

   public String getDepartment() ...
}

interface ClerkResolver {
    String resolveClerk(StrategyParameter strategyParameter);
}

但老实说,我对这个解决方案并不满意,因为每次策略需要新的/不同的参数时,我都必须更改参数类。 其次,策略的调用者必须设置所有参数,因为他不知道哪种策略会解决事务员,因此他必须提供所有参数(但这还不错)。

同样,对我来说,这听起来像是我的代码中的一个设计问题,但我找不到更好的解决方案。

- - 编辑

此解决方案的主要问题是在创建任务时。 任务服务如下所示:

class TaskService {

    private List<ClerkResolver> clerkResolvers;

    Task createTask(StrategyParamter ...) {

        // some stuff

       for(ClerkResolver clerkResolver : clerkResolvers) {
          String clerk = clerkResolver.resolveClerk(StrategyParameter...)
          ...
       }

       // some other stuff
    }

}

正如您在使用 TaskService 时所看到的,调用者必须提供必要的信息来解析文员,即部门名称和/或国家/地区,因为 TaskService 本身没有这些信息。

当必须创建任务时,调用者必须提供 StrategyParameter,因为它们是解析文员所必需的。 同样,问题是,呼叫者没有所有信息,即他不了解该国家/地区。 他只能设置部门名称。 这就是为什么我在接口中添加了第二个方法,以确保该策略可以处理业务员解析:

interface ClerkResolver {
    String resolveClerk(StrategyParameter strategyParameter);
    boolean canHandle(StrategyParameter strategyParameter);
}

冒着重复我的风险,这个解决方案对我来说听起来不合适。

因此,如果有人对此问题有更好的解决方案,我将不胜感激。

感谢您的意见!

我认为对于任务的实际内容存在一些混淆。 在我看来,任务是由文员完成的事情。 因此,您可以在不了解文员的情况下自行创建任务。

根据该任务,您可以为其选择合适的文员。 将任务分配给文员本身可以包装为其他类型的任务。 因此,选择文员的通用界面是:

interface ClerkResolver {
    String resolveClerk(Task task);
}

例如,为了实现这种文员解析器,您可以使用基于任务实际类型的策略模式。

恭喜,你发现了策略模式的缺点之一

策略模式可用于托管不同的算法,这些算法要么没有参数,要么每个算法的参数集相同。 但是,如果要使用具有不同参数集的各种算法,则达不到要求。

幸运的是, 本文提出了一个优雅的解决方案:

在此处输入图片说明


在此处输入图片说明


将其应用于您的特定情况:

public abstract class ClerkResolver {  // Role: Algorithm 

    protected Parameter[] parameters;

    public Parameter[] getParameters() {
        return parameters.clone();
    }

    abstract String resolveClerk();

}
class CountryClerkResolver extends ClerkResolver {

    public CountryClerkResolver() {
        parameters = new Parameter[1];
        parameters[0] = new StringParameter("country", "Denmark"); // Default value is 'Denmark'
    }

    private String country;

    @Override
    String resolveClerk() {
        country = ((StringParameter) parameters[0]).getValue();

        // CountryClerkResolver specific code

        return country;
    }

}
class DefaultClerkResolver extends ClerkResolver { // Role: ConcreteAlgorithm

    public DefaultClerkResolver() {
        parameters = new Parameter[1];
        parameters[0] = new StringParameter("department", "someName");
    }

    private String department;

    @Override
    public String resolveClerk() {
        department = ((StringParameter) parameters[0]).getValue();

        // DefaultClerkResolver specific code

        return department;
    }

}
public abstract class Parameter { // Role: Parameter

    private String name;

    public String getName() {
        return name;
    }

    public Parameter(String name) {
        this.name = name;
    }

}
public class StringParameter extends Parameter { // Role: ConcreteParameter

    private String value;

    public StringParameter(String name, String value) {
        super(name);
        this.value = value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

使用示例:

public class Main {
    public static void main(String... args) {  // Role: client
        ClerkResolver clerk_1 = new CountryClerkResolver();

        Parameter[] parameters = clerk_1.getParameters();

        StringParameter country = (StringParameter) parameters[0];  // [¤]
        country.setValue("USA"); // Overwriting default value

        clerk_1.resolveClerk();
    }
}

如果您希望CountryClerkResolver使用三个参数(其中一个是整数),您将执行以下操作:

首先引入一个IntegerParameter

public class IntegerParameter extends Parameter {

    private int value;

    public IntegerParameter(String name, int value) {
        super(name);
        this.value = value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

现在改变构造函数和策略的方法:

class CountryClerkResolver extends ClerkResolver {

    public CountryClerkResolver() {
        parameters = new Parameter[1];
        parameters[0] = new StringParameter( "country",         "Denmark"   ); // Default value is 'Denmark'
        parameters[1] = new StringParameter( "newStringParam",  "defaultVal");
        parameters[2] = new IntegerParameter("newIntegerParam", 9999        );
    }

    private String country;
    private String newStringParam;
    private int    newIntegerParam;

    @Override
    String resolveClerk() {
        country         = ((StringParameter)  parameters[0]).getValue();
        newStringParam  = ((StringParameter)  parameters[1]).getValue();
        newIntegerParam = ((IntegerParameter) parameters[2]).getValue();

        // CountryClerkResolver specific code

        return country;
    }

}

有关模式的更详细说明,请参阅论文


好处:

  • [灵活] 每当您想添加新的具体AlgorithmParameter时,都可以通过添加进行更改。
  • 您不必处理算法(策略)的公共方法的签名,因为它不带任何参数; 在调用方法之前,参数将被设置。

负债:

  • [稳定性] 在获取参数时(参见[¤] ),程序员可能会混淆parameters数组的索引。 (例如,如果parameters[0]不是country而是,比如continent怎样)

    • 解决稳定性问题的可能解决方案是:
public class Main {
    public static void main(String... args) {  // Role: client
        ClerkResolver clerk_1 = new CountryClerkResolver();

        Parameter[] parameters = clerk_1.getParameters();
                         
                   // Analyzability suffers because of ugly casting: 
        StringParameter country = (StringParameter) getParameterWithName("country", parameters);
        country.setValue("USA"); // Overwriting default value

        clerk_1.resolveClerk();
    }

    private static Parameter getParameterWithName(String paramName, Parameter[] parameters) {
        for (Parameter param : parameters) 
            if (param.getName().equals(paramName))
                return param;
        throw new RuntimeException();  
    }

}

      • 为了提高可读性,可以引入Parameter[]的抽象:
import java.util.ArrayList;
import java.util.List;

public class ParameterList {

    private final List<Parameter> parameters;

    public ParameterList(int length) {
        this.parameters = new ArrayList<>(length);
    }
    
    public void add(Parameter p) {
        parameters.add(p);
    }
    
    private Parameter getParameterOf(String name) {
        return parameters.stream()
                            .filter(p -> p.getName().equals(name))
                            .findFirst()
                            .orElse(null);
    }



    // =================================================== ~~~~~~~~~~~~~~~~~~~~~~~~
    // The liability of ParameterList is that we have to write a lot of boilerplate getter methods.
    // However, because most parameter to any strategy class is a primitive type (or String), we don't
    // have to continiously add new methods; this is thus acceptable.

    // === A getter for each type of {@code Parameter} is needed ~~~~~~~~~~~~~~~~~~~~~~~~
    public StringParameter getStringParameterOf(String name) {
        return (StringParameter) getParameterOf(name);
    }

    public IntegerParameter getIntegerParameterOf(String name) {
        return (IntegerParameter) getParameterOf(name);
    }

    // === A value of each type of {@code Parameter} is needed ~~~~~~~~~~~~~~~~~~~~~~~~
    public String getValueOfStringParameter(String name) {
        return ((StringParameter) getParameterOf(name)).getValue();
    }

    public int getValueOfIntegerParameter(String name) {
        return ((IntegerParameter) getParameterOf(name)).getValue();
    }

    // =================================================== ~~~~~~~~~~~~~~~~~~~~~~~~


    public ParameterList clone() throws CloneNotSupportedException {
        return (ParameterList) super.clone();
    }
    
}

GitHub:所有代码

我真的很喜欢 'SpaceTrucker 的建议,有时问题可以通过将抽象移到不同的层次来解决:)

但是,如果您的原始设计更有意义(只有您可以根据您对规范的感觉来判断)-那么恕我直言,您可以:1)保持“将所有内容加载到 StrategyParameter”的方法 2)或将此责任转移到战略

对于选项(2),我假设有一些通用实体(帐户?客户?)可以从中推断出部门/国家/地区。 然后你有“CountryClerkResolver.resolveClerk(String accountId)”来查找国家。

恕我直言,(1),(2)都是合法的,具体取决于上下文。 有时(1)对我有用,因为所有参数(部门+国家/地区)的预加载都很便宜。 有时我什至设法用商业直观实体(例如帐户)替换合成的“策略参数”。 有时(2)对我来说效果更好,例如,如果“部门”和“国家”需要单独且昂贵的查找。 对于复杂的参数,它变得特别引人注目——例如,如果策略根据他们在“客户满意度”评论中的分数来选择文员,那么这是一个复杂的结构,不应为更简单的策略加载。

让我们首先假设您的代码基于一个简单的 if-else-if 块。

在这种情况下,您仍然需要预先获得所有必需的输入。 没有办法绕过它。

通过使用策略模式,您开始解耦您的代码——即,您定义基本接口和具体实现。

仅仅有这种设计还不够好,因为您仍然需要一个 if-else-if 块。

此时,您可以查看以下设计更改:

  1. 使用工厂模式从该系统加载所有可用策略。 这可以基于元信息,例如 JDK 中可用的Service Loader模式。

  2. 确定一种策略,您可以通过该策略查询可用的实现,以确定它们是否可以处理给定的输入参数集。 这可以像canYouResolve(input) != null一样简单。 通过这样做,我们从 if-else-if 块更改为 for-each 循环。

  3. 在您的情况下,您也有一个默认实现 因此,让我们说默认实现是您模块的一部分,其他策略来自其他 jar(通过 ServiceLoader 从第 1 点加载)。

  4. 当您的代码开始使用时,您首先会寻找所有可用的策略; 询问他们是否可以处理当前的情况; 如果他们都无法处理,则使用默认实现。

如果出于某种原因,您有多个解析器能够处理特定输入,则应考虑为这些解析器定义优先级。

现在,来到输入参数,这些参数可以从某个输入对象派生吗? 如果是这样,那么为什么不将该输入对象本身发送到解析器。

注意:这与 JavaEE ELResolver 的工作方式非常相似 - 在这种情况下,代码将 EL 标记为已解析,从而通知根类解析已完成。

注意:如果您认为服务加载器太重,那么请查看搜索所有 META-INF/some-file-that-you-like 以识别系统中可用的解析器。

根据我自己的经验,大多数情况下,您最终会编写混合模式的代码来实现手头的用例。

希望这对您的场景有所帮助。

由于 Java 是静态类型的,模拟动态对象的一种好方法是使用 Map。 我会这样做是为了将动态参数传递给我的解析器:

class StrategyParameter extends Map {} 
// Map could be used directly, but this make the code more readable

然后,我的策略模式变为: interface ClerkResolver { String resolveClerk(StrategyParameter strategyParameter); }

class DefaultClerkResolver implements ClerkResolver {

    public String resolveClerk(StrategyParameter strategyParameter) {
        // strategyParameter.get("department");
    }
}

class CountryClerkResolver implements ClerkResolver {

    public String resolveClerk(StrategyParameter strategyParameter) {
        // strategyParameter.get("country");
    }

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM