除了Reza在他的回答中描述的方法,还有另一种方法。这篇文章最初是发布在我的博客上的,但由于Stack Overflow是我们大多数人获取信息的主要来源,因此在Reza的答案下面链接的博客文章很容易被忽略。
接下来就是解决方案。这个解决方案基于本地工厂模式。
我们将从一个表单工厂开始。
public interface IFormFactory
{
Form1 CreateForm1();
Form2 CreateForm2();
}
public class FormFactory : IFormFactory
{
static IFormFactory _provider;
public static void SetProvider( IFormFactory provider )
{
_provider = provider;
}
public Form1 CreateForm1()
{
return _provider.CreateForm1();
}
public Form2 CreateForm2()
{
return _provider.CreateForm2();
}
}
从现在开始,这个工厂是主要客户端创建表单的接口。客户端代码不再仅仅是调用。
var form1 = new Form1();
不,这是被禁止的。相反,客户端应始终调用。
var form1 = new FormFactory().CreateForm1()
(以及其他形式的类似情况)。
请注意,虽然工厂已经实现,但它本身并不会做任何事情!相反,它将创建委托给某种神秘的提供者,这个提供者必须被注入到工厂中。这个想法背后的原理是,提供者将在组合根中被注入一次,这是代码中靠近启动并且非常高的地方,这样所有依赖项都可以在那里解析。因此,表单工厂不需要知道最终将注入哪个提供者。
这种方法有一个显着的优点-根据实际要求,可以注入不同的提供者,例如,可以为实际应用程序编写基于DI的提供者(我们将在下面编写它),并为单元测试编写存根提供者。
无论如何,让我们来创建一个具有依赖项的表单:
public partial class Form1 : Form
{
private IHelloWorldService _service;
public Form1(IHelloWorldService service)
{
InitializeComponent();
this._service = service;
}
}
这个表单依赖于一个服务,而该服务将由构造函数提供。如果Form1需要创建另一个表单Form2,它会以我们已经讨论过的方式进行:
var form2 = new FormFactory().CreateForm2()
当一个表单不仅需要依赖于服务,还需要一些自由参数(字符串、整数等)时,事情就变得更加复杂了。通常情况下,您需要一个构造函数。
public Form2( string something, int somethingElse ) ...
但现在你需要更像这样的东西
public Form2( ISomeService service1, IAnotherService service2,
string something, int somethingElse ) ...
这是我们真正需要仔细研究的内容。再看一遍,一个现实生活中的表单可能需要:
- 由容器解决的某些参数
- 由表单创建者提供的其他参数(容器不知道!)。
我们如何处理这个问题?
为了有一个完整的例子,让我们修改表单工厂。
public interface IFormFactory
{
Form1 CreateForm1();
Form2 CreateForm2(string something);
}
public class FormFactory : IFormFactory
{
static IFormFactory _provider;
public static void SetProvider( IFormFactory provider )
{
_provider = provider;
}
public Form1 CreateForm1()
{
return _provider.CreateForm1();
}
public Form2 CreateForm2(string something)
{
return _provider.CreateForm2(something);
}
}
让我们看看表单是如何定义的
public partial class Form1 : Form
{
private IHelloWorldService _service;
public Form1(IHelloWorldService service)
{
InitializeComponent();
this._service = service;
}
private void button1_Click( object sender, EventArgs e )
{
var form2 = new FormFactory().CreateForm2("foo");
form2.Show();
}
}
public partial class Form2 : Form
{
private IHelloWorldService _service;
private string _something;
public Form2(IHelloWorldService service, string something)
{
InitializeComponent();
this._service = service;
this._something = something;
this.Text = something;
}
}
你能看到这里有一个模式吗?
- 每当一个表单(例如Form1)只需要依赖服务时,它在FormFactory中的创建方法为空(容器将解析依赖项)。
- 每当一个表单(例如Form2)需要依赖服务和其他自由参数时,它在FormFactory中的创建方法包含一个参数列表,对应于这些自由参数(服务依赖项将由容器解析)。
现在最后是组合根。让我们从服务开始。
public interface IHelloWorldService
{
string DoWork();
}
public class HelloWorldServiceImpl : IHelloWorldService
{
public string DoWork()
{
return "hello world service::do work";
}
}
请注意,虽然接口应该在堆栈的某个位置(以便被所有人识别),但实现可以在任何地方提供(表单不需要引用实现!)。接下来是起始代码,最终提供了表单工厂并设置了容器。
internal static class Program
{
[STAThread]
static void Main()
{
var formFactory = CompositionRoot();
ApplicationConfiguration.Initialize();
Application.Run(formFactory.CreateForm1());
}
static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((context, services) => {
services.AddTransient<IHelloWorldService, HelloWorldServiceImpl>();
services.AddTransient<Form1>();
services.AddTransient<Func<string,Form2>>(
container =>
something =>
{
var helloWorldService =
container.GetRequiredService<IHelloWorldService>();
return new Form2(helloWorldService, something);
});
});
}
static IFormFactory CompositionRoot()
{
var hostBuilder = CreateHostBuilder();
var host = hostBuilder.Build();
var serviceProvider = host.Services;
var formFactory = new FormFactoryImpl(serviceProvider);
FormFactory.SetProvider(formFactory);
return formFactory;
}
}
public class FormFactoryImpl : IFormFactory
{
private IServiceProvider _serviceProvider;
public FormFactoryImpl(IServiceProvider serviceProvider)
{
this._serviceProvider = serviceProvider;
}
public Form1 CreateForm1()
{
return _serviceProvider.GetRequiredService<Form1>();
}
public Form2 CreateForm2(string something)
{
var _form2Factory = _serviceProvider.GetRequiredService<Func<string, Form2>>();
return _form2Factory( something );
}
}
首先请注意如何使用Host.CreateDefaultBuilder创建容器,这是一项简单的任务。然后请注意如何注册服务以及如何在其他服务中注册表单。
对于没有任何依赖关系的表单来说,这很直接,只需
services.AddTransient<Form1>()
然而,如果一个表单需要服务和自由参数,它将被注册为...表单创建函数,一个任意自由参数的Func,返回实际的表单。看一下这个。
services.AddTransient<Func<string,Form2>>(
container =>
something =>
{
var helloWorldService = container.GetRequiredService<IHelloWorldService>();
return new Form2(helloWorldService, something);
});
这很聪明。我们使用其中一种注册机制来注册一个表单工厂函数,该机制本身使用工厂函数(是的,一个使用另一个工厂的工厂,即Factception。如果你感到迷失,请随意休息一下)。我们注册的函数Func<string, Form2>有一个参数,即something(对应于表单构造函数的自由参数),但它的其他依赖项由容器解决...这正是我们想要的。
这就是为什么实际的表单工厂需要注意它解析的内容。一个简单的表单解析如下:
return _serviceProvider.GetRequiredService<Form1>()
其中另一种方法需要分两步解决。我们首先解析工厂函数,然后使用创建方法的参数将其馈送到函数中:
var _form2Factory = _serviceProvider.GetRequiredService<Func<string, Form2>>()
return _form2Factory( something )
就是这样。每当创建一个表单时,它会是以下两者之一:
new FormFactory().CreateForm1();
对于“简单”表单(仅具有服务依赖项)或只是
new FormFactory().CreateForm2("foo")
对于需要同时依赖服务和其他自由参数的表单。
new Form1(new ConcreteCategory())
的构造函数中手动传递具体类型。 - Charles