【问题标题】:Strategy Pattern with each algorithm having a different method signature每种算法具有不同方法签名的策略模式
【发布时间】:2017-10-06 22:07:36
【问题描述】:

我正在对某些代码进行重构。

我们有一份投资者名单,每个人都分配了金额。总金额应该等于另一个总金额,但有时会有几美分的差异,因此我们使用不同的算法将这些差异分配给每个投资者。

目前的代码是这样的:

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

到目前为止,我们只有两种算法。我必须实施第三个。我有机会重构现有的实现以及编写一些通用代码来为未来的算法制作此功能,可能为每个客户定制。

我的第一个想法是“好吧,这是一种策略模式”。但我看到的问题是两种算法都接收到不同的参数列表(前两个除外)。未来的算法也可以接收不同的参数列表。唯一的“共同点”是投资者名单和其余部分。

我该如何设计它以使界面更简洁? 我想到了

  1. 建立一个包含所有可能参数的接口,并共享它 在所有实现中。
  2. 使用具有所有可能参数的对象作为属性,并将该通用对象用作接口的一部分。一世 将有 3 个参数:投资者列表、剩余对象和“参数”对象。但在这种情况下,我有一个类似的问题。实例化每个对象并填充所需的属性取决于算法(除非我设置了所有属性)。一世 必须使用工厂(或其他东西)来实例化它,使用界面中的所有参数,对吗?我会将参数过多的问题转移到那个“工厂”或其他什么地方。
  3. 使用动态对象而不是静态类型对象。仍然 和以前一样的问题,实例化

我也想过使用访问者模式,但据我所知,如果我有不同的算法供不同的实体使用,比如另一类投资者,就会出现这种情况。所以我认为这不是正确的方法。

到目前为止,最让我信服的是第二个,尽管我对此仍然有些沉默。

有什么想法吗?

谢谢

【问题讨论】:

  • 参数都是同类型的吗?如果是这样,它们可以放在一个列表中。是否可以仅使用 1 个算法迭代参数列表并执行所需的操作?
  • 当前实现有十进制值、整数和枚举。虽然可能有一个字符串
  • 无论算法如何,所有参数都设置了吗?什么决定算法?为什么不在那里新建算法呢?似乎是不必要的额外关卡
  • 一个枚举用于确定算法。到目前为止只有两个,我将添加第三个,我希望将来的算法代码更清晰
  • 看来这个 Round 方法很没有意义。你不能只调用你想要的算法来代替调用这个 Round 方法吗?

标签: c# algorithm oop design-patterns strategy-pattern


【解决方案1】:

策略有不同的实现。当所有替代的具体策略都需要相同的类型签名时,它很简单。但是当具体的实现开始从 Context 请求不同的数据时,我们必须通过放松封装优雅地退后一步(“破坏封装”是策略的已知缺点),我们可以将 Context 传递给策略方法签名或构造函数取决于需要多少。

通过使用接口并将大对象树分解成更小的容器,我们可以限制对大部分上下文状态的访问。

以下代码演示了传递方法参数。

    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 实例。
  • @Gonzalo.-这是真的。在第二次仔细检查代码后,我发现很少有关于策略必要条件的查询。这些算法真的替换吗? Round 方法是否属于它应该在的类? (可能不是这就是它需要这么多参数的原因。)。我们能否从适合所有策略的代码中识别出良好的上下文(上下文不应该在不同的策略上改变状态)。可能需要更多的重构,以便仅隔离策略适用的部分。虽然不确定,因为不知道完整的代码。
  • 是的,算法的标准是不同的——每个算法都是通过数据库中的配置来挑选的,每个算法都有两点区别(我想把它们分成两个不同的策略类,但那是为了将来) - 他们对投资者进行排序的方式(我应该首先选择哪个投资者?)以及我分配多少个单位(我应该给剩下的前一半吗?我应该给最小单位吗?)。但这对我来说是“round”内部实现的一部分
【解决方案2】:

好吧,我可能走错了方向……但是您将参数传递给 all 算法以及实际使用的算法的标识符似乎有点奇怪。理想情况下,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”比四舍五入有更多的逻辑(我已经省略了,但在 cmets 中提到了),所以感觉比它应该的更奇怪
  • 为什么不能更新 Round() 方法签名?您不是已经更新它以在输入末尾添加另一个参数吗?
  • 是的,但我无法更新在另一个 dll 中调用它的位置,不,到目前为止我没有更新它,我有完整的参数列表。我没有在 Round 签名末尾添加新参数
  • 啊啊啊啊....我看到了混乱。我一直在看到“我正在尝试编写一个接口”和“传入一个全局参数对象”——我以为你正在对 Round 这样做。您说的是更改 SomeStaticClass.Algorithm1 对象,而不是 Round() 函数。好吧,是的,你可能已经筋疲力尽了。只要调用 Round() 的人以如此仓促的方式传入参数,并且每个算法都需要不同的参数集?您可以为每个参数集合编写一个类...但它可能比您在原始帖子中得到的更难看。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-05
相关资源
最近更新 更多