Castle Windsor - 传播通过类型化工厂传递的依赖关系
Castle Winsdor - Propagating dependency passed via typed factory
我目前正在熟悉 IoC,并且在使用 IoC 原则重构现有库方面遇到困难。
假设我有三个 classes:Controller、Handler 和 Settings 和 Controller 的类型化工厂。
控制器是库的入口点
public class Controller
{
public Settings Settings { get; }
public Handler Handler { get; }
public Controller(Settings settings, Handler handler)
{
Settings = settings;
Handler = handler;
}
}
Handler 是 Controller
的依赖
public class Handler
{
public Settings Settings { get; }
public Handler(Settings settings)
{
Settings = settings;
}
}
Settings 是一个 class 包含一些库范围的设置'
public class Settings
{
public int Revision { get; set; }
}
IControllerFactory 是类型工厂
public interface IControllerFactory
{
Controller Create(Settings settings);
}
我想初始化一个库。为了简单起见,所有示例代码都在一个 Main 方法中。在实际应用中,Controller class 的消费者无权访问容器。
static void Main(string[] args)
{
//create container and register components
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<Controller>().LifestyleTransient(),
Component.For<Settings>().LifestyleBoundTo<Controller>(),
Component.For<Handler>().LifestyleBoundTo<Controller>()
);
container.Register(
Component.For<IControllerFactory>().AsFactory()
);
//in real application, factory is a dependency of a library consumer class
//which has no access to container
var controllerFactory = container.Resolve<IControllerFactory>();
//create Controller instance with Revision setting set to 100
var settings = new Settings()
{
Revision = 100
};
var controller = controllerFactory.Create(settings);
//check revision value for controller and handler
Console.WriteLine("Controller's setting revision: " + controller.Settings.Revision); //Controller's setting revision: 100
Console.WriteLine("Handler's setting revision: " + controller.Handler.Settings.Revision); //Handler's setting revision: 0
Console.ReadKey();
}
运行 此示例输出以下内容:
Controller's setting revision: 100
Handler's setting revision: 0
如您所见,作为参数传递给工厂的设置实例已正确传递给控制器构造函数,但不会传播到控制器的依赖项(即处理程序构造函数)。我找不到任何关于这是否是预期行为的信息。
如果类型化工厂的参数实际上是不可传播的,在我的情况下你会建议什么方法?创建自定义范围是其中之一,但不符合我的需求,因为它需要访问容器,据我所知,这被认为是一种不好的做法。
static void Main(string[] args)
{
//create container and register components
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<Controller>().LifestyleScoped(),
Component.For<Settings>().LifestyleScoped(),
Component.For<Handler>().LifestyleBoundTo<Controller>()
);
//creating scope means passing container around
using (container.BeginScope())
{
//create instance of controller
var settings = container.Resolve<Settings>();
settings.Revision = 100;
var controller = container.Resolve<Controller>();
//check revision value for controller and handler
Console.WriteLine("Controller's setting revision: " + controller.Settings.Revision); //Controller's setting revision: 100
Console.WriteLine("Handler's setting revision: " + controller.Handler.Settings.Revision); //Handler's setting revision: 100
}
Console.ReadKey();
}
运行 这个例子给出了想要的结果:
Controller's setting revision: 100
Handler's setting revision: 100
传递给工厂的参数不会添加到容器中 - 它们只会用于满足所需服务的依赖性。如果您希望将这些参数传递给其他依赖项,您必须自己负责。
引用容器是不良做法的一般经验法则是正确的。但是,与所有经验法则一样,它也有局限性。
(Settings
的实际可变性从你的问题中不清楚所以我假设你的隐含要求,它可以以未指定的方式变化,是这里的关键驱动因素。但是,回答你的问题如果不正确理解 Settings
)
的真实可变性,"what approach would you suggest" 是困难的
i) 对于您当前的设计,您需要以某种方式增强容器的行为以满足您所解释的要求,因此您必须访问容器。
正如您已经强调的那样,一种解决方案是使用自定义范围。这在控制对象生命周期方面提供了极大的灵活性,但确实以相当危险的方式泄漏了容器引用。为避免这种情况,您可以封装它以防止容器泄漏,您最终会得到一个自定义工厂和一个合作伙伴范围 class(就客户而言,这可能只是一个 IDisposable
).在这种情况下,我将其视为容器基线功能的增强,因此经验法则不适用。
ii) 另一种方法是稍微重新考虑您的设计并引入一些间接引用 Settings
。也就是说,不是 Handler
和 Controller
依赖于 Settings
,它们可以依赖于 SettingsProvider
。 SettingsProvider
然后可以作为单例添加到容器中,并且可以完全独立于容器管理有关如何访问当前适用的 Settings
所需的逻辑。
(有关详细信息,请参阅此 excellent explanation of the benefits of factories in Castle Windsor)
我目前正在熟悉 IoC,并且在使用 IoC 原则重构现有库方面遇到困难。
假设我有三个 classes:Controller、Handler 和 Settings 和 Controller 的类型化工厂。
控制器是库的入口点
public class Controller { public Settings Settings { get; } public Handler Handler { get; } public Controller(Settings settings, Handler handler) { Settings = settings; Handler = handler; } }
Handler 是 Controller
的依赖public class Handler { public Settings Settings { get; } public Handler(Settings settings) { Settings = settings; } }
Settings 是一个 class 包含一些库范围的设置'
public class Settings { public int Revision { get; set; } }
IControllerFactory 是类型工厂
public interface IControllerFactory { Controller Create(Settings settings); }
我想初始化一个库。为了简单起见,所有示例代码都在一个 Main 方法中。在实际应用中,Controller class 的消费者无权访问容器。
static void Main(string[] args)
{
//create container and register components
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<Controller>().LifestyleTransient(),
Component.For<Settings>().LifestyleBoundTo<Controller>(),
Component.For<Handler>().LifestyleBoundTo<Controller>()
);
container.Register(
Component.For<IControllerFactory>().AsFactory()
);
//in real application, factory is a dependency of a library consumer class
//which has no access to container
var controllerFactory = container.Resolve<IControllerFactory>();
//create Controller instance with Revision setting set to 100
var settings = new Settings()
{
Revision = 100
};
var controller = controllerFactory.Create(settings);
//check revision value for controller and handler
Console.WriteLine("Controller's setting revision: " + controller.Settings.Revision); //Controller's setting revision: 100
Console.WriteLine("Handler's setting revision: " + controller.Handler.Settings.Revision); //Handler's setting revision: 0
Console.ReadKey();
}
运行 此示例输出以下内容:
Controller's setting revision: 100
Handler's setting revision: 0
如您所见,作为参数传递给工厂的设置实例已正确传递给控制器构造函数,但不会传播到控制器的依赖项(即处理程序构造函数)。我找不到任何关于这是否是预期行为的信息。
如果类型化工厂的参数实际上是不可传播的,在我的情况下你会建议什么方法?创建自定义范围是其中之一,但不符合我的需求,因为它需要访问容器,据我所知,这被认为是一种不好的做法。
static void Main(string[] args)
{
//create container and register components
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<Controller>().LifestyleScoped(),
Component.For<Settings>().LifestyleScoped(),
Component.For<Handler>().LifestyleBoundTo<Controller>()
);
//creating scope means passing container around
using (container.BeginScope())
{
//create instance of controller
var settings = container.Resolve<Settings>();
settings.Revision = 100;
var controller = container.Resolve<Controller>();
//check revision value for controller and handler
Console.WriteLine("Controller's setting revision: " + controller.Settings.Revision); //Controller's setting revision: 100
Console.WriteLine("Handler's setting revision: " + controller.Handler.Settings.Revision); //Handler's setting revision: 100
}
Console.ReadKey();
}
运行 这个例子给出了想要的结果:
Controller's setting revision: 100
Handler's setting revision: 100
传递给工厂的参数不会添加到容器中 - 它们只会用于满足所需服务的依赖性。如果您希望将这些参数传递给其他依赖项,您必须自己负责。
引用容器是不良做法的一般经验法则是正确的。但是,与所有经验法则一样,它也有局限性。
(Settings
的实际可变性从你的问题中不清楚所以我假设你的隐含要求,它可以以未指定的方式变化,是这里的关键驱动因素。但是,回答你的问题如果不正确理解 Settings
)
i) 对于您当前的设计,您需要以某种方式增强容器的行为以满足您所解释的要求,因此您必须访问容器。
正如您已经强调的那样,一种解决方案是使用自定义范围。这在控制对象生命周期方面提供了极大的灵活性,但确实以相当危险的方式泄漏了容器引用。为避免这种情况,您可以封装它以防止容器泄漏,您最终会得到一个自定义工厂和一个合作伙伴范围 class(就客户而言,这可能只是一个 IDisposable
).在这种情况下,我将其视为容器基线功能的增强,因此经验法则不适用。
ii) 另一种方法是稍微重新考虑您的设计并引入一些间接引用 Settings
。也就是说,不是 Handler
和 Controller
依赖于 Settings
,它们可以依赖于 SettingsProvider
。 SettingsProvider
然后可以作为单例添加到容器中,并且可以完全独立于容器管理有关如何访问当前适用的 Settings
所需的逻辑。
(有关详细信息,请参阅此 excellent explanation of the benefits of factories in Castle Windsor)