代码之家  ›  专栏  ›  技术社区  ›  Grant Palin Bob King

构造(有点)复杂的对象

  •  7
  • Grant Palin Bob King  · 技术社区  · 14 年前

    当我创建类时,简单的构造函数往往是标准的。在我目前的一个项目,电影库,我有一个 Movie 域对象。它具有多个属性,从而生成一个构造函数,如下所示:

    public Movie(string title, int year, Genre genre, int length, IEnumerable<string> actors)
    {
        _title = title;
        _year = year;
        _genre = genre;
        _length = length;
        _actors = new List<string>(actors);
    }
    

    这并不可怕,但也不简单。用工厂法值得吗( static Movie CreateMovie(...) ),或者对象生成器?有没有实例化域类的典型模式?

    谢谢你的回复。起初我可能想得太多了,不过我学到了一些在更复杂的情况下有用的东西。我现在的解决方案是将title作为唯一必需的参数,其余的作为named/optional参数。这似乎是构造这个域对象的最理想的方法。

    9 回复  |  直到 14 年前
        1
  •  4
  •   LBushkin    14 年前

    如果您使用的是.NET 4.0, 你可以用 optional/named parameters 简化接受多个参数(其中一些参数是可选的)的对象的创建。当您希望避免许多不同的重载以提供有关对象的必要信息时,这很有帮助。

    你可以使用 Object Builder 模式来组装你的类型。ObjectBuilder需要一点努力来实现,并与您键入的内容保持同步—因此这样做是否有足够的价值取决于您的情况。

    我发现构建器模式在组装层次结构时最有效,而不是一个具有一堆属性的类型。在后一种情况下,我通常使用重载或可选/命名参数。

        2
  •  4
  •   Heinzi    14 年前

    Wikipedia says

    与其他创建模式一样,它处理创建对象(产品)的问题,而不指定要创建的对象的确切类。工厂方法设计模式通过定义一个单独的方法来创建对象来处理这个问题,然后子类可以重写这个方法来指定将要创建的产品的派生类型。

    因此,如果您想返回,工厂方法模式是有意义的 子类 属于 Movie . 如果这不是(也不会是)一个要求,那么用工厂方法替换公共构造函数并不能真正起到任何作用。

    C# object initializer syntax .

        3
  •  3
  •   Dave Swersky    14 年前

    您提到的CreateMovie()方法是另一个选项,以防您需要将内部构造函数与创建电影实例的行为分开。

    你有许多选择,可供你安排构造器。使用那些可以让你设计你的系统没有气味和许多原则(干,雅格尼,SRP)

        4
  •  2
  •   Lou Franco    14 年前

    这些参数似乎不是可选的,因此没有一种方法可以用更少或更少的参数来提供重载 使用可选参数。

     Movie m = new Movie("Inception", 2010, Genre.Drama, 150, actors);
    

    工厂的目的是为您提供一个可定制的具体接口实例,而不仅仅是为您调用构造函数。其思想是,在构造时不需要硬编码确切的类。这真的更好吗?

     Movie m = Movie.Create("Inception", 2010, Genre.Drama, 150, actors);
    

    Create() 返回其他混凝土类 Movie

    需要考虑的一件事是如何改进这一点,以便调用代码更容易理解。对我来说,最明显的问题是,不看代码就不清楚150意味着什么 电影 . 如果您愿意,有几种方法可以改进:

    1. 使用类型作为电影长度,并将该类型构造为inline new MovieLength(150)
    2. 使用 named parameters if you are using .NET 4.0
    3. (参见@Heinzi的答案)使用对象初始值设定项
    4. fluent interface

    使用流畅的界面,您的通话看起来

     Movie m = new Movie("Inception").
       MadeIn(2010).
       InGenre(Genre.Drama).
       WithRuntimeLength(150).
       WithActors(actors);
    

    坦白说,这一切对你的案子来说似乎都是杀伤力过大。如果您使用的是.NET4.0,那么命名参数是合理的,因为它们不需要太多代码,而且可以改进调用方的代码。

        5
  •  1
  •   DanDan    14 年前

    你对自己的问题回答得很好,这是工厂模式。使用工厂模式,您不需要庞大的构造函数来进行封装,您可以在工厂函数中设置对象的成员并返回该对象。

        6
  •  1
  •   Nate CSS Guy    14 年前

    这是完全可以接受的,伊莫。我知道静态方法有时是不受欢迎的,但我通常会将代码放入一个返回类实例的静态方法中。我通常只对允许有空值的对象这样做。

    如果对象的值不能为null,请将它们作为参数添加到构造函数中,这样就不会出现任何无效对象。

        7
  •  1
  •   Brian Gideon    14 年前

    我认为让公众保持现状没什么错。以下是我在决定是否使用工厂方法时遵循的一些规则。

    • 当初始化需要复杂的算法时,一定要使用工厂方法。
    • 当初始化需要IO绑定操作时,请使用工厂方法。
    • 当初始化可能引发在开发时无法防范的异常时,请使用工厂方法。
    • 当需要额外的措辞以增强可读性时,一定要使用工厂方法。

    所以根据我个人的规则,我会让构造器保持原样。

        8
  •  1
  •   Keith Pinson sumit vedi    11 年前

    如果您可以区分核心数据成员和配置参数,那么可以创建一个构造函数,该构造函数只接受所有核心数据成员,而不接受其他任何内容(即使是具有默认值的配置参数也不考虑可读性)。将配置参数初始化为sane默认值(在方法体中)并提供setter。在这一点上,工厂方法可以为您购买一些东西,如果您需要对象的公共配置的话。

    更好的是,如果你发现你有一个对象,需要大量的参数列表,对象可能太胖了。您已经意识到您的代码可能需要重构。考虑分解对象。关于OO的优秀文献强烈支持小对象(例如Martin Fowler, 重构 鲍勃马丁, ). 福勒解释如何分解大型物体。例如,配置参数(如果有的话)可能表示需要更多的多态性,特别是当它们是布尔或枚举时(重构“Convert Conditional to polymorphics”)。

    在给出更具体的建议之前,我需要看看你的对象是如何使用的。Fowler说,一起使用的变量应该成为它们自己的对象。因此,为了举例说明,如果您是基于类型、年份和长度来计算某些事物,而不是基于其他属性,那么这些属性一起可能需要分解为各自的对象,从而减少必须传递给构造函数的参数数量。

        9
  •  0
  •   madcyree    14 年前

    至于我-所有取决于您的域模型。如果你的域模型允许你创建简单的对象-你应该这样做。

    但通常我们有很多复合对象,每个对象的创建都太复杂了。这就是为什么我们在寻找封装复合对象创建逻辑的最佳方法。实际上,我们只有上面描述的两种选择——“工厂方法”和“对象生成器”。通过静态方法创建对象看起来有点奇怪,因为我们将对象创建逻辑放入对象中。反过来,对象生成器看起来很复杂。

    我认为答案在于单元测试。这正是TDD非常有用的情况—我们逐步建立领域模型,并了解领域模型复杂性的需要。