我在使用策略模式时遇到了一个问题。我正在实现创建任务的服务。此服务还解决了此任务的负责人员。解决职员是通过使用策略模式来完成的,因为有不同的方法可以做到这一点。关键是每个策略都需要不同的参数来解决职员。
例如:
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);
}
冒着重复我的风险,这个解决方案对我来说听起来不对。
所以,如果有人能够更好地解决这个问题,我将不胜感激。
感谢您的评论!
答案 0 :(得分:8)
我认为对于任务实际是什么存在一些困惑。在我看来,任务是由职员完成的。因此,您可以在不知道职员的情况下自行创建任务。
根据该任务,您可以为其选择合适的职员。将任务分配给职员本身可以包含在其他任务中。因此,选择职员的通用界面是:
interface ClerkResolver {
String resolveClerk(Task task);
}
为了实现这种职员解析器,您可以根据任务的实际类型使用策略模式。
答案 1 :(得分:5)
恭喜,你发现了策略模式的一个缺点:
<块引用>策略模式可用于托管不同的算法,这些算法要么没有参数,要么每个算法的参数集相同。但是,如果要使用具有不同参数集的各种算法,则达不到要求。
将其应用于您的具体情况:
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。
优点:
Algorithm
或 Parameter
时,可以通过添加进行更改。负债:
[¤]
),程序员可能会混淆 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();
}
}
答案 2 :(得分:3)
我真的很喜欢'SpaceTrucker的建议,有时通过将抽象移动到不同的层次来解决问题:)
但如果您的原始设计更有意义(根据您对规格的感觉,只能告诉您) - 那么恕我直言可以: 1)保持“将所有内容加载到StrategyParameter”的方法 2)或将此责任转移到战略
对于选项(2),我假设有一些共同的实体(账户?客户?),可以从中推断出部门/国家。 然后你有“CountryClerkResolver.resolveClerk(String accountId)”,它将查找该国家。
恕我直言(1),(2)都是合法的,具体取决于具体情况。 有时(1)对我有用,因为所有参数(部门+国家)都预先装载便宜。有时我甚至设法用一个商业直观的实体(例如Account)替换合成的“StrategyParameter”。 有时(2)对我来说效果更好,例如如果'部门'和'国家'需要单独和昂贵的查找。特别注意到复杂的参数 - 例如如果一个策略根据他们在“客户满意度”评论中的分数选择职员,那么这是一个复杂的结构,不应该为更简单的策略加载。
答案 3 :(得分:2)
让我们首先假设您的代码基于一个简单的if-else-if块。
在这种情况下,您仍需要预先获得所有必需的输入。没有绕过它。
通过使用策略模式,您可以开始解耦代码 - 即,您定义基本接口和具体实现。
只是让这个设计不够好,因为你仍然需要一个if-else-if块。
此时,您可以查看以下设计更改:
使用工厂模式加载此系统中的所有可用策略。这可以基于元信息,例如JDK中可用的Service Loader模式。
确定一种策略,通过该策略可以查询可用的实现,以确定它们是否可以处理给定的输入参数集。这可以像 canYouResolve(input)!= null 一样简单。通过这样做,我们从if-else-if块更改为for-each循环。
在您的情况下,您还有默认实施。所以,让我们说默认实现是模块的一部分,其他策略来自其他jar(从第1点通过ServiceLoader加载)。
当您的代码启动时,您首先要查找所有可用的策略;问他们是否可以处理当前情况;如果没有人能够处理它,那么使用默认实现。
如果出于某种原因,您有多个解析器能够处理特定输入,则应考虑为这些解析器定义优先级。
现在,来到输入参数,这些参数可以从某个输入对象派生吗?如果是这样,那么为什么不将该输入对象本身发送给解析器。
注意:这与JavaEE ELResolver的工作方式非常相似 - 在这种情况下,代码将EL标记为已解析,从而通知根类解析完成。
注意:如果您认为服务加载程序太重,请查看搜索所有META-INF / some-file-you-like以识别系统中可用的解析程序。
根据我自己的经验,大多数时候,你最终会编写混合模式的代码来实现手头的用例。
希望这有助于您的方案。
答案 4 :(得分:-2)
由于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");
}
}