java 不同参数的策略模式

zu0ti5jz  于 12个月前  发布在  Java
关注(0)|答案(6)|浏览(156)

我在使用策略模式时遇到了一个问题。我正在实现一个用于创建任务的服务。该服务还解析了此任务的负责职员。解析职员是通过使用策略模式来完成的,因为有不同的方法来执行此操作。关键是每个策略可能需要不同的参数来解析职员。
举例来说:

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


冒着重复我的风险,这个解决方案对我来说听起来不对。
所以,如果有人有更好的解决方案,我会很乐意听到它。
感谢您的评论!

q8l4jmvw

q8l4jmvw1#

我认为对于任务到底是什么存在一些混淆。在我看来,任务是由职员完成的事情。因此,您可以在不了解职员的情况下创建任务本身。
基于该任务,您可以为其选择合适的职员。将任务分配给职员本身可以 Package 到其他类型的任务中。因此,选择职员的常见界面如下:

interface ClerkResolver {
    String resolveClerk(Task task);
}

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

ebdffaop

ebdffaop2#

恭喜你,你发现了策略模式的一个缺点
策略模式可用于托管不同的算法,这些算法要么没有参数,要么每个算法的参数集相同。然而,如果要使用具有不同参数集的各种算法,则策略模式福尔斯不足。

幸运的是,这个paper提供了一个优雅的解决方案:

x1c 0d1x的数据



将其应用于您的具体情况:

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

}


有关该模式的更详细说明,请参阅the paper
优点:

  • [灵活]每当您想添加新的混凝土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: all code

bwitn5fc

bwitn5fc3#

我真的很喜欢SpaceTrucker的建议,有时问题可以通过将抽象转移到不同的层次来解决:)
但是,如果你的原始设计更有意义(只有你能告诉,基于你的感觉规格)-然后恕我直言,一个可以:1)保持你的方法“加载一切到StrategyParameter”2)或将此责任转移到战略
对于选项(2),我假设有一些公共实体(account?customer?),可以从中推断出部门/国家。然后您可以使用“CountryClerkResolver.resolveClerk(StringaccountId)”来查找国家。
恕我直言,(1),(2)都是合法的,这取决于上下文。有时(1)对我有用,因为所有参数(部门+国家)是便宜的预加载。有时我甚至设法取代合成'战略参数'与业务直观的实体(例如账户)。有时(2)对我来说更好,例如,如果'部门'和'国家'需要单独的和昂贵的查找。它变得特别注意复杂的参数-例如,如果一个策略根据职员在“客户满意度”评论中的得分来选择职员,那么这是一个复杂的结构,不应该为简单的策略加载。

guicsvcw

guicsvcw4#

让我们首先假设你的代码是基于一个简单的if-else-if块。
在这种情况下,你仍然需要预先准备好所有必要的输入,这是无法回避的。
通过使用策略模式,您开始解耦代码-即,您定义了基本接口和具体实现。
仅仅有这样的设计是不够的,因为你仍然需要一个if-else-if块。
此时,您可以查看以下设计更改:
1.使用工厂模式从这个系统加载所有可用的策略。这可以基于Meta信息,比如JDK中可用的Service Loader模式。
1.确定一个策略,通过它可以查询可用的实现,以确定它们是否可以处理给定的输入参数集。这可以像 canYouResolve(input)!= null 一样简单。通过这样做,我们将if-else-if块更改为for-each循环。
1.在你的例子中,你也有一个 Default Implementation。所以,让我们假设默认实现是你的模块的一部分,其他策略来自其他jar(通过ServiceLoader从第1点加载)。
1.当你的代码启动时,你首先寻找所有可用的策略;询问它们是否可以处理当前的场景;如果它们都不能处理,那么就使用默认的实现。
如果出于某种原因,您有多个解析器能够处理特定的输入,您应该考虑为这些解析器定义优先级。
现在,进入输入参数,这些参数可以从某个输入对象 * 派生 * 吗?如果可以,那么为什么不将输入对象本身发送给解析器呢?
注意事项:这与JavaEE ELResolver的工作方式非常相似-在这种情况下,代码将EL标记为已解析,从而通知根类解析完成。
注意事项:如果您认为服务加载器太重,那么可以搜索所有您喜欢的META-INF/some-file-that-you-like来识别系统中可用的解析器。
根据我自己的经验,大多数时候,您最终会编写混合模式的代码来实现手头的用例。
希望这对你的剧本有帮助。

w6mmgewl

w6mmgewl5#

战略模式的可能解决方案之一

@Service
@RequiredArgsConstructor
public class StrategyService {

  private final FirstWay firstWay;
  private final SecondWay secondWay;

  private final Map<Operation, Strategy> strategies = new HashMap<>();

  @FunctionalInterface
  interface Strategy {
    Message execute(Entity entity);
  }

  @PostConstruct
  private void init() {
    strategies.put(
        Operation.FIRST_STRATEGY,
        entity ->
            firstWay.process(entity.getId()));
    strategies.put(
        Operation.SECOND_STRATEGY,
        entity ->
            secondWay.process(entity.getId()));
  }

  public Message execute(Entity entity) {
    Strategy strategy = strategies.get(entity.getOperation());

    if (Objects.isNull(strategy)) {
      throw new TechnicalException(
              TechnicalException.NO_STRATEGY_OPERATION);
    }
    return strategy.execute(entity);
  }
}

字符串

njthzxwz

njthzxwz6#

由于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");
    }

}

相关问题