2010-08-24 44 views
6

当我创建类时,简单的构造函数往往是常态。在我目前的一个项目中,有一个电影库,我有一个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(...))还是一个对象构建器会值得吗?有实例化域类的典型模式吗?

UPDATE:感谢您的回复。尽管我已经学到了一些在更复杂的情况下将会有用的东西,但我最初可能会反思这个问题。我现在的解决方案是将标题作为唯一必需的参数,其余为命名/可选参数。这似乎是构建这个域对象的全面理想方式。

+0

您的问题的解决方案不应作为您的问题的更新发布。它应该是一个答案,或者在这种情况下,可能是一个评论。 – 2013-01-15 20:12:48

回答

4

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

如果你不在.NET 4,你可能想使用Object Builder模式来组装你的类型。对象构建器需要一点努力才能实现,并与您保持同步类型 - 所以这样做是否有足够的价值取决于您的情况。

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

+0

我目前使用3.5,但4.0是一个选项。我也尝试过建设者的想法,这是一个工作,并且在这种情况下似乎是矫枉过正。虽然我可以看到它在您提到的更复杂的设置中很有用。 – 2010-08-24 17:38:43

1

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

1

这是完全可以接受的,恕我直言。我知道静态方法有时会被忽略,但我通常会将该代码放入一个返回该类实例的静态方法中。我通常只对那些允许有空值的对象做这件事。

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

3

这取决于。

如果这是该类的唯一构造函数,则意味着所有属性都是实例化该对象所必需的。如果这符合您的业务规则,那就太棒了。否则,可能会有点麻烦。例如,如果你想用电影播种你的系统,但并不总是有演员,你可能会发现自己陷入了泡菜。

您提到的CreateMovie()方法是另一种选择,以防您需要将内部构造函数与创建Movie实例的操作分开。

您有许多选项可供您安排构造函数。使用那些允许你设计没有气味和多种原理的系统(DRY,YAGNI,SRP)。

+0

“用电影种子你的系统,但并不总是有演员” - 优点。我曾考虑过忽略这个参数并使用'AddActor(string name)'方法。 – 2010-08-24 17:34:40

+0

我开始考虑把标题作为唯一的ctor。参数,剩下的可设置的属性,所以我可以轻松地添加电影到系统并在稍后填写细节。 – 2010-08-24 17:36:32

+0

我通常不喜欢将属性放在任何构造函数中,因此我的选项保持打开状态。使用新的C#对象初始化语法处理属性赋值非常容易:[new object(){fooProp =“1”,barProp =“Hello”}] – 2010-08-24 17:44:18

4

是的,使用工厂方法是一种典型的模式,但问题是:为什么你需要它?这是Wikipedia says关于工厂方法:

像其他创建型模式,它没有指定确切类对象将创建创建对象(产品)的问题涉及。工厂方法设计模式通过定义用于创建对象的单独方法来处理此问题,然后可以重写该子类以指定将创建的产品的派生类型。

所以,如果你想返回子类Movie工厂方法模式才有意义。如果这不是(也不是)要求,用工厂方法替换公共构造函数并不真正起到任何作用。

对于您的问题中陈述的要求,您的解决方案对我来说看起来非常好:所有必填字段都作为参数传递给构造函数。如果您的字段都不是必填字段,则可能需要添加默认初始值设定项并使用C# object initializer syntax

+0

工厂方法的优点。这是我所知道的一种创造性技术,所以我想我会把它扔进战场。鉴于引用,这里似乎没有必要。 – 2010-08-24 17:50:37

1

我没有看到你的构造函数的接口有什么问题,也没有看到什么是静态方法。我将拥有完全相同的参数,对吗?

参数似乎不是可选的,所以没有办法使用可选参数来减少过载或使用可选参数。

从点,查看来电者的,它看起来是这样的:

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

工厂的目的是为您提供一个界面的定制具体的实例,而不是只是调用构造函数为你。这个想法是,确切的类没有硬编码的建设点。这真的好吗?

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

这对我来说看起来差不多。唯一更好的是如果Create()返回其他具体的类比Movie

有一点需要考虑的是如何改善这一点,以便调用代码很容易理解。对我来说最明显的问题是,没有看到代码为Movie的代码并不明显。有几种方法来改善,如果你想:

  1. 使用类型电影的长度和构建型直列新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); 

坦率地说,所有这些对你的情况来说似乎有些过火。如果您使用的是.NET 4.0,命名参数是合理的,因为它们不是那么多的代码,并且会改善调用者的代码。

1

我没有看到任何错误的方式离开公共构造函数。在决定是否采用工厂方法时,我倾向遵循一些规则。

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

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

0

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

但是我们经常会有很多复合对象,每个对象的创建过于复杂。这就是为什么我们正在寻找封装组合对象创建逻辑的最佳方式。实际上,我们只有两种选择 - “工厂方法”和“对象构建器”。通过静态方法创建对象看起来有点奇怪,因为我们将对象创建逻辑放入对象中。对象构建器反过来看起来很复杂。

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

1

如果您可以从配置参数中区分核心数据成员,则创建一个构造函数,以获取所有核心数据成员,而不是其他任何地方(甚至不包含默认值—为便于阅读而配置的配置参数)。初始化配置参数以保持缺省值(在方法体内)并提供setter。在那个时候,如果你想要的对象有共同的配置,工厂方法可以为你购买一些东西。

更好的是,如果您发现有一个对象需要大量参数,则该对象可能太胖。你已经闻到了你的代码可能需要重构的事实。考虑分解你的对象。关于OO的良好文献强烈地争论小物体(例如Martin Fowler,重构; Bob Martin,Clean Code)。福勒解释如何分解大对象。例如,配置参数(如果有的话)可能表明需要更多的多态性,特别是如果它们是布尔值或枚举(重构“转换条件到多态性”)。

在给出更具体的建议之前,我需要查看对象的使用方式。福勒说,一起使用的变量应该成为他们自己的对象。所以,为了说明起见,如果您根据流派,年份和长度计算某些事物,但不计算其他属性,那么可能需要将它们一起分解到它们自己的对象中。减少必须参数的数量被传递给你的构造函数。