封装就是将相关的方法或者属性抽象成为一个对象。
封装的意义:
- 对外隐藏内部实现,接口不变,内部实现自由修改。
- 只返回需要的数据和方法。
- 提供一种方式防止数据被修改。
- 更好的代码复用。
当一个类的属性类型为集合,或者方法返回类型为集合时,如果符合以下条件,我们就可以考虑将集合进行封装:
- 返回的数据仅用于展示
- 当集合的Add,Remove方法包含其它业务逻辑
向类的调用者隐藏类中的完整集合有如下几个好处:
- 保证返回的集合数据不会被修改。
- 在Add, Remove方法中可以添加验证,日志或其他业务逻辑。
代码示例:
using System.Collections.Generic; namespace LosTechies.DaysOfRefactoring.EncapsulateCollection.Before { public class Order { private List<OrderLine> _orderLines; private double _orderTotal; public IList<OrderLine> OrderLines { get { return _orderLines; } } public void AddOrderLine(OrderLine orderLine) { _orderTotal += orderLine.Total; _orderLines.Add(orderLine); } public void RemoveOrderLine(OrderLine orderLine) { orderLine = _orderLines.Find(o => o == orderLine); if (orderLine == null) return; _orderTotal -= orderLine.Total; _orderLines.Remove(orderLine); } } public class OrderLine { public double Total { get; private set; } } }
上面的代码在Add或者Remove orderLine时存在业务逻辑,如果调用时直接修改OrderLines的元素,就会产生bug,所以需要重构如下:
using System.Collections.Generic; namespace LosTechies.DaysOfRefactoring.EncapsulateCollection.After { public class Order { private List<OrderLine> _orderLines; private double _orderTotal; //方法一:返回IEnumerable类型 public IEnumerable<OrderLine> OrderLines { get { return _orderLines.Skip(0); } } //方法二:返回只读类型 public ReadOnlyCollection<OrderLine> OrderLines { get { return _orderLines.AsReadOnly(); } } public void AddOrderLine(OrderLine orderLine) { _orderTotal += orderLine.Total; _orderLines.Add(orderLine); } public void RemoveOrderLine(OrderLine orderLine) { orderLine = _orderLines.Find(o => o == orderLine); if (orderLine == null) return; _orderTotal -= orderLine.Total; _orderLines.Remove(orderLine); } } public class OrderLine { public double Total { get; private set; } } }
注意:虽然直接返回IEnumerable,这样只能遍历取出它的值,但是还是可以通过转换为List后操作集合中的元素,所以我们采用_orderLines.Skip(0)迭代返回,这样就能阻止调用者转换为list。