如何向对象工厂提供不同的参数?
How to provide different arguments to an object factory?
我有一个库,其中一些 类 实现了相同的接口:
internal class MyObj1 : IMyObj {
public MyObj1(string param1, int param2) {}
}
internal class MyObj2 : IMyObj {
public MyObj2(bool param1, string param2, int param3) {}
}
internal class MyObj3 : IMyObj {
public MyObj3(string param1, int param2) {}
}
我想创建一个只允许 IMyObj 访问 MyObj1、MyObj2、MyObj3 的对象工厂:
public class MyObjFactory {
public IMyObj Create<T>() {
return (IMyObj)Activator.CreateInstance(typeof(T));
}
}
我不知道如何将构造函数参数传递给工厂方法。有什么想法吗?
使用Activator.CreateInstance
Method (Type, Object[])
Creates an instance of the specified type using the constructor that
best matches the specified parameters.
public IMyObj Create<T>(params object[] args)
{
return (IMyObj)Activator.CreateInstance(typeof(T),args);
}
或者
public IMyObj Create<T>(string param1, int param2) where T : MyObj1
{
return (IMyObj)Activator.CreateInstance(typeof(T),args);
}
public IMyObj Create<T>(bool param1, string param2, int param3) where T : MyObj2
{
return (IMyObj)Activator.CreateInstance(typeof(T),args);
}
...
...
我建议 不要 使用 Activator.CreateInstance
,因为它相对较慢,并且会降低运行时安全性(例如,如果您弄错了构造函数参数的数量它会在运行时抛出异常。
我建议如下:
public IMyObj CreateType1(string param1, int param2)
{
return new MyObj1(param1, param2);
}
public IMyObj CreateType2(bool param1, string param2, int param3)
{
return new MyObj2(param1, param2, param3);
}
这听起来像是你所在的位置:
a) 您不希望 classes 创建它们所依赖的额外 classes,因为这会将它们耦合在一起。每个 class 都必须了解它所依赖的 class 的太多信息,例如它们的构造函数参数。
b) 您创建一个工厂来分离这些对象的创建。
c) 你发现你在 (a) 中遇到的问题现在已经转移到 (b) 中,但它是完全相同的问题,只是更多 classes。现在您的工厂必须创建 class 个实例。但是它将从哪里获得创建这些对象所需的构造函数参数?
一种解决方案是使用 DI 容器。如果这是完全熟悉的,那么这是 10% 的坏消息和 90% 的好消息。有一点学习曲线,但还不错。 90% 的好消息是您已经意识到自己需要它,它将成为一个非常有价值的工具。
当我说 "DI container" 时 - 也称为 "IoC (Inversion of Control) container,",指的是 Autofac、Unity 或 Castle Windsor 等工具。我主要与 Windsor 一起工作,所以我在示例中使用它。
DI 容器是一种无需显式调用构造函数即可为您创建对象的工具。 (这个解释 100% 肯定是不够的——你还需要 Google。相信我,这是值得的。)
假设你有一个 class 依赖于几个抽象(接口)。这些接口的实现依赖于更多的抽象:
public class ClassThatDependsOnThreeThings
{
private readonly IThingOne _thingOne;
private readonly IThingTwo _thingTwo;
private readonly IThingThree _thingThree;
public ClassThatDependsOnThreeThings(IThingOne thingOne, IThingTwo thingTwo, IThingThree thingThree)
{
_thingOne = thingOne;
_thingTwo = thingTwo;
_thingThree = thingThree;
}
}
public class ThingOne : IThingOne
{
private readonly IThingFour _thingFour;
private readonly IThingFive _thingFive;
public ThingOne(IThingFour thingFour, IThingFive thingFive)
{
_thingFour = thingFour;
_thingFive = thingFive;
}
}
public class ThingTwo : IThingTwo
{
private readonly IThingThree _thingThree;
private readonly IThingSix _thingSix;
public ThingTwo(IThingThree thingThree, IThingSix thingSix)
{
_thingThree = thingThree;
_thingSix = thingSix;
}
}
public class ThingThree : IThingThree
{
private readonly string _connectionString;
public ThingThree(string connectionString)
{
_connectionString = connectionString;
}
}
这很好,因为每个人 class 都很简单且易于测试。但是你究竟要如何创建一个工厂来为你创建所有这些对象呢?该工厂必须 know/contain 创建每一个对象所需的一切。
单个 classes 的情况更好,但组合它们或创建实例变得非常令人头疼。如果您的代码的某些部分只需要其中一个怎么办 - 您是否创建另一个工厂?如果您必须更改其中一个 class 以便现在它具有更多或不同的依赖项怎么办?现在你必须回去修理你所有的工厂。那是一场噩梦。
DI 容器(同样,此示例使用 Castle.Windsor)允许您执行此操作。起初它看起来像是更多的工作,或者只是解决问题。但事实并非如此:
var container = new WindsorContainer();
container.Register(
Component.For<ClassThatDependsOnThreeThings>(),
Component.For<IThingOne, ThingOne>(),
Component.For<IThingTwo, ThingTwo>(),
Component.For<IThingThree, ThingThree>()
.DependsOn(Dependency.OnValue("connectionString", ConfigurationManager.ConnectionStrings["xyz"].ConnectionString)),
Component.For<IThingFour,IThingFour>(),
Component.For<IThingFive, IThingFive>(),
Component.For<IThingSix, IThingSix>()
);
现在,如果你这样做:
var thing = container.Resolve<ClassThatDependsOnThreeThings>();
或
var thingTwo = container.Resolve<IThingTwo>();
只要您已经向容器注册了类型,并且还注册了实现所有嵌套依赖项所需的任何类型,容器就会根据需要创建每个对象,调用每个对象的构造函数,直到它最终可以创建您要求的对象。
您可能会注意到的另一个细节是 none 这些 classes 创建了它们所依赖的东西。没有new ThingThree()
。每个 class 所依赖的内容都在其构造函数中指定。这是依赖注入的基本概念之一。如果 class 只是 接收 和 IThingThree
的实例,那么它真的永远不知道实现是什么。它只依赖于接口,对实现一无所知。这适用于依赖倒置,即 SOLID 中的 "D"。它有助于防止您的 classes 与特定的实现细节耦合。
太强大了。这意味着,如果配置得当,您可以在代码中的任何位置请求您需要的依赖项——通常作为一个接口——然后直接接收它。需要它的 class 不必知道如何创建它。这意味着 90% 的时间你甚至根本不需要工厂。你的 class 的构造函数只是说它需要什么,而容器提供它。
(如果您确实需要一个工厂,这在某些情况下确实会发生,Windsor 和其他一些容器可以帮助您创建一个。。)
要使其正常工作,需要学习如何配置您正在使用的应用程序类型以使用 DI 容器。例如,在 ASP.NET MVC 应用程序中,您可以配置容器来为您创建控制器。这样,如果您的控制器依赖更多的东西,容器可以根据需要创建这些东西。 ASP.NET Core 通过提供自己的 DI 容器使它变得更容易,因此您所要做的就是注册各种组件。
这是一个不完整的答案,因为它描述了解决方案是什么,但没有告诉您如何实施它。这将需要您进行更多搜索,例如 "How do I configure XYZ for dependency injection," 或只是了解更多关于一般概念的信息。一位作者将其称为 0.50 美元概念的 5 美元术语。在您尝试并了解它是如何工作之前,它看起来既复杂又令人困惑。然后你会明白为什么它内置于 ASP.NET 核心,Angular,以及为什么所有种类的语言都使用依赖注入。
当你达到这样的地步 - 正如你所拥有的那样 - 你遇到了 DI 解决的问题,这真的很令人兴奋,因为这意味着你意识到必须有更好、更干净的方法来完成你想要做的事情.好消息是有。学习并使用它会对整个代码产生连锁反应,使您能够更好地应用 SOLID 原则并编写更易于单元测试的更小 classes。
我有一个库,其中一些 类 实现了相同的接口:
internal class MyObj1 : IMyObj {
public MyObj1(string param1, int param2) {}
}
internal class MyObj2 : IMyObj {
public MyObj2(bool param1, string param2, int param3) {}
}
internal class MyObj3 : IMyObj {
public MyObj3(string param1, int param2) {}
}
我想创建一个只允许 IMyObj 访问 MyObj1、MyObj2、MyObj3 的对象工厂:
public class MyObjFactory {
public IMyObj Create<T>() {
return (IMyObj)Activator.CreateInstance(typeof(T));
}
}
我不知道如何将构造函数参数传递给工厂方法。有什么想法吗?
使用Activator.CreateInstance
Method (Type, Object[])
Creates an instance of the specified type using the constructor that best matches the specified parameters.
public IMyObj Create<T>(params object[] args)
{
return (IMyObj)Activator.CreateInstance(typeof(T),args);
}
或者
public IMyObj Create<T>(string param1, int param2) where T : MyObj1
{
return (IMyObj)Activator.CreateInstance(typeof(T),args);
}
public IMyObj Create<T>(bool param1, string param2, int param3) where T : MyObj2
{
return (IMyObj)Activator.CreateInstance(typeof(T),args);
}
...
...
我建议 不要 使用 Activator.CreateInstance
,因为它相对较慢,并且会降低运行时安全性(例如,如果您弄错了构造函数参数的数量它会在运行时抛出异常。
我建议如下:
public IMyObj CreateType1(string param1, int param2)
{
return new MyObj1(param1, param2);
}
public IMyObj CreateType2(bool param1, string param2, int param3)
{
return new MyObj2(param1, param2, param3);
}
这听起来像是你所在的位置:
a) 您不希望 classes 创建它们所依赖的额外 classes,因为这会将它们耦合在一起。每个 class 都必须了解它所依赖的 class 的太多信息,例如它们的构造函数参数。
b) 您创建一个工厂来分离这些对象的创建。
c) 你发现你在 (a) 中遇到的问题现在已经转移到 (b) 中,但它是完全相同的问题,只是更多 classes。现在您的工厂必须创建 class 个实例。但是它将从哪里获得创建这些对象所需的构造函数参数?
一种解决方案是使用 DI 容器。如果这是完全熟悉的,那么这是 10% 的坏消息和 90% 的好消息。有一点学习曲线,但还不错。 90% 的好消息是您已经意识到自己需要它,它将成为一个非常有价值的工具。
当我说 "DI container" 时 - 也称为 "IoC (Inversion of Control) container,",指的是 Autofac、Unity 或 Castle Windsor 等工具。我主要与 Windsor 一起工作,所以我在示例中使用它。
DI 容器是一种无需显式调用构造函数即可为您创建对象的工具。 (这个解释 100% 肯定是不够的——你还需要 Google。相信我,这是值得的。)
假设你有一个 class 依赖于几个抽象(接口)。这些接口的实现依赖于更多的抽象:
public class ClassThatDependsOnThreeThings
{
private readonly IThingOne _thingOne;
private readonly IThingTwo _thingTwo;
private readonly IThingThree _thingThree;
public ClassThatDependsOnThreeThings(IThingOne thingOne, IThingTwo thingTwo, IThingThree thingThree)
{
_thingOne = thingOne;
_thingTwo = thingTwo;
_thingThree = thingThree;
}
}
public class ThingOne : IThingOne
{
private readonly IThingFour _thingFour;
private readonly IThingFive _thingFive;
public ThingOne(IThingFour thingFour, IThingFive thingFive)
{
_thingFour = thingFour;
_thingFive = thingFive;
}
}
public class ThingTwo : IThingTwo
{
private readonly IThingThree _thingThree;
private readonly IThingSix _thingSix;
public ThingTwo(IThingThree thingThree, IThingSix thingSix)
{
_thingThree = thingThree;
_thingSix = thingSix;
}
}
public class ThingThree : IThingThree
{
private readonly string _connectionString;
public ThingThree(string connectionString)
{
_connectionString = connectionString;
}
}
这很好,因为每个人 class 都很简单且易于测试。但是你究竟要如何创建一个工厂来为你创建所有这些对象呢?该工厂必须 know/contain 创建每一个对象所需的一切。
单个 classes 的情况更好,但组合它们或创建实例变得非常令人头疼。如果您的代码的某些部分只需要其中一个怎么办 - 您是否创建另一个工厂?如果您必须更改其中一个 class 以便现在它具有更多或不同的依赖项怎么办?现在你必须回去修理你所有的工厂。那是一场噩梦。
DI 容器(同样,此示例使用 Castle.Windsor)允许您执行此操作。起初它看起来像是更多的工作,或者只是解决问题。但事实并非如此:
var container = new WindsorContainer();
container.Register(
Component.For<ClassThatDependsOnThreeThings>(),
Component.For<IThingOne, ThingOne>(),
Component.For<IThingTwo, ThingTwo>(),
Component.For<IThingThree, ThingThree>()
.DependsOn(Dependency.OnValue("connectionString", ConfigurationManager.ConnectionStrings["xyz"].ConnectionString)),
Component.For<IThingFour,IThingFour>(),
Component.For<IThingFive, IThingFive>(),
Component.For<IThingSix, IThingSix>()
);
现在,如果你这样做:
var thing = container.Resolve<ClassThatDependsOnThreeThings>();
或
var thingTwo = container.Resolve<IThingTwo>();
只要您已经向容器注册了类型,并且还注册了实现所有嵌套依赖项所需的任何类型,容器就会根据需要创建每个对象,调用每个对象的构造函数,直到它最终可以创建您要求的对象。
您可能会注意到的另一个细节是 none 这些 classes 创建了它们所依赖的东西。没有new ThingThree()
。每个 class 所依赖的内容都在其构造函数中指定。这是依赖注入的基本概念之一。如果 class 只是 接收 和 IThingThree
的实例,那么它真的永远不知道实现是什么。它只依赖于接口,对实现一无所知。这适用于依赖倒置,即 SOLID 中的 "D"。它有助于防止您的 classes 与特定的实现细节耦合。
太强大了。这意味着,如果配置得当,您可以在代码中的任何位置请求您需要的依赖项——通常作为一个接口——然后直接接收它。需要它的 class 不必知道如何创建它。这意味着 90% 的时间你甚至根本不需要工厂。你的 class 的构造函数只是说它需要什么,而容器提供它。
(如果您确实需要一个工厂,这在某些情况下确实会发生,Windsor 和其他一些容器可以帮助您创建一个。
要使其正常工作,需要学习如何配置您正在使用的应用程序类型以使用 DI 容器。例如,在 ASP.NET MVC 应用程序中,您可以配置容器来为您创建控制器。这样,如果您的控制器依赖更多的东西,容器可以根据需要创建这些东西。 ASP.NET Core 通过提供自己的 DI 容器使它变得更容易,因此您所要做的就是注册各种组件。
这是一个不完整的答案,因为它描述了解决方案是什么,但没有告诉您如何实施它。这将需要您进行更多搜索,例如 "How do I configure XYZ for dependency injection," 或只是了解更多关于一般概念的信息。一位作者将其称为 0.50 美元概念的 5 美元术语。在您尝试并了解它是如何工作之前,它看起来既复杂又令人困惑。然后你会明白为什么它内置于 ASP.NET 核心,Angular,以及为什么所有种类的语言都使用依赖注入。
当你达到这样的地步 - 正如你所拥有的那样 - 你遇到了 DI 解决的问题,这真的很令人兴奋,因为这意味着你意识到必须有更好、更干净的方法来完成你想要做的事情.好消息是有。学习并使用它会对整个代码产生连锁反应,使您能够更好地应用 SOLID 原则并编写更易于单元测试的更小 classes。