views:

93

answers:

2

I am developing a project that calculates various factors for a configuration of components. The configuration is set/changed by the user at runtime. I have a Component base class and all configuration items are derived from it.

The information for each component is retrieved from data storage as and when it is required. So that the storage medium can change I have written a DataInterface class to act as an intermediary.

Currently the storage medium is an Access Database. The DataInterface class thus opens the database and creates query strings to extract the relevant data. The query string will be different for each component.

The problem I have is designing how the call to GetData is made between the component class and the DataInterface class. My solutions have evolved as follows:

1) DataInterface has a public method GetXXXXData() for each component type. (where XXX is component type).

Sensor sensor = new Sensor();
sensor.Data = DataInterface.GetSensorData();

2) DataInterface has a public method GetData(componentType) and switches inside on component type.

Sensor sensor = new Sensor();
sensor.Data = DataInterface.GetData(ComponentType.Sensor);

3) Abstract component base class has virtual method GetData() which is overidden by each derived class. GetData() makes use of the DataInterface class to extract data.

Sensor sensor = new Sensor();
sensor.GetData(); 
//populates Data field internally. Could be called in constructor

For me solution 3 appears to be the most OOD way of doing things. The problem I still have however is that the DataInterface still needs to switch on the type of the caller to determine which query string to use.

I could put this information in each component object but then this couples the components to the storage medium chosen. Not good. Also, the component should not care how the data is stored. It should just call its GetData method and get data back.

Hopefully, that makes sense. What im looking for is a way to implement the above functionality that does not depend on using a switch on type.

I'm still learning how to design architecture so any comments on improvement welcome.

TIA

A: 

First, I agree with the idea of each component object, in it's constructor being responsible for asking for its configuration. In fact, perhaps that's pushed up into the base class constructor. We end up with

DataInterface.GetData( getMyType() );

kind of a call.

Then, you main question, how can we implement GetData( type)?

In effect you want a mapping from a type to a query string, and you don't want to be changing code as new components are added. So how about providing some data-driven approach. A simple external configuration proving that mapping. Then it's just a config change to add more components.

djna
+1  A: 

Actually, solution #3 is the worst because it gives the Sensor class artificial responsibilities. The other two solutions are better in that they encapsulate the data access responsibilities into different classes.

I would suggest the following interfaces and classes.

interface IComponentDataReader 
{
    object GetData();
}

abstract class AbstractComponent
{
    private IComponentDataReader dataReader;

    public AbstractComponent(IComponentDataReader dataReader)
    {
        this.dataReader = dataReader;
    }

    protected object GetData()
    {
        return dataReader.GetData();
    }
}

class Sensor : AbstractComponent
{
    public Sensor(IComponentDataReader dataReader)
        : base(dataReader)
    {
    }

    public void DoSomethingThatRequiresData()
    {
        object data = GetData();

        // do something
    }
}

class SensorDataReader : IComponentDataReader
{
    public object GetData()
    {
        // read your data

        return data;
    }
}

class MyApplication
{
    public static void Main(string[] args)
    {
        Sensor sensor = new Sensor(new SensorDataReader());
        sensor.DoSomethingThatRequiresData();
    }
}

I hope this makes sense. Basically, for good OOD, if you can keep your classes to do only one thing (Single Responsibility Principle) and know only about itself, you will be fine. You must be asking why there is an IComponentDataReader passed to SensorComponent if it should only know about itself. In this case, consider that this is provided to SensorComponent (Dependency Injection) instead of it requesting for it (which would be looking outside its own responsibilities).

jeyoung
So, if I have understood your answer correctly; If i add a new component, say Resistor, then I would also need to add a class called ResistorDataReader?
Kildareflare
Yes. This is correct.If you are into declarative programming, you could even have some kind of automatic dependency injection which would instantiate the correct implementation of `IComponentReader` depending on what component needs it. Personally, I prefer to have control on what gets passed around. After all, you as the developer knows how the solution works and how the different classes collaborate, so ultimately you are responsible for doing the correct wiring.
jeyoung
To clear any mis-understanding, you don't need a one-to-one mapping of component and datareader. You may have a component that rely on the same data as another component. In that case, you would pass the same datareader to both components. I guess you already know that you could have an `MSAccessSensorDataReader` and a `SQLSensorDataReader`, both implementing the `IComponentDataReader`.
jeyoung
Thanks jeyoung. This seems OK. However I see a problem whereby If I change how data is extracted from the Access database I will have to change every different type of MSAccessXXXXXDataReader. This also means I will have DataBase code spread among many objects (the DataReaders) I was hoping to keep it all in one class to aid maintainability. It looks like this is not possible without breakin encapsulation or creating close coupling.
Kildareflare
Behind your `MSAccessXYZDataReader` classes, you are free to have all the SQL code in one big class. The essential is that `IComponentDataReader` removes the need for the no-no switch statements and takes the responsibility of getting data off the component classes.
jeyoung