如何向对象工厂提供不同的参数?

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。