Java 不同参数的策略模式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19973203/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Strategy Pattern with different parameters
提问by Daniel
I came across a problem when using the strategy pattern. I am implementing a service for creating tasks. This service also resolves the responsible clerk for this task. Resolving the clerk is done by using the strategy pattern because there are different ways of doing this. The point is that every strategy could need different parameters to resolve the clerk.
我在使用策略模式时遇到了一个问题。我正在实施一项用于创建任务的服务。此服务还解决了此任务的负责文员。解决职员问题是通过使用策略模式来完成的,因为有不同的方法可以做到这一点。关键是每种策略都可能需要不同的参数来解决文员问题。
For example:
例如:
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.
}
}
The problem is that every resolver may depend on different parameters to resolve the responsible clerk. For me this sounds like a design issue in my code. I also tried to have a class as a parameter to keep all values that could be needed by the strategies, like:
问题是每个解析器可能依赖不同的参数来解析负责的文员。对我来说,这听起来像是我代码中的一个设计问题。我还尝试使用一个类作为参数来保留策略可能需要的所有值,例如:
class StrategyParameter {
private String department;
private String country;
public String getDepartment() ...
}
interface ClerkResolver {
String resolveClerk(StrategyParameter strategyParameter);
}
But to be honest, I am not satisfied with this solution because I have to change the parameter class everytime a strategy needs a new / different argument. And secondly the caller of the strategy must set all parameters because he does not know which strategy will resolve the clerk, therefore he has to provide all parameters (but this isn't that bad).
但老实说,我对这个解决方案并不满意,因为每次策略需要新的/不同的参数时,我都必须更改参数类。其次,策略的调用者必须设置所有参数,因为他不知道哪种策略会解决事务员,因此他必须提供所有参数(但这还不错)。
Again, for me this sounds like a design issue in my code, but I can't find a better solution.
同样,对我来说,这听起来像是我代码中的一个设计问题,但我找不到更好的解决方案。
--- EDIT
- - 编辑
The main problem with this solution is when creating the task. The task service looks like this:
此解决方案的主要问题是在创建任务时。任务服务如下所示:
class TaskService {
private List<ClerkResolver> clerkResolvers;
Task createTask(StrategyParamter ...) {
// some stuff
for(ClerkResolver clerkResolver : clerkResolvers) {
String clerk = clerkResolver.resolveClerk(StrategyParameter...)
...
}
// some other stuff
}
}
As you can see when the TaskService is used, the caller must provide the necessary information to resolve the clerk, i.e. the department name and/or the country, because the TaskService itself doesn't have these information.
正如您在使用 TaskService 时所看到的,调用者必须提供必要的信息来解析文员,即部门名称和/或国家/地区,因为 TaskService 本身没有这些信息。
When a task has to be created, the caller must provide the StrategyParameter, because they are necessary to resolve the clerk. Again, the problem is, that the caller doesn't have all the information, i.e. he has no knowledge of the country. He can only set the department name. That's why I added a second method to the interface to ensure that the strategy can handle the clerk resolution:
当必须创建任务时,调用者必须提供 StrategyParameter,因为它们是解析文员所必需的。同样,问题是,呼叫者没有所有信息,即他不了解该国家/地区。他只能设置部门名称。这就是为什么我在接口中添加了第二个方法,以确保该策略可以处理业务员解析:
interface ClerkResolver {
String resolveClerk(StrategyParameter strategyParameter);
boolean canHandle(StrategyParameter strategyParameter);
}
At the risk of repeating me, this solution doesn't sound right to me.
冒着重复我的风险,这个解决方案对我来说听起来不合适。
So, if anybody has a better solution for this problem I would appreciate to hear it.
因此,如果有人对此问题有更好的解决方案,我将不胜感激。
Thanks for your comments!
感谢您的意见!
采纳答案by SpaceTrucker
I think there is some confusion about what the task actually is. In my thinking a task is something that is done by a clerk. So you are able to create a task itself without knowing about a clerk.
我认为对于任务的实际内容存在一些混淆。在我看来,任务是由文员完成的事情。因此,您可以在不了解文员的情况下自行创建任务。
Based on that task you can choose an appropriate clerk for it. The assignment of the task to the clerk can itself be wrapped to some other kind of task. So a common interface for choosing a clerk would be:
根据该任务,您可以为其选择合适的文员。将任务分配给文员本身可以包装为其他类型的任务。因此,选择文员的通用界面是:
interface ClerkResolver {
String resolveClerk(Task task);
}
For implementing this kind of clerk resolver you can use the strategy pattern based on the actual type of the task for example.
例如,为了实现这种文员解析器,您可以使用基于任务实际类型的策略模式。
回答by Harsha R
Let us start by assuming that your code is based on a simple if-else-if blocks.
让我们首先假设您的代码基于一个简单的 if-else-if 块。
In such a scenario, you will still need to have all the required inputs upfront. There is no getting around it.
在这种情况下,您仍然需要预先获得所有必需的输入。没有办法绕过它。
By using the strategy pattern, you start decoupling your code - i.e., you define the base interface and concrete implementation.
通过使用策略模式,您开始解耦您的代码——即,您定义基本接口和具体实现。
Just having this design isn't good enough, because you still need to have an if-else-if block.
仅仅有这种设计还不够好,因为您仍然需要一个 if-else-if 块。
At this point, you can look at the following design changes:
此时,您可以查看以下设计更改:
Use a factory pattern to load all the available strategies from this system. This could be based on meta information, like the Service Loaderpattern that is available in the JDK.
Identify a strategy by which you can query the available implementations to find out if they can handle the given input set of parameters. This can be as simple as canYouResolve(input) != null. By doing this we change from an if-else-if block to an for-each loop.
In your case, you have a Default Implementationas well. So, let us say that the default implementation is part of your module and the other strategies are coming in from the other jars (that get loaded via the ServiceLoader from point 1).
When your code kicks-in, you first look for all available strategies; ask them if they can handle the current scenario; if none of them can handle it, then use the default implementation.
使用工厂模式从该系统加载所有可用策略。这可以基于元信息,例如JDK 中可用的Service Loader模式。
确定一种策略,您可以通过该策略查询可用的实现,以确定它们是否可以处理给定的输入参数集。这可以像canYouResolve(input) != null一样简单。通过这样做,我们从 if-else-if 块更改为 for-each 循环。
在您的情况下,您也有一个默认实现。因此,让我们说默认实现是您模块的一部分,而其他策略来自其他 jar(从第 1 点通过 ServiceLoader 加载)。
当您的代码开始使用时,您首先会寻找所有可用的策略;询问他们是否可以处理当前的情况;如果他们都无法处理,则使用默认实现。
If for some reason, you have more than one resolver being able to handle a particular input, you should consider defining a priority for those resolvers.
如果出于某种原因,您有多个解析器能够处理特定输入,则应考虑为这些解析器定义优先级。
Now, coming to the input parameters, can these parameters be derivedfrom some input object? If so, then why not send that input object itself to the resolver.
现在,来到输入参数,这些参数可以从某个输入对象派生吗?如果是这样,那么为什么不将该输入对象本身发送到解析器。
Note: This is very similar to how the JavaEE ELResolver works - In that case, the code marks the EL as resolved, thereby informing the root class that resolution is complete.
注意:这与 JavaEE ELResolver 的工作方式非常相似 - 在这种情况下,代码将 EL 标记为已解析,从而通知根类解析已完成。
Note: If you think the service loader is too heavy, then look at searching for all META-INF/some-file-that-you-like to identify the resolvers that are available in the system.
注意:如果您认为服务加载器太重,那么请查看搜索所有 META-INF/some-file-that-you-like 以识别系统中可用的解析器。
From my own experience, most of the times, you end up writing code that mixes patterns to achieve the use case at hand.
根据我自己的经验,大多数情况下,您最终会编写混合模式的代码来实现手头的用例。
Hope this helps your scenario.
希望这对您的场景有所帮助。
回答by Param
Since Java is statically typed, one good way of simulating dynamic objects is by using a Map. I would do this for passing dynamic parameters to my resolvers:
由于 Java 是静态类型的,模拟动态对象的一种好方法是使用 Map。我会这样做是为了将动态参数传递给我的解析器:
class StrategyParameter extends Map {}
// Map could be used directly, but this make the code more readable
Then, my strategy pattern becomes: interface ClerkResolver { String resolveClerk(StrategyParameter strategyParameter); }
然后,我的策略模式变为: 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");
}
}
回答by Pelit Mamani
I really liked 'SpaceTrucker's suggestion, that sometimes problems are solved by moving the abstraction to a different level :)
我真的很喜欢 'SpaceTrucker 的建议,有时问题可以通过将抽象移到不同的层次来解决:)
But if your original design makes more sense (which only you can tell, based on your feel of the spec) - then IMHO one can either: 1) Keep your approach of "loading everything into StrategyParameter" 2) Or move this responsibility to the Strategy
但是,如果您的原始设计更有意义(只有您可以根据您对规范的感觉来判断)-那么恕我直言,您可以:1)保持“将所有内容加载到 StrategyParameter”的方法 2)或将此责任转移到战略
For option (2), I assume there's some common entity (account? customer?) from which one can deduce the department/country. Then you have "CountryClerkResolver.resolveClerk(String accountId)" which would look up the country.
对于选项(2),我假设有一些通用实体(帐户?客户?)可以从中推断出部门/国家/地区。然后你有“CountryClerkResolver.resolveClerk(String accountId)”来查找国家。
IMHO both (1),(2) are legitimate, depending on context. Sometimes (1) works for me, because all params (department+country) are cheap to pre-load. Sometimes I even manage to replace the synthetic 'StrategyParameter' with a business-intuitive entity (e.g. Account). Sometimes (2) works better for me, e.g. if 'department' and 'country' required separate and expensive lookups. It becomes especially noticed with complex params - e.g. if a strategy selects clerks based on their scores in 'customer satisfaction' reviews, that's a complex structure which shouldn't be loaded for simpler strategies.
恕我直言,(1),(2)都是合法的,具体取决于上下文。有时(1)对我有用,因为所有参数(部门+国家/地区)的预加载都很便宜。有时我什至设法用商业直观实体(例如帐户)替换合成的“策略参数”。有时(2)对我来说效果更好,例如,如果“部门”和“国家”需要单独且昂贵的查找。对于复杂的参数,它变得特别引人注目——例如,如果策略根据他们在“客户满意度”评论中的分数来选择文员,那么这是一个复杂的结构,不应为更简单的策略加载。