Decoration with several interface

泄露秘密 提交于 2021-02-06 11:54:35

问题


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.


回答1:


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



回答2:


I think you are steering right into the service locator pattern - which is a severe anti-pattern. You have a service locator, if you depend on an interface that acts like a wishing well: you can ask it for anything. I think that this is exactly where your GetAs is getting you to.

Service locator is considered an anti-pattern because it hides the dependencies that a class has. Instead, you only see the service locator as single dependency but you don't immediately see what dependencies are going to be called.

If you ask for an implementation, I would recommend to use a dependency injection framework. There are plenty of them on the market like MEF, Ninject, Unity, Windsor, DryIoC, SimpleInject, LightInjector, Grace, Stashbox, ... just to name a few that come to my mind.

The point of a decorator is something completely different. A decorator is used if you do NOT just forward the interface calls, but add some extra logic (such as a retry behaviour) to it. In that case, you would however still limit yourself to the methods of the original interface.




回答3:


The Decorator Pattern is not intended for adding new methods to the decorated object. This is what you are trying to do, and it's impossible to do elegantly in a statically-typed language.

What Decorator is useful for is where the interface to the decorators and the decorated component is the same, and the decorator adds a bit of extra functionality to the method either before or after passing the request along the decorator chain.



来源:https://stackoverflow.com/questions/55166176/decoration-with-several-interface

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!