C#: 封装,如集合的封装

4
我想知道这些选项中哪一个被认为是最干净或最好使用的,以及为什么。
其中一个暴露了一个乘客列表,让用户添加和删除等操作。另一个则隐藏了列表,只允许用户枚举它们并使用特殊方法添加。
示例1
class Bus
{
    public IEnumerable<Person> Passengers { get { return passengers; } }
    private List<Passengers> passengers;

    public Bus()
    {
        passengers = new List<Passenger>();
    }

    public void AddPassenger(Passenger passenger)
    {
        passengers.Add(passenger);
    }
}

var bus = new Bus1();
bus.AddPassenger(new Passenger());
foreach(var passenger in bus.Passengers)
    Console.WriteLine(passenger);

示例2

class Bus
{
    public List<Person> Passengers { get; private set; }

    public Bus()
    {
        Passengers = new List<Passenger>();
    }
}

var bus = new Bus();
bus.Passengers.Add(new Passenger());
foreach(var passenger in bus.Passengers)
    Console.WriteLine(passenger);

我认为第一种类更好地实现了封装。在这种情况下,这可能是更好的方法(因为你应该确保公交车上还有空间等)。但我猜想第二种类也可能有用?比如说,如果这个类并不真正关心列表发生了什么,只要它有一个就可以了。你觉得呢?


1
这里有一个可能会有用的话题,特别是关于返回List<T>中可变性方面的讨论:https://dev59.com/PnM_5IYBdhLWcg3wyWWt - Sam Harwell
5个回答

6
在第一个示例中,您可以改变您的集合。
考虑以下内容:
var passengers = (List<Passenger>)bus.Passengers;

// Now I have control of the list!
passengers.Add(...);
passengers.Remove(...);

为了解决这个问题,您可以考虑类似以下的方法:
class Bus
{
  private List<Passenger> passengers;

  // Never expose the original collection
  public IEnumerable<Passenger> Passengers
  {
     get { return passengers.Select(p => p); }  
  }

  // Or expose the original collection as read only
  public ReadOnlyCollection<Passenger> ReadOnlyPassengers
  {
     get { return passengers.AsReadOnly(); }
  }

  public void AddPassenger(Passenger passenger)
  {
     passengers.Add(passenger);
  }
 }

0
在大多数情况下,我认为示例2是可以接受的,前提是底层类型是可扩展的和/或公开了某种形式的onAdded/onRemoved事件,以便您的内部类可以响应对集合的任何更改。
在这种情况下,List不适用,因为类无法知道是否已添加某些内容。相反,您应该使用Collection,因为Collection类具有几个虚成员(Insert、Remove、Set、Clear),可以重写并添加事件触发器以通知包装类。
(您还必须注意,类的用户可以修改列表/集合中的项目,而父类不知道它,因此请确保您不依赖于项目未更改 - 除非它们显然是不可变的 - 或者如果需要,可以提供onChanged样式事件。)

0

运行您的相应示例通过 FxCop,这将为您提供有关公开 List<T> 风险的提示。


0
我认为这完全取决于你的情况。一般来说,我会选择第二种选项,因为它是最简单的,除非你有业务原因需要加强控制。

1
最好还是从一开始就适当地封装,除非你有不这样做的商业原因。想想,当六个月后,突然出现了关于向集合中添加项目的条件的新业务规则时,会发生什么。现在你必须重构所有客户端代码,而不仅仅是一个类。 - Winston Smith

0

选项2是最简单的,但这会让其他类向集合中添加/删除元素,这可能很危险。

我认为一个好的启发式方法是考虑包装器方法的作用。如果您的AddPassenger(或Remove或其他)方法只是将调用转发到集合,则我会选择更简单的版本。如果您必须在插入它们之前检查元素,则选项1基本上是不可避免的。如果您必须跟踪已插入/已删除的元素,则可以选择任何一种方式。使用选项2,您必须在集合上注册事件以获取通知,而使用选项1,您必须为要使用的列表上的每个操作创建包装器(例如,如果您想要Insert以及Add),所以我想这取决于情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接