使用可变对象作为构造函数参数

时间:2016-01-21 09:33:41

标签: c# design-patterns

将对象作为构造函数参数传递的最佳做法是什么?传递可变对象可能会导致意外结果。

一个简单的例子。我们期望200,但得到10000调用TestMethod():

public class Test
{
    public int TestMethod()
    {
        var variable1 = new SomeMeasurements
        {
            Width = 10,
            Height = 20
        };
        var obj1 = new MyRectangle(variable1);

        // <... more code...>

        variable1.Height = 1000; // a local variable was reused here, and it's field was changed

        // <... more code...>

        return obj1.GetArea();
    }
}

public class SomeMeasurements
{
    public int Width { get; set; }
    public int Height { get; set; }
}


public class MyRectangle
{
    SomeMeasurements _arg;

    public MyRectangle(SomeMeasurements arg)
    {
        _arg = arg;
    }

    public int GetArea()
    {
        return _arg.Width * _arg.Height;
    }
}

在这种情况下,错误很明显,但是对于更复杂的类,调试可能很繁琐。我想到了如何解决这个问题的几件事:

选项1。修复TestMethod() - 创建variable1后不得更改MyRectangle

选项2。修复类SomeMeasurements - 将其转换为结构:

public struct SomeMeasurements
{
    public int Width { get; set; }
    public int Height { get; set; }
}

选项3。修复类SomeMeasurements - 使其成为不可变的:

public class SomeMeasurements
{
    public SomeMeasurements(int width, int height)
    {
        Width = width;
        Height = height;
    }

    public int Width { get; }
    public int Height { get; }
}

选项4。修复类MyRectangle正文 - 它不能使用可变对象:

public class MyRectangle
{
    int _height;
    int _width;

    public MyRectangle(SomeMeasurements arg)
    {
        _height = arg.Height;
        _width = arg.Width;
    }

    public int GetArea()
    {
        return _width * _height;
    }
}

选项5。制作SomeMeasurements ICloneable并在Clone()构造函数中使用MyRectangle

这些选项中的任何一个都存在缺陷 - 可能很难避免重复使用variable1,MyRectangle可能会更复杂地将其转换为结构,MyRectangle可能是外部的,您可能无法更改它什么是最正确的解决方法?

2 个答案:

答案 0 :(得分:2)

通常,您应该传递符合某个接口的服务,或仅传递构造函数中的不可变对象。如果你想保护它免受外部变化的影响,构造函数应该获取传递给它的任何可变数据的副本。

一旦数据通过构造函数,它应该被视为新实例状态的一部分,并且不应该在该实例之外进行修改。

您的选择3,4似乎最有用。选项2可以解决问题,因为您将数据的副本传递给构造函数。在许多情况下,选项1可能无法控制。

答案 1 :(得分:1)

这取决于类之间的关系,以及它们的目的。

如果你考虑从StreamReader实例构造的Stream类,那么Stream预期将继续是“它自己的”可变类,它有自己的一套责任,而读者以给定的方式处理其可变性。这两个对象之间存在持续的关系,如果在这里对Stream做了一些事情,那么期望它会影响读者。

在这种情况下,我们显然只是对传递给构造函数的Stream进行了引用。

在其他情况下,传递一个对象来表示正在创建的对象的初始状态。两者之间没有持续的关系。

这里最好复制传递的对象或其字段。 (当谈到微操作时,复制字段会使初始构造变得非常慢,并且使用它们的速度会稍微快一些。)

您正在处理的是您正在设计的内容,因为您可以决定让课程以任何方式运作。有些情况显然必须是一个或另一个(在StreamReader示例中,没有理由永远不会坚持你正在处理的Stream,但通常有一个选择。赞成最少惊喜的原则,如果您仍然无法决定复制方法,那么对象之间没有持续的关系,因为您的依赖关系现在变得更加简单。