Question

I have a class hierarchy responsible for parsing or mapping one model (or message) to another. It has non-trivial logic.

internal interface IMessageParser<T, K>
    where T : class
    where K : class
{
    K Serialize(T originalMessage);
    T Deserialize(K concreteMessage);
}

internal abstract class OriginalToConcreteMessageParser : IMessageParser<OriginalMessage, ConcreteMessage>
{
    // some private methods that do stuff and are called in the Serialize() method

    public virtual ConcreteMessage Serialize(OriginalMessage originalMessage)
    {
        // some stuff
    }
}

There are 21 of these concrete parsers:

internal sealed class OriginalToConcreteMessageParserFooMessageParser : OriginalToConcreteMessageParser 
{
}

internal sealed class OriginalToConcreteMessageParserBarMessageParser : OriginalToConcreteMessageParser 
{
}

I want to add a new private method to OriginalToConcreteMessageParser and call it in Serialize(). Let's call this method Baz().

I could create OriginalToConcreteBazMessageParser and make all 21 concrete implementations inherit from this but I would prefer not to have to do this.

The functionality that Baz() provides is definitely at the abstraction level of OriginalToConcreteMessageParser.

In summary, I want to inject a method into OriginalToConcreteMessageParser and call it in Serialize() without touching OriginalToConcreteMessageParser.

Was it helpful?

Solution

I think that you could try some implementation of the decorator pattern, or maybe the strategy pattern

The decorator, has this motivation, that more or less is the same that you have:

As an example, consider a window in a windowing system. To allow scrolling of the window's contents, we may wish to add horizontal or vertical scrollbars to it, as appropriate. Assume windows are represented by instances of the Window class, and assume this class has no functionality for adding scrollbars. We could create a subclass ScrollingWindow that provides them, or we could create a ScrollingWindowDecorator that adds this functionality to existing Window objects. At this point, either solution would be fine. Now let's assume we also desire the ability to add borders to our windows. Again, our original Window class has no support. The ScrollingWindow subclass now poses a problem, because it has effectively created a new kind of window. If we wish to add border support to all windows, we must create subclasses WindowWithBorder and ScrollingWindowWithBorder. Obviously, this problem gets worse with every new feature to be added. For the decorator solution, we simply create a new BorderedWindowDecorator—at runtime, we can decorate existing windows with the ScrollingWindowDecorator or the BorderedWindowDecorator or both, as we see fit.

but probably it will be harder to implement than strategy, and probably too powerful for what you actually need. Decorator is good when a child class will merge the functionality of one, two, or more classes, but using the exact interface as it if where just one class.

With strategy, you can easily switch specific behaviors of a class. Is good when the only think that changes is a function, and the behavior is not usually composed,, but simply different between diferent implementations. Lets say that all the classes have a common behavior, but in the moment of the serialization, the can perform some slightly different operations. How to handle it? well, you make your IMessageParser capable of receive a parsing strategy (an object implementing an interfaz with probably just a function, so all the code you was thinking of putting in BAZ() will be in the strategy object ). And in every concrete class, if the strategy is present, the serialize function uses it. If the strategy is null, the concrete class just use the default behavior.

This is good since know, you want to use that Baz() function to add some functionality to your serialize function, but only in some cases, and this does the trick. And, also in the future, it allows you to add some further behavior to perform during the serialization, just creating new strategy objects.

I would use strategy. You create a SerializeStrategy interface, with a execute method. And then one or more concrete classes implementing that interface. Then you define a setStrategy method in the IMessageParser interface, and implement it in the base class, OriginalToConcreteMessageParser or any other at that level, and save there the strategy object. In the child classes just check if there is a strategy to use.

If you read the pattern carefully, and you take care of having all the participants as decoupled as you can, you can build a SOLID model, and easy to maintain application.

as we can read in the same link above:

This allows better decoupling between the behavior and the class that uses the behavior. The behavior can be changed without breaking the classes that use it, and the classes can switch between behaviors by changing the specific implementation used without requiring any significant code changes. Behaviors can also be changed at run-time as well as at design-time. For instance, a car object’s brake behavior can be changed from BrakeWithABS() to Brake() by changing the brakeBehavior member to: brakeBehavior = new Brake(); This gives greater flexibility in design and is in harmony with the Open/closed principle (OCP)

OTHER TIPS

You could use a delegate for that, but you'd obviously have to change the method signature:

internal abstract class OriginalToConcreteMessageParser : IMessageParser<OriginalMessage, ConcreteMessage>
{
    public virtual ConcreteMessage Serialize(OriginalMessage originalMessage, Func<OriginalMessage, ConcreteMessage> baz)
    {
        return baz(originalMessage);
    }
}

You could optionally add overloads of Serialize to your concrete classes that inject the Baz method:

OriginalToConcreteMessageParserFooMessageParser:

internal sealed class OriginalToConcreteMessageParserFooMessageParser : OriginalToConcreteMessageParser
{
    public ConcreteMessage Serialize(OriginalMessage originalMessage)
    {
        Func<OriginalMessage, ConcreteMessage> baz = message =>
            {
                ConcreteMessage foo = ToFoo(message);

                return foo;
            };

        return base.Serialize(originalMessage, baz);
    }
}

OriginalToConcreteMessageParserBarMessageParser:

internal sealed class OriginalToConcreteMessageParserBarMessageParser : OriginalToConcreteMessageParser
{
    public ConcreteMessage Serialize(OriginalMessage originalMessage)
    {
        Func<OriginalMessage, ConcreteMessage> baz = message =>
        {
            ConcreteMessage bar = ToBar(message);

            return bar;
        };

        return base.Serialize(originalMessage, baz);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top