【问题标题】:Avoiding coupling with Strategy pattern避免与策略模式耦合
【发布时间】:2011-08-18 22:21:36
【问题描述】:

我正在尝试将策略模式应用于特定情况,但在如何避免将每个具体策略耦合到为其提供数据的上下文对象时遇到了问题。下面是一个模式的简化案例,它以几种不同的方式出现,但应该以类似的方式处理。

我们有一个对象Acquisition,它提供与特定时间框架相关的数据——基本上是使用不同硬件收集的一堆外部数据。由于它包含的数据量,它已经太大了,所以我不想给它任何进一步的责任。我们现在需要获取其中的一些数据,并根据一些配置向硬件发送相应的电压。

所以,想象一下以下(非常简化的)类:

class Acquisition
{
    public Int32 IntegrationTime { get; set; }
    public Double Battery { get; set; }
    public Double Signal { get; set; }
}

interface IAnalogOutputter
{
    double getVoltage(Acquisition acq);
}

class BatteryAnalogOutputter : IAnalogOutputter
{
    double getVoltage(Acquisition acq)
    {
        return acq.Battery;
    }
}

现在,每个具体的策略类都必须与我的 Acquisition 类耦合,这也是最有可能修改的类之一,因为它是我们应用程序的核心。这仍然是对旧设计的改进,旧设计是Acquisition内部 中的一个巨大的 switch 语句。每种类型的数据可能有不同的转换方式(Battery 是简单的 pass-through,其他的就没那么简单了),所以我觉得 Strategy 模式或者类似的应该是要走的路。

我还要注意,在最终实现中,IAnalogOutputter 将是一个抽象类而不是一个接口。这些类将位于用户可配置的列表中并序列化为 XML 文件。该列表必须在运行时可编辑并被记住,因此 Serializable 必须是我们最终解决方案的一部分。以防万一。

如何确保每个实现类都获得工作所需的数据,而不将其与我最重要的类之一联系起来?还是我以完全错误的方式处理这类问题?

【问题讨论】:

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


    【解决方案1】:

    Strategy Pattern 封装了一个通常很复杂的操作/计算。

    你想要返回的电压取决于

    • 配置件
    • 部分采集数据

    所以我会把这些放到另一个类中并传递给策略实现者。

    同样在序列化方面,您没有序列化策略类,可能只有它们的名称或类型名称。


    更新

    好吧,您的实现似乎只需要一个采集数据。这对于策略模式来说有点不寻常——但我不认为它更适合Visitor,所以策略很好。除了实现者需要的配置之外,我将创建一个具有作为属性的类,获取数据(可能从它继承)。

    【讨论】:

    • 澄清一下——采集数据包含大约20条信息,所有这些信息都将被各种电压输出器使用。每个实现将仅使用一个数据,但电压输出器的不同可能实现将有相同数量(20)个。那么我如何从Acquisition 到“另一个类 [to] 将其传递给策略实施者”?不过,关于序列化的好点,我肯定会考虑的。
    【解决方案2】:

    您可以做的一件事是使用工厂方法来构建您的策略。您的个人策略只能在他们的构造函数中接收他们需要的个人数据元素,工厂方法是唯一需要知道如何在给定Acquisition 对象的情况下填写该数据的东西。像这样的:

    public class OutputterFactory
    {
        public static IAnalogOutputter CreateBatteryAnalogOutputter(Acquisition acq)
        {
            return new BatteryANalogOutputter(acq.Battery);
        }
    
    
    
    }
    

    【讨论】:

    • 我也想过这个,但后来好像每条新数据,我都要添加一个属性,一个方法,一个类。虽然它确实将它解耦了一点,但它使 cruft 的数量增加了 3 的幂。但是,我现在想知道是否为此使用单独的方法是最好的选择。
    【解决方案3】:

    好的,我不想在这里给其他人的功劳,但我找到了一个非常适合我的目的的混合解决方案。它完美地序列化,并大大简化了新输出类型的添加。关键是一个单一的界面,IOutputValueProvider。另请注意,此模式处理检索各种存储数据的方式(例如字典而不是参数)是多么容易。

    interface IOutputValueProvider
    {
        Double GetBattery();
        Double GetSignal();
        Int32 GetIntegrationTime();
        Double GetDictionaryValue(String key);
    }
    
    interface IAnalogOutputter
    {
        double getVoltage(IOutputValueProvider provider);
    }
    
    class BatteryAnalogOutputter : IAnalogOutputter
    {
        double getVoltage(IOutputValueProvider provider)
        {
            return provider.GetBattery();
        }
    }
    
    class DictionaryValueOutputter : IAnalogOutputter
    {
        public String DictionaryKey { get; set; }
        public double getVoltage(IOutputValueProvider provider)
        {
            return provider.GetDictionaryValue(DictionaryKey);
        }
    }
    

    那么,我只需要确保Acquisition 实现了接口:

    class Acquisition : IOutputValueProvider
    {
        public Int32 IntegrationTime { get; set; }
        public Double Battery { get; set; }
        public Double Signal { get; set; }
        public Dictionary<String, Double> DictionaryValues;
    
        public double GetBattery() { return Battery;}
        public double GetSignal() { return Signal; }
        public int GetIntegrationTime() { return IntegrationTime; }
        public double GetDictionaryValue(String key) 
        {
            Double d = 0.0;
            return DictionaryValues.TryGetValue(key, out d) ? d : 0.0;
        }
    }
    

    这并不完美,因为现在必须维护一个巨大的接口,并且在Acquisition 中有一些重复的代码,但是更改某些东西影响我的应用程序其他部分的风险要小得多。它还允许我开始子类化Acquisition,而无需更改其中的一些外部部分。我希望这将有助于其他一些处于类似情况的人。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多