Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decoration with several interface

Tags:

I am always struggling with decoration + interfaces. Say I have the following 'behavior' interfaces :

interface IFlyable { void Fly();}
interface ISwimmable { void Swim();}

A main interface

interface IMainComponent { void DoSomethingA(); void DoSomethingB();}

A decorator on the main interace

    public class Decorator : IMainComponent
    {
        private readonly IMainComponent decorated;
        [..]

        public virtual void DoSomethingA()
        {
            decorated.DoSomethingA();
        }

        public virtual void DoSomethingB()
        {
            decorated.DoSomethingB();
        }
    }

My issue is how to forward all the interfaces implemented by the decorated object to the decorator. A solution is to make the decorator implementation the interfaces :

    public class Decorator : IMainComponent, IFlyable, ISwimmable
    {
        [..]

        public virtual void Fly()
        {
            ((IFlyable)decorated).Fly();
        }

        public virtual void Swim()
        {
            ((ISwimmable)decorated).Swim();
        }

But I don't like it because :

  1. It may looks like the "Decorator" implement an interface while it is not the case (Cast exception at run time)
  2. This is not scalable, I need to add each new interface (and not forget about this addition)

An other solution is to add "a manual cast" that propagates throw the decoration tree :

    public class Decorator : IMainComponent
    {
        public T GetAs<T>()
            where T : class
        {
            //1. Am I a T ?
            if (this is T)
            {
                return (T)this;
            }

            //2. Maybe am I a Decorator and thus I can try to resolve to be a T
            if (decorated is Decorator)
            {
                return ((Decorator)decorated).GetAs<T>();
            }

            //3. Last chance
            return this.decorated as T;
        }

But the issues are :

  1. The caller can be manipulating the wrapped object after a call to GetAs().
  2. This can lead to confusion/unwanted behaviour if using a method from IMainComponent after a call on GetAs (something like ((IMainComponent)GetAs()).DoSomethingB(); ==> this may call the implementation of the wrapped object, not the full decoration.
  3. The GetAs() method need to be called and exiting code with cast/regular "As" will not work.

How to you approch/resolve this issue ? Is there a pattern addressing this issue ?

PD : my question is for a final C# implementation but maybe the solution is more broad.

like image 455
Toto Avatar asked Mar 14 '19 15:03

Toto


People also ask

What are decorators in design patterns?

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.

What is a decorator method?

Decorator Method is a Structural Design Pattern which allows you to dynamically attach new behaviors to objects without changing their implementation by placing these objects inside the wrapper objects that contains the behaviors.

Where are decorator patterns used?

The Decorator pattern is best when the decorators modify the behavior of the methods in the interface. A decorator can add methods, but added methods don't carry through when you wrap in another decorator.

What are decorators in programming?

Decorators are functions (or classes) that provide enhanced functionality to the original function (or class) without the programmer having to modify their structure.


1 Answers

You will need to create separate decorators for each interface. An alternative is to use a generic interface for your services and a generic decorator. For example:

public interface ICommandService<TCommand>
{
   Task Execute(TCommand command);
}

public class LoggingCommandService<TCommand> : ICommandService<TCommand>
{
  public LoggingCommandService(ICommandService<TCommand> command, ILogger logger)
  {
    ...
  }

  public async Task Execute(TCommand command)
  {
    // log
    await this.command.Execute(command);
    // log
  }
}
like image 73
devdigital Avatar answered Sep 29 '22 02:09

devdigital