【问题标题】:How to provide different arguments to an object factory?如何为对象工厂提供不同的参数?
【发布时间】:2018-02-13 11:38:28
【问题描述】:

我有一个包含一些实现相同接口的类的库:

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));
    }
}

我不知道如何将构造函数参数传递给工厂方法。有什么想法吗?

【问题讨论】:

  • 为什么new MyObjectFactory.Create&lt;MyObj1&gt;("a", 1)new MyObj1("a", 1) 更可取?
  • @NightOwl888 刚才所说的 - 这正是您所需要的。反射和Activator.CreateInstance() 解决不了任何问题。它只是将问题转移到角落里。您仍然必须弄清楚从哪里获取构造函数参数,或者更糟糕的是,它会导致您开始只创建只有空构造函数的类。我正在研究一个例子。

标签: c# .net design-patterns factory


【解决方案1】:

听起来你现在的位置是这样的:

a) 您不希望类创建它们所依赖的其他类,因为这会将它们耦合在一起。每个类都必须对它所依赖的类了解太多,例如它们的构造函数参数。

b) 你创建一个工厂来分离这些对象的创建。

c) 你发现你在 (a) 中遇到的问题现在已经转移到 (b) 中,但它是完全相同的问题,只是有更多的类。现在您的工厂必须创建类实例。但是它将从哪里获得创建这些对象所需的构造函数参数?

一种解决方案是使用 DI 容器。如果这完全熟悉,那么这就是 10% 的坏消息和 90% 的好消息。有一点学习曲线,但还不错。 90% 的好消息是,您已经意识到自己需要它,并且它将成为一个非常有价值的工具。

当我说“DI 容器”时 - 也称为“IoC(控制反转)容器”,它指的是 Autofac、Unity 或 Castle Windsor 等工具。我主要与温莎一起工作,所以我在示例中使用它。

DI 容器是一种无需显式调用构造函数即可为您创建对象的工具。 (这个解释 100% 肯定是不够的 - 你需要更多地谷歌。相信我,这是值得的。)

假设您有一个依赖于多个抽象(接口)的类。这些接口的实现依赖于更多抽象:

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;
    }
}

这很好,因为每个单独的类都简单且易于测试。但是你到底要如何创建一个工厂来为你创建所有这些对象呢?该工厂必须知道/包含创建每个对象所需的一切。

单独的类更好,但是组合它们或创建实例成为一个主要的问题。如果您的代码的某些部分只需要其中一个怎么办 - 您是否创建另一个工厂?如果您必须更改其中一个类以使其具有更多或不同的依赖项怎么办?现在你必须回去修理你所有的工厂。这是一场噩梦。

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>();

只要你已经向容器注册了类型,并且你还注册了满足所有嵌套依赖项所需的任何类型,容器就会根据需要创建每个对象,调用每个对象的构造函数,直到它可以最后创建您要求的对象。

您可能会注意到的另一个细节是,这些类都没有创建它们所依赖的东西。没有new ThingThree()。每个类所依赖的任何东西都在其构造函数中指定。这是依赖注入的基本概念之一。如果一个类只是接收IThingThree 的实例,那么它真的永远不知道实现是什么。它只依赖于接口,对实现一无所知。这适用于依赖倒置,即 SOLID 中的“D”。它有助于防止您的类与特定的实现细节耦合。

这是非常强大的。这意味着,当正确配置时,您可以在代码中的任何位置请求您需要的依赖项——通常作为接口——然后接收它。需要它的类不必知道如何创建它。这意味着 90% 的时间你甚至根本不需要工厂。你的类的构造函数只是说明它需要什么,容器提供它。

(如果您确实需要一个工厂,在某些情况下确实会发生这种情况,Windsor 和其他一些容器会帮助您创建一个。Here's an example。)

要使其工作的一部分涉及学习如何配置您正在使用的应用程序类型以使用 DI 容器。例如,在 ASP.NET MVC 应用程序中,您可以配置容器来为您创建控制器。这样,如果您的控制器依赖于更多的东西,容器可以根据需要创建这些东西。 ASP.NET Core 提供了自己的 DI 容器,让您更轻松地注册各种组件。

这是一个不完整的答案,因为它描述了解决方案是什么,而没有告诉您如何实现它。这将需要您进行更多搜索,例如“我如何配置 XYZ 以进行依赖注入”,或者只是总体上了解更多有关该概念的信息。一位作者称其为 0.50 美元概念的 5 美元术语。在您尝试并了解其工作原理之前,它看起来很复杂且令人困惑。然后你就会明白为什么它会内置在 ASP.NET Core、Angular 中,以及为什么各种语言都使用依赖注入。

当您达到 DI 解决问题的地步时,这真的很令人兴奋,因为这意味着您意识到必须有一种更好、更清洁的方法来完成您正在尝试做的事情。好消息是有。学习和使用它将在整个代码中产生连锁反应,使您能够更好地应用 SOLID 原则并编写更易于单元测试的较小类。

【讨论】:

  • 感谢您的详细解答。我读了它并且知道我应该阅读更多关于 DI 的内容。但是,我不确定 DI 容器是否适合我的目标。看起来像很多代码来做小事。你怎么看待这件事?我的目标很简单:1)我只想通过公共接口(IMyObj)获取我的所有对象,2)我想拒绝直接创建一个类实例。这就是我所需要的。
【解决方案2】:

我建议不要使用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);
}

【讨论】:

  • X Y 晚餐服务完美!
  • 工厂从哪里获得param1param2等的值?需要工厂的对象如何创建工厂?
【解决方案3】:

使用Activator.CreateInstance Method (Type, Object[])

使用构造函数创建指定类型的实例 与指定参数最匹配。

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);
}
...
...

【讨论】:

  • 看起来像我需要的。您能否建议我如何改进此代码 sn-p 以使使用 MyObjFactory 的人清楚 args?现在人们不知道他应该传递什么作为“args”。
  • 看起来不错。替代方法很清楚并解决了我的问题。您是否建议使用它而不是第一个?
  • 正如 mjwills 所说,工厂模式并没有真正为此增加太多价值。为什么新的MyObjectFactory.Create&lt;MyObj1&gt;("a", 1) 优于新的MyObj1("a", 1)?即它实际上更少的字符来做后面的事情并按原样旋转你的对象
  • @mjwills,因为工厂解决了主要问题 - 我只能访问 IMyObj 而无法直接访问 MyObj1、MyObj2 或 MyObj3。
  • 当学生准备好时,主人出现。 (我不是大师——这只是一个很好的例子。)当有人发现自己在创建工厂并想知道——等等——工厂将在哪里获得它插入到它创建的对象中的值时,那么是时候DI 容器出现。开发人员可能已经受到了解的限制。是时候拥抱令人敬畏的了。
猜你喜欢
  • 2011-09-30
  • 1970-01-01
  • 1970-01-01
  • 2014-05-03
  • 2020-04-07
  • 1970-01-01
  • 1970-01-01
  • 2014-03-03
  • 2017-03-19
相关资源
最近更新 更多