构造一个(有点)复杂的对象

发布于 2024-09-15 23:50:07 字数 568 浏览 5 评论 0原文

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

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

When I create classes, simple constructors tend to be the norm. On one of my current projects, a movie library, I have a Movie domain object. It has a number of properties, resulting in a constructor as follows:

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

This isn't terrible, but it's not simple either. Would it be worthwhile to use a factory method (static Movie CreateMovie(...)), or a perhaps an object builder? Is there any typical pattern for instantiating domain classes?

UPDATE: thanks for the responses. I was probably overthinking the matter initially, though I've learned a few things that will be useful in more complex situations. My solution now is to have the title as the only required parameter, and the rest as named/optional parameters. This seems the all round ideal way to construct this domain object.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

锦上情书 2024-09-22 23:50:07

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

如果您未使用 .NET 4,您可能需要使用< code>Object Builder 模式来组装您的类型。对象生成器需要花费一些精力来实现,并与您的类型保持同步 - 因此这样做是否有足够的价值取决于您的情况。

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

If you are using .NET 4.0, you can use optional/named parameters to simplify the creation of an object that accepts multiple arguments, some of which are optional. This is helpful when you want to avoid many different overloads to supply the necessary information about the object.

If you're not on .NET 4, you may want to use the Object Builder pattern to assembly your type. Object builder takes a bit of effort to implement, and keep in sync with you type - so whether there's enough value in doing so depends on your situation.

I find the builder pattern to be most effective when assembling hierarchies, rather than a type with a bunch of properties. In the latter case, I generally either overloads or optional/named parameters.

闻呓 2024-09-22 23:50:07

是的,使用工厂方法是一种典型的模式,但问题是:为什么需要它?这是维基百科对工厂方法的说法

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

因此,如果您想返回 Movie子类,工厂方法模式就有意义。如果这不是(也不会是)要求,那么用工厂方法替换公共构造函数实际上没有任何作用。

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

Yes, using a factory method is a typical pattern, but the question is: Why do you need it? This is what Wikipedia says about Factory Methods:

Like other creational patterns, it deals with the problem of creating objects (products) without specifying the exact class of object that will be created. The factory method design pattern handles this problem by defining a separate method for creating the objects, which subclasses can then override to specify the derived type of product that will be created.

So, the factory method pattern would make sense if you want to return subclasses of Movie. If this isn't (and won't be) a requirement, replacing the public constructor with a factory method doesn't really serve any purpose.

For the requirements stated in your question, your solution looks really fine to me: All mandatory fields are passed as parameters to the constructor. If none of your fields are mandatory, you might want to add a default initializer and use the C# object initializer syntax.

花开柳相依 2024-09-22 23:50:07

视情况而定。

如果这是该类的唯一构造函数,则意味着实例化该对象需要所有属性。如果这符合您的业务规则,那就太好了。如果没有的话,可能会有点麻烦。例如,如果您想在系统中植入电影,但并不总是有演员,您可能会发现自己陷入困境。

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

您有许多选项可用于安排构造函数。使用那些可以让您设计没有气味和大量原则的系统(DRY、YAGNI、SRP)。

It depends.

If that is the only constructor for that class, it means all the properties are required in order to instantiate the object. If that aligns with your business rules, great. If not, it might be a little cumbersome. If, for example, you wanted to seed your system with Movies but didn't always have the Actors, you could find yourself in a pickle.

The CreateMovie() method you mention is another option, in case you have a need to separate the internal constructor from the act of creating a Movie instance.

You have many options available to your for arranging constructors. Use the ones that allow you to design your system with no smells and lots of principles (DRY, YAGNI, SRP.)

飘逸的'云 2024-09-22 23:50:07

我没有看到你的构造函数接口有什么问题,也没有看到静态方法会给你带来什么。我会有完全相同的参数,对吧?

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

从调用者的角度来看,它看起来像这样:

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

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

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

对我来说似乎几乎一样。唯一更好的是 Create() 返回除 Movie 之外的其他具体类。

需要考虑的一件事是如何改进这一点,以便调用代码易于理解。对我来说最明显的问题是,如果不查看 Movie 的代码,就不清楚 150 的含义。如果您愿意,有几种方法可以改进:

  1. 使用电影长度类型并构造该类型 inline new MovieLength(150)
  2. 使用 命名参数如果您使用.NET 4.0
  3. (请参阅@Heinzi的答案)使用对象初始值设定项
  4. 使用流畅的界面

,你的电话看起来像

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

坦率地说,所有这些对于你的情况来说似乎都太过分了。如果您使用 .NET 4.0,命名参数是合理的,因为它们不需要太多代码,并且可以改进调用方的代码。

I don't see anything wrong with your constructor's interface and don't see what a static method will get you. I will have the exact same parameters, right?

The parameters don't seem optional, so there isn't a way to provide an overload with fewer or
use optional parameters.

From the point-of-view of the caller, it looks something like this:

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

