策略模式,每种算法都有不同的方法签名
我正在对某些代码进行重构。策略模式,每种算法都有不同的方法签名
我们有一个投资者名单,分配给每个投资者。总金额应该等于另一个总额,但有时会有几美分的差异,所以我们使用不同的算法将这些差异分配给每个投资者。
当前的代码是这样的:
public void Round(IList<Investors> investors, Enum algorithm, [here goes a list of many parameters]) {
// some checks and logic here - OMMITED FOR BREVITY
// pick method given algorithm Enum
if (algoritm == Enum.Algorithm1) {
SomeStaticClass.Algorithm1(investors, remainders, someParameter1, someParameter2, someParameter3, someParameter4)
} else if (algoritm == Enum.Algorithm2) {
SomeStaticClass.Algorithm2(investors, remainders, someParameter3)
}
}
到目前为止,我们只有两种算法。我必须执行第三个。我被赋予重构现有实现的可能性,以及为将来的算法做一些通用代码,以便为每个客户定制。
我的第一个想法是“好的,这是一种战略模式”。但是我看到的问题是两种算法都会收到不同的参数列表(前两个除外)。未来的算法也可以接收不同的参数列表。 “共同”唯一的事情是投资者名单和其余部分。
我该如何设计这个,所以我有一个更清洁的界面? 我想到
- 建立与所有可能的参数的接口,并且所有的实现之间共享 。
- 将具有所有可能参数的对象用作属性,并将该通用对象用作接口的一部分。 I 将具有3个参数:投资者列表,剩余物对象和“参数”对象。但在这种情况下,我有类似的问题。实例化每个对象并填充所需的属性取决于算法(除非我将它们全部设置)。 I 将不得不使用工厂(或其他)来实例化它,使用界面中的所有参数,对吗?我会将参数过多的问题转移到“工厂”或其他任何方面。
- 使用动态对象而不是静态类型的对象。不过 像以前提出了同样的问题,实例化
我也想过用访问者模式的,但据我所知,如果我有不同的实体不同的算法来使用,这样会出现这种情况,另一投资者类别。所以我不认为这是正确的做法。
到目前为止,最令我信服的是第二个,尽管我仍然对此保持低调。
任何想法?
感谢
战略有不同的实现。当所有替代具体策略需要相同的类型签名时,它是直截了当的。但是当具体的实现开始向上下文请求不同的数据时,我们必须通过放松封装来优雅地退后一步(“破坏封装”是已知的策略的缺点),或者我们可以将上下文传递给方法签名或构造函数中的策略需要多少钱。
通过使用接口并将较大的对象树分解为更小的包含,我们可以限制对大部分上下文状态的访问。
以下代码演示了通过方法参数。
public class Context {
private String name;
private int id;
private double salary;
Strategy strategy;
void contextInterface(){
strategy.algorithmInterface(this);
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public double getSalary() {
return salary;
}
}
public interface Strategy {
// WE CAN NOT DECIDE COMMON SIGNATURE HERE
// AS ALL IMPLEMENTATIONS REQUIRE DIFF PARAMS
void algorithmInterface(Context context);
}
public class StrategyA implements Strategy{
@Override
public void algorithmInterface(Context context) {
// OBSERVE HERE BREAKING OF ENCAPSULATION
// BY OPERATING ON SOMEBODY ELSE'S DATA
context.getName();
context.getId();
}
}
public class StrategyB implements Strategy{
@Override
public void algorithmInterface(Context context) {
// OBSERVE HERE BREAKING OF ENCAPSULATION
// BY OPERATING ON SOMEBODY ELSE'S DATA
context.getSalary();
context.getId();
}
}
好的,您正在关注选项二。但是你所说的是始终填充上下文对象中的所有属性,即使其中一些属性在算法中不需要。我对吗 ?如果没有,我不知道你在这里如何解决不同的签名问题,因为你将它移出策略并将其移入上下文实例化对象 –
@ Gonzalo.-你的上下文似乎更复杂,所以也许在每次调用策略方法时传递的上下文对象都没有意义。另一种方法是让每个具体策略都用具有所需环境的特定构造函数实例化。一个简单的工厂可以封装创建逻辑。然后AlgorithmX方法将是多态的,可能没有参数。你也可以用一个简单的工厂来使用这个答案来创建Context实例。 – Fuhrmanator
@贡萨洛 - 那是真的。经过仔细审阅代码后,我发现很少有关战略必要条件的疑问。这些算法是否相互替换? Round方法属于它应该是的类吗? (可能不是这就需要这么多参数的原因)。我们能否从适合所有策略的代码中识别出良好的背景(背景不应该通过不同的策略改变状态)。可能会需要更多的重构,以便将策略适用部分分离。虽然不知道,因为不知道完整的代码。 –
好吧,我可能会在错误的方向前进...但似乎有点奇怪,你传递参数给所有的算法,以及实际使用到的算法标识符。 Round()函数不应该理想地获得它需要的操作吗?
我想象的功能调用 ROUND()看起来是这样的:
if (something)
algToUse = Enum.Algorithm1;
else
if (otherthing)
algToUse = Enum.Algorithm2;
else
algToUse = Enum.Algorithm3;
Round(investors, remainder, algToUse, dayOfMonth, lunarCycle, numberOfGoblinsFound, etc);
...如果有什么,而是你做了这样的事情:
public abstract class RoundingAlgorithm
{
public abstract void PerformRounding(IList<Investors> investors, int remainders);
}
public class RoundingRandomly : RoundingAlgorithm
{
private int someNum;
private DateTime anotherParam;
public RoundingRandomly(int someNum, DateTime anotherParam)
{
this.someNum = someNum;
this.anotherParam = anotherParam;
}
public override void PerformRounding(IList<Investors> investors, int remainder)
{
// ... code ...
}
}
// ... and other subclasses of RoundingAlgorithm
// ... later on:
public void Round(IList<Investors> investors, RoundingAlgorithm roundingMethodToUse)
{
// ...your other code (checks, etc)...
roundingMethodToUse.Round(investors, remainders);
}
...然后您的较早功能看起来像:
RoundingAlgorithm roundingMethod;
if (something)
roundingMethod = new RoundingByStreetNum(1, "asdf", DateTime.Now);
else
if (otherthing)
roundingMethod = new RoundingWithPrejudice(null);
else
roundingMethod = new RoundingDefault(1000);
Round(investors, roundingMethod);
..基本上,而不是填充该枚举值,只需创建一个RoundingAlgorithm对象,并将其传递给Round()。
不幸的是我无法更新Round方法签名。我也不喜欢它,但是我放置我的问题的地方是我可以重构的“起点”。也许问题在于“Round”有更多的逻辑,而不仅仅是四舍五入(我已经忽略了它,但在评论中提到),所以它感觉比它应该更012 –
为什么不能更新Round()方法签名?你是不是已经更新它,在输入结尾添加另一个参数? – Kevin
是的,但我不能更新它在另一个dll中调用的位置,不,我没有更新到目前为止,我有完整的参数列表。我没有在轮次签名末尾添加新参数 –
参数是否都是同一类型?如果是这样,他们可以放在一个列表中。只有1个算法可以迭代参数列表并执行所需的操作吗? – brumScouse
当前实现具有小数值,整数和枚举。可能有一个字符串,虽然 –
所有的参数设置,无论算法?什么决定算法?为什么不在这里新增阿尔戈?似乎不必要的额外级别 – brumScouse