【问题标题】:Decoration with several interface多界面装饰
【发布时间】:2019-03-14 15:20:51
【问题描述】:

我总是在装饰 + 界面上苦苦挣扎。假设我有以下“行为”界面:

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. 看起来“装饰器”实现了一个接口,但实际上并非如此(运行时投射异常)
  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# 实现,但也许解决方案更广泛。

【问题讨论】:

  • 你想达到什么目的?有没有现实生活中的例子?
  • 这里decorated的数据类型是什么?它在哪里声明?目前尚不清楚您在问什么。并且装饰器和装饰器之间存在混淆......
  • 为什么要在装饰器中实现接口? MainComponent 应该实现所有接口并且 Decorator 扩展 MainComponent 对象。所以Decorator: MainComponent { override Fly... }
  • @devdigital :我多次遇到这个问题。最新的现实生活是创建一个在不同触发器(ITimedComponent、IFileChangedComponent、IFrequencyComponent 等)上完成其工作的组件。装饰用于在执行作业时记录/在之前/之后存储指标,在作业运行之前执行数据冻结等。
  • @ChetanRanpariya 装饰是一个 IMainComponent 实例。 sn-p没满,我修改了。

标签: c# design-patterns


【解决方案1】:

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

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】:

    我认为您正在转向服务定位器模式 - 这是一种严重的反模式。你有一个服务定位器,如果你依赖一个像许愿井一样的接口:你可以向它提出任何要求。我认为这正是您的GetAs 将您带到的地方。

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

    如果您要求实现,我建议使用依赖注入框架。市场上有很多这样的工具,例如 MEF、Ninject、Unity、Windsor、DryIoC、SimpleInject、LightInjector、Grace、Stashbox ……仅举几例我想到的。

    装饰器的意义完全不同。如果您不只是转发接口调用,而是向其添加一些额外的逻辑(例如重试行为),则使用装饰器。在这种情况下,您仍然会限制自己使用原始接口的方法。

    【讨论】:

      【解决方案3】:

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

      装饰器的用处在于装饰器和被装饰组件的接口相同,并且装饰器在沿装饰器链传递请求之前或之后向方法添加一些额外的功能。

      【讨论】:

        猜你喜欢
        • 2013-06-06
        • 1970-01-01
        • 2021-11-24
        • 2018-10-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-28
        • 1970-01-01
        • 2013-01-16
        相关资源
        最近更新 更多