适配器和装饰器
适配器
适配器模式 将一个服务合同适配(就像包装一样)到另一个。
这篇 入门文章 描述了一个适配器模式的具体示例,以及如何在 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 集成的一些特殊注意事项。