项目
版本

适配器和装饰器

适配器

适配器模式 将一个服务合同适配(就像包装一样)到另一个。

这篇 入门文章 描述了一个适配器模式的具体示例,以及如何在 Autofac 中使用它。

Autofac 提供了内置的适配器注册,因此你可以注册一组服务,并使它们自动适应不同的接口。

var builder = new ContainerBuilder();

// 注册要适应的服务
builder.RegisterType<SaveCommand>()
       .As<ICommand>()
       .WithMetadata("Name", "Save File");
builder.RegisterType<OpenCommand>()
       .As<ICommand>()
       .WithMetadata("Name", "Open File");

// 然后注册适配器。在这种情况下,ICommand
// 注册使用了一些元数据,所以我们正在
// 适配 Meta<ICommand> 而不是单纯的 ICommand。
builder.RegisterAdapter<Meta<ICommand>, ToolbarButton>(
   cmd => new ToolbarButton(cmd.Value, (string)cmd.Metadata["Name"]));

var container = builder.Build();

// 解决的按钮集合中将包含两个按钮
// 对于每个注册的 ICommand 实例,都会有一个适配的按钮。
var buttons = container.Resolve<IEnumerable<ToolbarButton>>();

装饰器

装饰器模式 与适配器模式类似,其中一个服务“包裹”另一个服务。然而,与适配器相反,装饰器暴露的是它们装饰的 相同服务。使用装饰器的目的是在不改变对象签名的情况下向对象添加功能。

Autofac 提供了内置的装饰器注册,因此你可以注册服务并自动用装饰类包裹它们。

简化语法

从 Autofac 4.9.0 开始,它提供了一种简化装饰器 语法 ,可以作为经典语法(下面所示)的替代方案。它使用起来更简单,比早期机制更具灵活性。

var builder = new ContainerBuilder();

// 注册要装饰的服务。
builder.RegisterType<SaveCommandHandler>()
       .As<ICommandHandler>();
builder.RegisterType<OpenCommandHandler>()
       .As<ICommandHandler>();

// 然后注册装饰器。你可以注册多个
// 装饰器,它们将以你注册它们的顺序应用。在这个例子中,所有ICommandHandler
// 都会被日志和诊断装饰器装饰。
builder.RegisterDecorator<LoggingDecorator, ICommandHandler>();
builder.RegisterDecorator<DiagnosticDecorator, ICommandHandler>();

var container = builder.Build();

// 解决的命令集合将包含两个项
// 其中每一个都将被装饰器包裹。
var handlers = container.Resolve<IEnumerable<ICommandHandler>>();

如果你事先不知道类型,你可以手动指定,而不是使用泛型:

builder.RegisterDecorator(typeof(LoggingDecorator), typeof(ICommandHandler));
builder.RegisterDecorator(typeof(DiagnosticDecorator), typeof(ICommandHandler));

如果你想手动实例化你的装饰器或执行更复杂的装饰器创建,这也是可能的。

builder.RegisterDecorator<ICommandHandler>(
  (context, parameters, instance) => new ComplexDecorator(instance)
);

在 Lambda 中,context 是正在发生的决议的 IComponentContext(因此,如果需要,你可以解决其他东西);parameters 是一个 IEnumerable<Parameter>,其中包含传递的所有参数;而 instance 是正在被装饰的服务实例。请记住,如果存在多个装饰器链,则 instance 可能是一个 装饰器实例 而不是正在被装饰的根/基础事物。

开放泛型支持装饰。

var builder = new ContainerBuilder();

