【问题标题】:Property that is set only within protected constructor仅在受保护的构造函数中设置的属性
【发布时间】:2020-01-16 17:33:41
【问题描述】:

我有以下课程,想知道使用私有 setter 将 ListOfItems 声明为 public 是否正确(从 OOP 最佳实践的角度来看)。

public abstract class Game
{
    public List<int> ListOfItemss { get; private set; }

    protected Game()
    {
    }

    protected Game(string gameInput)
    {
        ListOfItems = new List<int>();
        var gameParser = new GameParser();
        ListOfItems = gameParser.ParseGameInputString(gameInput);
    }

    public abstract int Bowl(int items);
}

或者我应该通过私有字段,即

public abstract class Game
{
    private List<int> _listOfItems;

    public List<int> ListOfItemss 
    {
            get { return _listOfItems; }
            private set { _listOfItems = value; }
        }

    protected Game()
    {
    }

    protected Game(string gameInput)
    {
        ListOfItems = new List<int>();
        _listOfItems = new List<int>(); 
        var gameParser = new GameParser();
        _listOfItems = gameParser.ParseGameInputString(gameInput);
    }

    public abstract int Bowl(int items);
}

    -

根据建议更新: 根据以下建议,这是我认为最终版本中的代码:

public abstract class Game
{
    public IReadOnlyCollection<int> ListOfItems { get; } = new List<int>();

    protected Game()
    {
    }

    protected Game(string gameInput)
    {
        var gameParser = new GameParser();
        ListOfItems = gameParser.ParseGameInputString(gameInput);
    }

    public abstract int Bowl(int items);
}

【问题讨论】:

  • 两者是一样的——唯一的区别是你在第二个实例中控制私有字段。您应该实现其中一个 - 好处是,如果您将其实现为属性而不是字段(这是上述方法之一),您可以调整属性的实现而不会破坏其他代码/重构。
  • 仅供参考,当您立即将其设置为其他内容时,您无需将其设置为新列表。在第二个中更糟糕的是,您在将其设置为最终值之前将其设置为两个新列表。
  • 是的,正如发布的答案所说 - 如果您希望分配在构造函数中并且它永远不会在代码中的其他任何地方,您可以通过删除设置器将其设为只读。您仍然可以将项目添加到列表中,只是无法分配新列表。
  • 不,这就是我和 RufusL 的重点。
  • 然后返回 ReadOnlyCollection&lt;int&gt; 而不是 List&lt;int&gt; 将是要走的路。理想情况下,您应该将该属性声明为IReadOnlyList&lt;int&gt;

标签: c# oop


【解决方案1】:

删除 setter(这实际上使其对构造函数是私有的)。正如 Rufus 所指出的,如果您不希望在课堂之外修改此列表,请不要将其公开为列表。 此外,您还允许使用空 ListOfItemss 构建游戏。我会让默认构造函数初始化列表,就像其他 c'tor 所做的那样。如果您尝试访问 game.ListOfItemss,这会使它们保持一致并避免令人讨厌的空引用异常:

public abstract class Game
{
    public IEnumerable<int> ListOfItemss { get; } = new List<int>();

    protected Game()
    {
    }

    protected Game(string gameInput)
    {
        var gameParser = new GameParser();
        ListOfItemss = gameParser.ParseGameInputString(gameInput);
    }

    public abstract int Bowl(int items);
}

【讨论】:

  • 这种情况下客户端仍然可以修改列表内容
  • 因为他允许构建没有内容,这是有道理的。如果只有参数化构造函数,您希望公共属性为 IEnumerable
  • 他(在 cmets 中)表示 “我不希望他们以任何方式修改这个列表”,所以我猜这个列表是从班级的其他地方填充的( ?)。
  • 它是一个抽象类,因此将属性暴露给派生类可能是填充列表的预期方式。制作列表属性protected 并拥有一个单独的public 只读列表/集合可以支持。
【解决方案2】:

你可以删除setter:public List&lt;int&gt; ListOfItemss { get; }

编辑:正如 cmets 中提到的,您可以获得有关 public List&lt;int&gt; ListOfItemss { get; }public List&lt;int&gt; ListOfItemss { get; private set; } here 之间区别的更多信息

【讨论】:

【解决方案3】:

坦率地说,您根本不应该提供对该列表的外部访问,这违反了封装性。即使该属性被声明为IReadOnlyList&lt;int&gt;,也没有什么能阻止某人做(List&lt;int&gt;)game.ListOfItemss。只要您限制对内部的访问,是否私有设置器并不重要。如果您需要从外部访问列表中的项目,请通过索引器等受控方法将其公开。

class Option0
{
    private readonly List<int> items;
    public int this[int index] => this.items[index];
}

应该只允许您的类或派生类访问此列表。

话虽如此,如果您想将其公开为真正的只读列表,请仅公开它的只读视图。

class Option1
{
    private readonly List<int> items;
    public IReadOnlyList<int> Items => items.AsReadOnly();
    public Option1()
    {
        this.items = ...;
    }
}

class Option2
{
    private readonly List<int> items;
    public IReadOnlyList<int> Items { get; }
    public Option2()
    {
        this.items = ...;
        this.Items = this.items.AsReadOnly();
    }
}

【讨论】:

    猜你喜欢
    • 2012-01-28
    • 2016-02-19
    • 2016-04-07
    • 2015-08-12
    • 2011-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多