具有多个界面的装饰

时间:2019-03-14 15:20:51

标签: c# design-patterns

我一直在努力装饰+接口。说我有以下“行为”界面:

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

主界面

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

主界面上的装饰器

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

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

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

我的问题是如何将装饰对象实现的所有接口转发给装饰器。一种解决方案是使装饰器实现接口:

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

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

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

但是我不喜欢它,因为:

  1. 情况并非如此(运行时的Cast异常),好像“装饰器”实现了一个接口
  2. 这是不可扩展的,我需要添加每个新接口(不要忘记此添加)

另一种解决方案是添加“手动投射”以传播装饰树:

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

但是问题是:

  1. 调用GetAs()之后,调用者可以操纵包装的对象。
  2. 如果在调用GetAs之后使用IMainComponent中的方法,则可能导致混乱/不必要的行为(类似((IMainComponent)GetAs())。DoSomethingB(); ==>这可能会调用包装对象的实现) ,而不是完整的装饰。
  3. 需要调用GetAs()方法,并且退出带有强制转换/常规“ As”的代码将无效。

您如何解决/解决此问题?是否有解决此问题的模式?

PD:我的问题是最终的C#实现,但解决方案可能更广泛。

3 个答案:

答案 0 :(得分:3)

您将需要为每个接口创建单独的装饰器。一种替代方法是为您的服务和通用装饰器使用通用接口。例如:

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

答案 1 :(得分:1)

我认为您正在转向服务定位器模式-这是一个严重的反模式。您有一个服务定位器,如果您依赖一个功能如愿的界面:您可以要求任何东西。我认为这正是您的GetAs带您去的地方。

服务定位器被认为是反模式,因为它隐藏了类所具有的依赖关系。相反,您仅将服务定位器视为单个依赖项,但没有立即看到将要调用的依赖项。

如果您要求实现,我建议使用依赖项注入框架。市场上有很多它们,例如MEF,Ninject,Unity,Windsor,DryIoC,SimpleInject,LightInjector,Grace,Stashbox,...仅是我想起的几个。

装饰器的要点完全不同。如果您不仅转发接口调用,还向它添加一些额外的逻辑(例如重试行为),则使用装饰器。但是,在那种情况下,您仍然将自己局限于原始接口的方法。

答案 2 :(得分:0)

装饰器模式不适用于向装饰对象添加新方法。这就是您要尝试做的事情,而且不可能用静态类型的语言优雅地做。

Decorator有用的地方是装饰器和装饰组件的接口相同,并且装饰器在将请求沿着装饰器链传递之前或之后都向该方法添加了一些额外的功能。