如何在 C# 中为具有引用类型属性的对象创建构建器?

发布于 2024-08-03 21:29:45 字数 1351 浏览 5 评论 0原文

我已经开始构建一个构建器,以便我可以轻松地为我正在编写的单元测试创​​建测试数据。

构建器的基本结构是:

public class MyClassBuilder
{
    public int id = 0; //setting the default value here

    //allows MyClass to be built with a specific id
    public MyClassBuilder WithId(int id)
    {
        this.id = id;
        return this;
    }

    public MyClass Build()
    {
        return new MyClass(id);
    }
}

该模式的用法则变为:

MyClass mc = new MyClassBuilder().WithId(5).Build();

我对此感到满意...但我有疑问的是当 MyClass 具有一个不平凡的属性时... .我有点不确定如何使用默认值构建它。

public class MyClassBuilder
{
    public int id = 0; //setting the default value here

    //do I construct the default value using a MySecondClassBuilder?
    public MySecondClass mySecondClass;

    //allows MyClass to be built with a specific id
    public MyClassBuilder WithId(int id)
    {
        this.id = id;
        return this;
    }

    public MyClassBuilder WithMySecondClass(MySecondClass mySecondClass)
    {
        this.mySecondClass = mySecondClass;
    }

    public MyClass Build()
    {
        return new MyClass(id);
    }
}

我的假设是我将为 MySecondClass 创建一个构建器并使用它来创建默认实现。

任何人都可以确认我的假设是正确的并且是最佳实践吗?

我目前正在测试我的假设,但我想我应该在 StackOverflow 上记录这个想法,因为我只能使用 google 找到构建器模式的唯一示例,仅构建值类型而不是引用类型的属性。

I've started to construct a builder so that I can easily create test data for unit tests I am writing.

The basic structure of the builder is:

public class MyClassBuilder
{
    public int id = 0; //setting the default value here

    //allows MyClass to be built with a specific id
    public MyClassBuilder WithId(int id)
    {
        this.id = id;
        return this;
    }

    public MyClass Build()
    {
        return new MyClass(id);
    }
}

The usage of this pattern then becomes:

MyClass mc = new MyClassBuilder().WithId(5).Build();

I'm comfortable with that...but where I have questions is when MyClass has a property that is non trivial....I'm a little uncertain about how to go about constructing it with a default value.

public class MyClassBuilder
{
    public int id = 0; //setting the default value here

    //do I construct the default value using a MySecondClassBuilder?
    public MySecondClass mySecondClass;

    //allows MyClass to be built with a specific id
    public MyClassBuilder WithId(int id)
    {
        this.id = id;
        return this;
    }

    public MyClassBuilder WithMySecondClass(MySecondClass mySecondClass)
    {
        this.mySecondClass = mySecondClass;
    }

    public MyClass Build()
    {
        return new MyClass(id);
    }
}

My assumption is that I would create a builder for MySecondClass and use that to create the default implementation.

Can anyone confirm that my assumption is correct and is the best practice?

I'm currently in the process of testing out my assumption but I thought I'd document this idea on StackOverflow since the only examples of the builder patter that I could find using google only ever constructed properties that are value types and not reference types.

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

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

发布评论

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

评论(2

被翻牌 2024-08-10 21:29:45

与大多数事情一样 - 这取决于情况。

如果您想要在 mySecondClass 字段中引用语义(即:您想要保存对另一个类的引用),那么它与您的整数完全相同。将默认值设置为 null 是完全可以接受的。

但是,如果您希望始终保证一个值,则需要为第二个类构建或仅构建一个新对象。

另外,如果您想要克隆而不是通过引用复制,则需要构造一个新对象并将其分配给引用。

话虽如此,在许多情况下,传统的构造函数比尝试构建流畅的构建风格界面更合适。像这样的构建过程很容易“搞乱”流畅的界面,因为与传统的构造函数相比,您更多地依赖于用户。

举个例子,在您的代码中,如果用户从不调用 Build 会发生什么?它仍然返回一些东西(因为每个方法都返回一个对象),但是它有效吗?作为你们班的消费者,我不确定。另一方面,具有重载的传统构造函数可以准确地向我显示什么是有效的,什么是无效的,并且是编译时强制执行的。

As with most things - it depends.

If you want reference semantics in the mySecondClass field (ie: you want to save a reference to another class), then it's exactly like your integer. Having the default be null could be perfectly acceptable.

However, if you want to always guarantee a value, you'll need to either build or just construct a new object for your second class.

Also, if you want cloning instead of by reference copies, you'd need to construct a new object and assign it to the reference.

That being said, in many cases, traditional constructors are more appropriate than trying to do a fluent build-style interface. It's really easy to "mess up" a fluent interface for building like this, since you're relying on the user more than with a traditional constructor.

As an example, in your code, what happens if the user never calls Build? It's still returning something (since each of the methods returns an object), but is it valid? I wouldn't be sure as a consumer of your class. Traditional constructors with overloads, on the other hand, show me exactly what is valid and what is not valid, and are compile-time enforced.

零度° 2024-08-10 21:29:45

一旦我有了一个复杂的对象,我想要在单元测试中使用一个虚拟对象,我通常会转向模拟/伪造/隔离框架。在 C# 中,我更喜欢 Moq,但也有几个,比如 Rhino Mocks、Typemock 等。

这样做的好处是,它们通常能够用默认值和空对象清除原始类型中存在的所有属性,而无需执行任何操作,只需使用单行语句来创建假对象。同时,您可以轻松配置对象,以返回与测试相关的属性所需的值。此外,大多数此类框架都支持递归伪造,因此类型中的复杂对象也会被伪造,等等。

人们可能不想使用单独的框架是有原因的,在这种情况下,我认为 Reed Copsey 的建议将是手动方式。

如果没有任何理由避免添加模拟框架(假设您已经将测试分开,这只会成为您的测试项目的依赖项),我会衷心推荐它用于复杂类型。

Once I have a complex object where I want a dummy to use in unit tests, I'll generally turn to a mocking/faking/isolation framework. In C#, my preference is Moq, but there are several out there, like Rhino Mocks, Typemock and so on.

The benefit there is that they will typically have the capability to stub out all properties that exist in the original type with default values and empty objects, without you having to do anything other than a single line statement to create the fake object. At the same time, you can easily configure the object to return the values you want for those of the properties that matter to your test. Also, most such frameworks support faking recursively, so that the complex objects inside your type will also get faked, and so on.

There are reasons why one might not want to use a separate framework, in which case I think Reed Copsey's suggestion would be the manual way to do it.

If there aren't any reasons to avoid adding a mocking framework (which would only be a dependency to your test projects, assuming you've split testing out), I would heartily recommend it for complex types.

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