用温莎城堡装饰 collection 中解决的每个项目

Decorate each item resolved in a collection with Castle Windsor

假设我有一个包含多个基本实现的接口:

interface IPrinter
{
    void Print();
}

class Printer1 : IPrinter
{
    public void Print()
    {
        Console.WriteLine(GetType());
    }
}

class Printer2 : IPrinter
{
    public void Print()
    {
        Console.WriteLine(GetType());
    }
}

我还有一个装饰器实现:

class ModifyingPrinter : IPrinter
{
    private readonly IPrinter _printer;

    public ModifyingPrinter(IPrinter printer)
    {
        _printer = printer;
    }

    public void Print()
    {
        Console.Write("Modified ");
        _printer.Print();
    }
}

最后,我有一个需要 collection 接口的服务:

class Service
{
    private readonly IEnumerable<IPrinter> _printers;
    public Service(IEnumerable<IPrinter> printers)
    {
        _printer = printer;
    }

    public void Print()
    {
        foreach (var printer in _printer)
        {
            printer.Print();
        }
    }
}

我想要的是 Service 在 collection 中有 Printer1Printer2,每个都用 ModifyingPrinter 装饰,这样输出Service.Print() 的是:

Modified Printer1
Modified Printer2

我尝试按如下方式注册 类:

var container = new WindsorContainer();

container.Kernel.Resolver.AddSubResolver(
    new CollectionResolver(container.Kernel, true));

container.Register(Component.For<IPrinter>()
                            .ImplementedBy<ModifyingPrinter>()
                            .LifestyleTransient());
container.Register(Component.For<IPrinter>()
                            .ImplementedBy<Printer1>());
container.Register(Component.For<IPrinter>()
                            .ImplementedBy<Printer2>());

container.Register(Component.For<Service>());

但这会导致以下输出:

Modified Printer1
Printer1
Printer2

似乎 Windsor 的 CollectionResolver 提供了 IPrinter 的所有实现中的每一个(这是可以理解的)而不是认识到 ModifyingPrinter 是一个装饰器并使用它来包装其他两个实现。

我的问题:有没有办法注册这些 类 以便 collection 只包含修饰的基础实现?

我在另一个 post 某处读到我可以创建一个拦截器并向拦截器注册 Printer1Printer2,但我想保留装饰器模式,如果我可以。

我看到有两种方法可以达到预期的效果:

  • 使用 decorator 并在装饰行为中包装 Print 方法。这感觉像是一种合乎逻辑的行为,因为您可能不想创建一个 IPrinter 没有预期行为而是装饰行为的行为。
  • 使用 factory method 通过在预期打印机周围新建装饰打印机的方法来解析您的 IPrinters。我担心这可能有点复杂,因为如果你想从基于约定的配置中受益,你将不得不过滤掉装饰器。但它应该可以满足您的需求

事实证明,Castle.Windsor 开箱即用无法满足我的要求。我尝试创建一个新的解析器来处理这种情况,但没有取得太大进展。

最后,我重组为使用真正的工厂来装饰类。

class PrinterModifierFactory : IPrinterModifierFactory
{
    private Func<IPrinter, IPrinter> _decorate;
    private IEnumerable<IPrinter> _printers;

    public PrinterModifierFactory(Func<IPrinter, IPrinter> decorate,
                                  IEnumerable<IPrinter> printers)
    {
        _decorate = decorate;
        _printers = printers;
    }

    public IEnumerable<IPrinter> GetAll()
    {
        return _printers.Select(_decorate);
    }
}

然后我修改了我的服务来简单地接收这个工厂的实例(当然是通过接口)而不是 IEnumerable<IPrinter>

class Service
{
    private readonly IEnumerable<IPrinter> _printers;
    private IPrinterModifierFactory factory;

    public Service(IPrinterModifierFactory factory)
    {
        _factory = factory;
    }

    public void Print()
    {
        _Initialize();

        foreach (var printer in _printers)
        {
            printer.Print();
        }
    }

    void _Initialize()
    {
        if (_printers != null) return;

        _printers = factory.GetAll().ToList();
    }
}

(为简洁起见,我省略了 null 检查等细节。)

终于,在安装程序中,我可以注册工厂和装饰方法了。

container.Register(Component.For<IPrinterModifierFactory>()
                            .ImplementedBy<PrinterModifierFactory>());
Func<IPrinter, IPrinter> decorator = p => new ModifyingPrinter(p);
container.Register(Component.For<Func<IPrinter, IPrinter>>()
                            .Instance(decorator));

答案是在我的架构中更明确一点。