// 注册要装饰的开放泛型。
builder.RegisterGeneric(typeof(CommandHandler<>)
       .As(ICommandHandler<>);

// 然后注册装饰器。你可以注册多个
// 装饰器,它们将以你注册它们的顺序应用。在这个例子中,所有ICommandHandler<T>实例
// 将被日志和诊断装饰器装饰。
builder.RegisterGenericDecorator(typeof(LoggingDecorator<>), typeof(ICommandHandler<>));
builder.RegisterGenericDecorator(typeof(DiagnosticDecorator<>), typeof(ICommandHandler<>));

var container = builder.Build();

// 解决的处理器将被两个装饰器包裹。
var handler = container.Resolve<ICommandHandler<Save>>();

装饰可以有条件。会为注册提供一个上下文对象,允许您决定是否应用装饰器:

// 只有在没有其他装饰器应用时,才将错误处理装饰器添加到命令处理器。
builder.RegisterDecorator<ErrorHandlerDecorator, ICommandHandler>(
  context => !context.AppliedDecorators.Any());
builder.RegisterGenericDecorator(
  typeof(ErrorHandlerDecorator<>),
  typeof(ICommandHandler<>),
  context => !context.AppliedDecorators.Any());

这些 Lambda 中的 context 是一个 IDecoratorContext,其中包含有关已应用装饰器列表、实际正在解决的服务类型等信息。

你可以使用这个上下文在你的装饰器中做出决策,如果需要的话,可以将其注入到装饰器的构造函数参数中。

public class ErrorHandlerDecorator : ICommandHandler
{
  private readonly ICommandHandler _decorated;
  private readonly IDecoratorContext _context;

  public ErrorHandlerDecorator(ICommandHandler decorated, IDecoratorContext context)
  {
    this._decorated = decorated ?? throw new ArgumentNullException(nameof(decorated));
    this._context = context ?? throw new ArgumentNullException(nameof(context));
  }

  public void HandleCommand(Command command)
  {
    if(this._context.ImplementationType.GetCustomAttribute<SkipHandlingAttribute>() != null)
    {
      // 不处理错误运行命令
    }
    else
    {
      // 添加特殊的错误处理逻辑
    }
  }
}

不能为装饰器指定生命周期作用域。 装饰器的生命周期与其所装饰的事物的生命周期相关联。服务及其所有装饰器都在同一时间销毁。如果你装饰一个单例,所有装饰器也将是单例。如果你装饰一个按请求实例化的东西(例如,在 Web 应用程序中),装饰器也会在整个请求期间存在。

经典语法

自 Autofac 2.4 起,经典语法就已经存在,并且至今仍在工作。它比新语法更复杂,但如果你有一些现有的使用它的代码,该代码将继续有效。

这篇 文章 详细介绍了 Autofac 中装饰器的工作原理。

var builder = new ContainerBuilder();

// 注册要装饰的服务。你必须
// 通过名称注册它们,而不是作为<ICommandHandler>(),
// 这样装饰器就可以是作为<ICommandHandler>()的注册。
builder.RegisterType<SaveCommandHandler>()
       .Named<ICommandHandler>("handler");
builder.RegisterType<OpenCommandHandler>()
       .Named<ICommandHandler>("handler");

// 然后注册装饰器。装饰器使用命名注册来获取要包裹的项目。
builder.RegisterDecorator<ICommandHandler>(
    (c, inner) => new CommandHandlerDecorator(inner),
    fromKey: "handler");

var container = builder.Build();

// 解决的命令集合将包含两个项
// 其中每一个都将被 CommandHandlerDecorator 包裹。
var handlers = container.Resolve<IEnumerable<ICommandHandler>>();

你也可以使用开放的装饰器注册。

var builder = new ContainerBuilder();

// 注册带有名称的开放泛型,以便装饰器可以使用它。
builder.RegisterGeneric(typeof(CommandHandler<>))
       .Named("handler", typeof(ICommandHandler<>));

// 注册通用装饰器,以便它可以包裹解析的命名泛型。
builder.RegisterGenericDecorator(
        typeof(CommandHandlerDecorator<>),
        typeof(ICommandHandler<>),
        fromKey: "handler");

var container = builder.Build();

// 然后你可以解决封闭的泛型,它们将被你的装饰器包装。
var mailHandlers = container.Resolve<IEnumerable<ICommandHandler<EmailCommand>>>();

如果你在一个 WCF 服务实现类上使用装饰器,有关 WCF 集成的一些特殊注意事项

在本文档中