Avoiding coupling with Strategy pattern

2019-04-29 15:41发布

I am attempting to apply the Strategy pattern to a particular situation, but am having an issue with how to avoid coupling each concrete strategy to the context object providing data for it. The following is a simplified case of a pattern that occurs a few different ways, but should be handled in a similar way.

We have an object Acquisition that provides data relevant to a specific frame of time - basically a bunch of external data collected using different pieces of hardware. It's already too large because of the amount of data it contains, so I don't want to give it any further responsibility. We now need to take some of this data, and based on some configuration send a corresponding voltage to a piece of hardware.

So, imagine the following (much simplified) classes:

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

Now, every concrete strategy class has to be coupled to my Acquisition class, which is also one of the most likely classes to be modified since it's core to our application. This is still an improvement over the old design, which was a giant switch statement inside the Acquisition class. Each type of data may have a different conversion method (while Battery is a simple pass-through, others are not at all that simple), so I feel Strategy pattern or similar should be the way to go.

I will also note that in the final implementation, IAnalogOutputter would be an abstract class instead of an interface. These classes will be in a list that is configurable by the user and serialized to an XML file. The list must be editable at runtime and remembered, so Serializable must be part of our final solution. In case it makes a difference.

How can I ensure each implementation class gets the data it needs to work, without tying it to one of my most important classes? Or am I approaching this sort of problem in the completely wrong manner?

3条回答
Emotional °昔
2楼-- · 2019-04-29 15:56

Ok, I hate to not give someone else the credit here, but I found a hybrid solution that works very well for my purposes. It serializes perfectly, and greatly simplifies the addition of new output types. The key was a single interface, IOutputValueProvider. Also note how easily this pattern handles the retrieval of varying ways of storing the data (such as a Dictionary instead of a parameter).

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

So then, I just need to ensure Acquisition implements the interface:

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

This isn't perfect, since now there's a gigantic interface that must be maintained and some duplicate code in Acquisition, but there's a heck of a lot less risk of something being changed affecting the other parts of my application. It also allows me to start subclassing Acquisition without having to change some of these external pieces. I hope this will help some others in similar situations.

查看更多
等我变得足够好
3楼-- · 2019-04-29 16:13

Strategy Pattern encapsulates a - usually complex - operation/calculation.

The voltage you want to return is dependent on

  • pieces of configuration
  • Some of the acquisition data

So I would put these into another class and pass it to strategy implementors.

Also in terms of serialisation, you do not have serialise the strategy classes, perhaps only their name or type name.


UPDATE

Well, it seems that your implementations need only one piece of the acquisition data. That is a bit unusual for a strategy pattern - but I do not believe it fits Visitor better so strategy is fine. I would create a class which has as property, acquisition data (perhaps inherits from it) in addition to configuration that implementors need.

查看更多
一纸荒年 Trace。
4楼-- · 2019-04-29 16:22

One thing you could do is use factory methods to construct your Strategies. Your individual strategies can take in their constructor only the individual data elements they need, and the factory method is the only thing that needs to know how to fill in that data given an Acquisition object. Something like this:

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



}
查看更多
登录 后发表回答