使用 Castle Windsor 在 Service Class 的构造函数中使用参数化构造函数初始化 class

Initialise a class with a parameterized constructor in the constructor of a Service Class using Castle Windsor

请注意,我已经更改了问题中的代码。

请查看下面的服务器端代码(WCF 服务):

using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;


namespace WcfService1
{
    public class WindsorInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                Component.For<IGreeting, Greeting>(),
                Component.For<IGreetingService, GreetingService>());
        }
    }

    public interface ILanguage
    {
        string SayHello();
    }

    public class Spanish : ILanguage
    {
        public string SayHello()
        {
            return "Hola";
        }
    }

    public interface IGreeting
    {
        string SayHello();
    }

    public class Greeting: IGreeting
    {
        ILanguage Language;

        public Greeting (ILanguage language)
        {
            Language = language;
        }

        public string SayHello()
        {
            return Language.SayHello();
        }
    }

    public interface IGreetingFactory
    {
        IGreeting Create(ILanguage Language);
    }

    [ServiceContract]
    public interface IGreetingService
    {
        [OperationContract]
        string SayHello(string strLanguage);
    }

    public class GreetingService : IGreetingService
    {
        private readonly IGreetingFactory greetingFactory;
        private IGreeting greeting;

        public GreetingService()
        {
        }

        public GreetingService(IGreetingFactory greetingFactory)
        {
            // store the factory until we need it
            this.greetingFactory = greetingFactory;
        }

        public string SayHello (string strLanguage)
        {
            if (strLanguage == "S")
            {
                ILanguage Language = new Spanish();
                Language = new Spanish();
                greeting = new Greeting(Language);
            }
            return greeting.SayHello();
        }

    }
}

和下面的客户端代码:

ServiceReference1.GreetingServiceClient s1 = new ServiceReference1.GreetingServiceClient();
            string greeting = s1.SayHello("S");

ServiceReference1.GreetingServiceClient 是服务参考。

代码按我预期的方式工作,即 Castle Windsor 允许我将 Greeting 注入到服务的构造函数中。但是,Greeting class 本身有一个参数化的构造函数(它需要一种语言)。在上面的代码中,我必须在服务的 Say Hello 方法中初始化问候语(使用一种语言)。如何在服务的构造函数中初始化问候语(使用语言)?

提供 运行 时间、用户驱动或其他动态依赖项的一种主要方法*是使用 factories 创建您的对象。 Castle Windsor 提供了几种不同的工具来帮助解决这个问题,或者您可以使用内核并自己实现一个工厂。

Windsor 的设施允许您提供 delegate-based 工厂,它们只是创建对象的方法。您可以在此处使用它,但您会失去创建内容的灵活性(如果您要将 ICalculator 的实现替换为其他 class,则必须更新此方法)。

为了获得最大的灵活性,您需要使用温莎的 interface-based 工厂。有了这些,您就可以提供工厂接口,然后 Windsor 将自动生成它的实现。

让我们以上面代码的简化版本为例。如果你刚刚有这个对象:

public class Calculator : ICalculator
{
    string Operator;

    public Calculator(string operator)
    {
        Operator=operator;
    }
}

并且您想在创建对象时传递 operator,您可以像这样定义一个工厂:

public interface ICalculatorFactory
{
    ICalculator Create(string operator);
}

然后您将在安装程序中注册它:

kernel.Register(Component.For<ICalulatorFactory>().AsFactory());

现在,只要您想在任何地方使用计算器,就可以为它注入一个工厂,然后只需调用 Create:

public class CalculatorUseExample
{
    private readonly ICalculator addCalculator;
    private readonly ICalculator subCalculator;

    public CalculatorUseExample(ICalculatorFactory calculatorFactory)
    {
        addCalculator = calculatorFactory.Create("+");
        subCalculator = calculatorFactory.Create("-");
    }
}

请注意,operator 参数的名称很重要;默认情况下(您可以根据需要更改此设置),Windsor 按名称匹配参数。

如果我们将您的 CalculatorService class 重新添加到组合中,您可以使用相同的模式:

public interface ICalculatorServiceFactory
{
    ICalculatorService Create(string operator);
}

public class CalculatorService : ICalculatorService
{
    private readonly ICalculator Calculator;

    public CalculatorService(string operator, ICalculatorFactory calculatorFactory)
    {
        Calculator=calculatorFactory.Create(operator);   
    }
}

但我不太喜欢那样,因为服务为什么要关心运营商是什么?这是计算器的细节。相反,将工厂更改为仅接受 ICalculator 并将对象组合在一起,您正在创建此服务:

public interface ICalculatorServiceFactory
{
    ICalculatorService Create(ICalculator calculator);
}

public class CalculatorService : ICalculatorService
{
    private readonly ICalculator Calculator;

    public CalculatorService(ICalculator calculator)
    {
        Calculator=calculator;
    }
}

public class CalculatorServiceUseExample
{
    public CalculatorServiceUseExample(ICalculatorServiceFactory calculatorServiceFactory, ICalculatorFactory calculatorFactory)
    {
        var addCalculator = calculatorFactory.Create("+");
        var service = calculatorServiceFactory.Create(addCalculator);

        // TODO: use the service
    }
}

使用这种模式有优点也有缺点,我在 my answer here 中讨论过。一些优点是您 可以 保护自己免受未来更改的影响并避免服务定位器模式。缺点包括接口对象的激增和工厂的潜在病毒式使用(参见我上面的第一个解决方案,我们不得不创建另一个工厂)。

* 当然还有其他的,这就是我解决这种特殊情况的方法,因为对我来说,它表明了意图,并且是代码读者最容易发现的。


根据您对 WCF 的编辑以及我所了解的您正在尝试做的事情,我会像这样实施服务合同:

public class CalculatorService : ICalculatorService
{
    private readonly ICalculatorFactory calculatorFactory;
    private ICalculator calculator;

    public CalculatorService(ICalculatorFactory calculatorFactory)
    {
        // store the factory until we need it
        this.calculatorFactory = calculatorFactory;
    }

    public void ChangeCalculatorServiceClient(string operator)
    {
        // A new operator, we'll need a new calculator
        calculator = calculatorFactory.Create(operator);
    }
}

好吧,您再次更改了问题以包含另一个问题;现在您想根据参数实例化不同的类型。您可以并且仍然应该为此使用工厂,这就是我的做法:

using Castle.Facilities.TypedFactory;

public class WindsorInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Component.For<IGreeting, Greeting>(),
            Component.For<IGreetingFactory>().AsFactory(),
            Component.For<IGreetingService, GreetingService>(),
            Component.For<ILanguageFactory, LanguageFactory>());
    }
}

public interface ILanguageFactory
{
    ILanguage Create(string language);
}

public class LanguageFactory : ILanguageFactory
{
    private readonly IKernel kernel;

    public LanguageFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public ILanguage Create(string language)
    {
        switch (language)
        {
            case "S":
                return kernel.Resolve<Spanish>();
            default:
                throw new ArgumentException();
        }
    }
}

public class GreetingService : IGreetingService
{
    private readonly IGreetingFactory greetingFactory;
    private readonly ILanguageFactory languageFactory;
    private IGreeting greeting;

    public GreetingService(IGreetingFactory greetingFactory, ILanguageFactory languageFactory)
    {
        // store the factory until we need it
        this.greetingFactory = greetingFactory;
    }

    public string SayHello (string strLanguage)
    {
        var language = languageFactory.Create(strLanguage);
        greeting = greetingFactory.Create(language);
        return greeting.SayHello();
    }
}