The purpose of a factory is to provide you a customizable concrete instance of an interface, not just call the constructor for you. The idea is that the exact class is not hard-coded at the point of construction. Is this really better?

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

It seems pretty much the same to me. The only thing better is if Create() returned other concrete classes than Movie.

One thing to think about is how to improve this so that calling code is easy to understand. The most obvious problem to me is that it isn't obvious what the 150 means without looking at the code for Movie. There are a few ways to improve that if you wanted to:

  1. Use a type for movie length and construct that type inline new MovieLength(150)
  2. Use named parameters if you are using .NET 4.0
  3. (see @Heinzi's answer) use Object Initializers
  4. Use a fluent interface

With a fluent interface, your call would look like

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

Frankly, all of this seems like overkill for your case. Named parameters are reasonable if you are using .NET 4.0, because they aren't that much more code and would improve the code at the caller.

凉墨 2024-09-22 23:50:07

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

You gave a good answer to your own question, it's the factory pattern. With the factory pattern you don't need huge constructors for encapsulation, you can set the object's members in your factory function and return that object.

朮生 2024-09-22 23:50:07

恕我直言,这是完全可以接受的。我知道静态方法有时会令人不悦,但我通常将该代码放入返回类实例的静态方法中。我通常只对允许具有空值的对象执行此操作。

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

This is perfectly acceptable, IMHO. I know static methods are sometimes frowned upon, but I typically drop that code into a static method that returns an instance of the class. I typically only do that for objects that are permitted to have null values.

If the values of the object can't be null, add them as parameters to the constructor so you don't get any invalid objects floating around.

护你周全 2024-09-22 23:50:07

我认为让公共构造函数保持原样没有什么问题。以下是我在决定是否使用工厂方法时倾向于遵循的一些规则。

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

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

I see nothing wrong with leaving the public constructor the way it is. Here are some of the rules I tend follow when deciding whether to go with a factory method.

  • Do use a factory method when initialization requires a complex algorithm.
  • Do use a factory method when initialization requires an IO bound operation.
  • Do use a factory method when initialization may throw an exception that cannot be guarded against at development time.
  • Do use a factory method when extra verbage may be warranted to enhance the readability.

So based on my own personal rules I would leave the constructor the way it is.

七堇年 2024-09-22 23:50:07

如果您可以区分核心数据成员和配置参数,请创建一个只接受所有核心数据成员的构造函数(甚至不接受具有默认值的配置参数,以提高可读性)。将配置参数初始化为合理的默认值(在方法主体中)并提供设置器。那时,如果您想要的对象有通用配置,工厂方法就可以给您带来一些东西。

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

在给出更具体的建议之前,我需要了解您的对象的使用方式。福勒说,一起使用的变量应该被制作成它们自己的对象。因此,为了便于说明,如果您根据类型、年份和长度计算某些内容,而不是其他属性,那么可能需要将这些属性一起分解为各自的对象,从而减少必须计算的参数数量。被传递给你的构造函数。

If you can distinguish core data members from configuration parameters, make a constructor that takes all of the core data members and nothing else (not even configuration parameters with default values—shoot for readability). Initialize the configuration parameters to sane default values (in the body of the method) and provide setters. At that point, a factory method could buy you something, if there are common configurations of your object that you want.

Better yet, if you find you have an object that takes a huge list of parameters, the object may be too fat. You have smelled the fact that your code may need to be refactored. Consider decomposing your object. The good literature on OO strongly argues for small objects (e.g. Martin Fowler, Refactoring; Bob Martin, Clean Code). Fowler explain how to decompose large objects. For example, the configuration parameters (if any) may indicate the need for more polymorphism, especially if they are booleans or enumerations (refactoring "Convert Conditional to Polymorphism").

I would need to see the way that your object is used before giving more specific advice. Fowler says that variables that are used together should be made into their own object. So, sake of illustration, if you are calculating certain things on the basis of the genre, year and length, but not the other attributes, those together may need to be broken out in to their own object—reducing the number of parameters that must be passed to your constructor.

以为你会在 2024-09-22 23:50:07

至于我 - 一切都取决于您的域模型。如果您的领域模型允许您创建简单的对象 - 您应该这样做。

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

我认为答案在于单元测试。这正是 TDD 非常有用的情况 - 我们一步步制作领域模型并了解领域模型复杂性的需求。

As for me - all depending on your domain model. If your domain model allows you to create simple objects - you should do it.

But often we have a lot of composite objects and the creation of each individually is too complicated. That's why we`re looking for the best way to encapsulate the logic of composite object creation. Actually, we have only two alternatives described above - "Factory Method" and "Object Builder". Creating object through the static method looks a bit strange because we placing the object creation logic into the object. Object Builder, in turn, looks to complicated.

I think that the answer lies in the unit tests. This is exactly the case when TDD would be quite useful - we make our domain model step-by-step and understand the need of domain model complexity.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文