【问题标题】:How are setters used with mutable objects?setter 如何与可变对象一起使用?
【发布时间】:2021-02-06 15:55:33
【问题描述】:

据我了解,C# 中的 setter 旨在强制执行业务逻辑并保留一些不变性。但这似乎与大多数类是可变的并且 getter 分发对它们的引用的事实不相容。假设我有一个Car 类:

class Car
{
    public Car(int maxSpeed)
    {
        _maxSpeed = maxSpeed;
    }

    public void UpgradeEngine()
    {
        _maxSpeed += 50;
    }

    private int _maxSpeed;

    public int max_speed
    {
        get => _maxSpeed;
    }
}

我有一个SchoolDriver 班级,不允许拥有最高速度超过 200 的汽车:

class SchoolDriver
{
    private Car _schoolBus = new Car ( 200 );
    public Car SchoolBus
    {
        get => _schoolBus;
        set
        {
            if (value.max_speed <= 200)
                _schoolBus = value;
        }
    }
}

当然,您不能分配不符合要求的新车,但您可以轻松更改现有汽车的最大速度:

class Program
{
    public static int Main()
    {
        SchoolDriver d = new SchoolDriver();
        d.SchoolBus = new Car(250); // Won't change the car.
        d.SchoolBus.UpgradeEngine(); // Will change the speed

        return 0;
    }
}

这似乎打破了不变量和封装。除了将Car 类包装在不可变类中之外,很难强制执行任何类型的不变量。

我的问题:对于具有可变对象和引用语义的 setter,什么是有效用例?如何强制执行诸如“SchoolDriver 的汽车不能超过 200 最大速度”之类的内容?

在 C++ 中,我会返回一个 const-refence,它(语义上)不能在没有邪恶的情况下被静音 const_cast

【问题讨论】:

    标签: c#


    【解决方案1】:

    在这种情况下,正确的方法实际上是使 Car 类不可变:

    class Car
    {
        public Car(int maxSpeed)
        {
           max_speed = maxSpeed;
        }
        public int max_speed
        {
            get;
        }
    }
    

    汽车的最大速度通常不会在汽车建成后改变,因此将其设为只读属性是非常合理的。

    在保存方面,当您确实有一个可变对象(并且无法更改它)时,最好根本不直接公开它。

    class SchoolDriver
    {
        private Car _schoolBus = new Car ( 200 );
        public void UpgradeCarEngine()
        {
            if (_schoolBus.max_speed < 150)
               _schoolBus.UpgradeEngine();
        }
    }
    

    还要考虑这个常见的错误:

    public class PersonList
    {
        private List<string> _names;
        public PersonList()
        {
             _names = new List<string>();
        }
    
        public List<string> Names => _names; // This property is read-only, isn't it?
        public void Add(string name)
        {
            if (!string.IsNullOrEmpty(name))
            {
                _names.Add(name);
            }
        }
    }
    
    

    属性Names 是只读的,但这并不妨碍使用它向列表中添加无效条目。

    幸运的是,该框架内置了只读包装器:

    
        public IReadOnlyList<string> Names => _names.AsReadOnly();
    

    当然,缺点是每次访问该属性都会创建一个新对象。

    【讨论】:

    • 汽车就是一个简单的例子。你可能有一个类,如果发生变异,将无法满足你的要求,所以你不想被其他人改变。我可以将该类包装在一个不可变的包装器中,但这对于我使用的每个类来说工作量都太大了。
    • @AyxanHaqverdili 提供可变包装器是一种解决方案,但首先您需要询问您是否希望ScoolDriver 完全公开其Schoolbus 属性。将该属性设为私有,没有人会弄乱它。
    • 好吧,那么我们将讨论私有字段,而不是公共属性。
    • 您可能会使用字段,而不是属性,是的。但从语义上讲,这并没有什么区别。如果您需要公开 Schoolbus 的某些功能,您可以公开 它们 而不是完整对象(即在 ScoolDriver 类上添加 SchoolBusSpeed 属性,可以进行测试)跨度>
    • 是的,基本上就是这个概念。一个类应该确保它只公开不允许以意想不到的方式改变内部状态的方法和属性。
    猜你喜欢
    • 2013-02-28
    • 1970-01-01
    • 2011-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-05
    • 2011-07-27
    • 1970-01-01
    相关资源
    最近更新 更多