tags:

views:

282

answers:

7

Suppose we have an object that represents the configuration of a piece of hardware. For the sake of argument, a temperature controller (TempController). It contains one property, the setpoint temperature.

I need to save this configuration to a file for use in some other device. The file format (FormatA) is set in stone. I don't want the TempController object to know about the file format... it's just not relevant to that object. So I make another object, "FormatAExporter", that transforms the TempController into the desired output.

A year later we make a new temperature controller, let's call it "AdvancedTempController", that not only has a setpoint but also has rate control, meaning one or two more properties. A new file format is also invented to store those properties... let's call it FormatB.

Both file formats are capable of representing both devices ( assume AdvancedTempController has reasonable defaults if it lacks settings ).

So here is the problem: Without using 'isa' or some other "cheating" way to figure out what type of object I have, how can FormatBExporter handle both cases?

My first instinct is to have a method in each temperature controller that can provide a customer exporter for that class, e.g., TempController.getExporter() and AdvancedTempController.getExporter(). This doesn't support multiple file formats well.

The only other approach that springs to mind is to have a method in each temperature controller that returns a list of properties and their values, and then the formatter can decide how to output those. It'd work, but that seems convoluted.

UPDATE: Upon further work, that latter approach doesn't really work well. If all your types are simple it might, but if your properties are Objects then you end up just pushing the problem down a level... you are forced to return a pair of String,Object values, and the exporter will have to know what the Objects actually are to make use of them. So it just pushes the problem to another level.

Are there any suggestions for how I might keep this flexible?

A: 

I'd have the "temp controller", through a getState method, return a map (e.g. in Python a dict, in Javascript an object, in C++ a std::map or std::hashmap, etc, etc) of its properties and current values -- what's convoluted about it?! Could hardly be simpler, it's totally extensible, and totally decoupled from the use it's put to (displaying, serializing, &c).

Alex Martelli
A: 

If FormatBExporter takes an AdvancedTempController, then you can make a bridge class that makes TempController conform to AdvancedTempController. You may need to add some sort of getFormat() function to AdvancedTempController though.

For example:

FormatBExporter exporterB;
TempController tempController;
AdvancedTempController bridged = TempToAdvancedTempBridge(tempController);

exporterB.export(bridged);

There is also the option of using a key-to-value mapping scheme. FormatAExporter exports/imports a value for key "setpoint". FormatBExporter exports/imports a values for keys "setpoint" and "ratecontrol". This way, old FormatAExporter can still read the new file format (it just ignores "ratecontrol") and FormatBExporter can read the old file format (if "ratecontrol" is missing, it uses a default).

Tom Dalling
A: 

Well, a lot of that depends on the file formats you're talking about.

If they're based on key/value combinations (including nested ones, like xml), then having some kind of intermediate memory object that's loosely typed that can be thrown at the appropriate file format writer is a good way to do it.

If not, then you've got a scenario where you've got four combinations of objects and file formats, with custom logic for each scenario. In that case, it may not be possible to have a single representation for each file format that can deal with either controller. In other words, if you can't generalize the file format writer, you can't generalize it.

I don't really like the idea of the controllers having exporters - I'm just not a fan of objects knowing about storage mechanisms and whatnot (they may know about the concept of storage, and have a specific instance given to them via some DI mechanism). But I think you're in agreement with that, and for pretty much the same reasons.

kyoryu
A: 

In the OO model, the object methods as a collective is the controller. It's more useful to separate your program in to the M and V and not so much the C if you're programming using OO.

+4  A: 

What you can do is let the TempControllers be responsible for persisting itself using a generic archiver.

class TempController 
{
    private Temperature _setPoint;
    public Temperature SetPoint { get; set;}

    public ImportFrom(Archive archive)
    {
        SetPoint = archive.Read("SetPoint");
    }
    public ExportTo(Archive archive)

    {
        archive.Write("SetPoint", SetPoint);
    }
}

class AdvancedTempController
{
    private Temperature _setPoint;
    private Rate _rateControl;
    public Temperature SetPoint { get; set;}
    public Rate RateControl { get; set;}

    public ImportFrom(Archive archive)
    {
        SetPoint = archive.Read("SetPoint");
        RateControl = archive.ReadWithDefault("RateControl", Rate.Zero);
    }

    public ExportTo(Archive archive)
    {
        archive.Write("SetPoint", SetPoint);
        archive.Write("RateControl", RateControl);
    }
}

By keeping it this way, the controllers do not care how the actual values are stored but you are still keeping the internals of the object well encapsulated.

Now you can define an abstract Archive class that all archive classes can implement.

abstract class Archive
{
    public abstract object Read(string key);
    public abstract object ReadWithDefault(string key, object defaultValue);
    public abstract void Write(string key);
}

FormatA archiver can do it one way, and FormatB archive can do it another.

class FormatAArchive : Archive
{
    public object Read(string key)
    {
        // read stuff 
    }

    public object ReadWithDefault(string key, object defaultValue)
    {
        // if store contains key, read stuff
        // else return default value
    }

    public void Write(string key)
    {
        // write stuff
    }
}

class FormatBArchive : Archive
{
    public object Read(string key)
    {
        // read stuff
    }

    public object ReadWithDefault(string key, object defaultValue)
    {
        // if store contains key, read stuff
        // else return default value
    }

    public void Write(string key)
    {
        // write stuff
    }
}

You can add in another Controller type and pass it whatever formatter. You can also create another formatter and pass it to whichever controller.

jop
This also make the code more testable because it because much easier to mock the collaborators.
Michaël Larouche
It's a touch more elaborate than I had planned, but I like it. It provides the flexibility of returning all the properties in a list, but while preserving type information across the interface. And it definitely helps testability.
Chris Arguin
+1  A: 

In C# or other languages that support this you can do this:

class TempController {
    int SetPoint;
}
class AdvancedTempController : TempController {
    int Rate;
}

class FormatAExporter {
    void Export(TempController tc) {
      Write(tc.SetPoint);
    }
}

class FormatBExporter {
    void Export(TempController tc) {
      if (tc is AdvancedTempController) {
          Write((tc as AdvancedTempController).Rate);
      }
      Write(tc.SetPoint);
    }
}
Ruud v A
Granted, this is the easiest way in the short term. But tou used 'is' and 'as'. Now you have to maintain FormatBExporter every time to handle all the various types. When taken to the extreme, you'll have a forest of 'if' statements to figure out what the type is, and the whole point of these types was to do this sort of thing themselves.
Chris Arguin
A: 

I guess this is the where the Factory method pattern would apply

LenW