小酌重構系列[24]——封裝集合

概述

當方法返回類型或屬性類型爲集合時,有些開發者會千篇一概地使用IList<T>集合。然而IList<T>具備集合的全部操做,這意味着調用者不只能夠讀取集合信息,還可以修改集合。業務需求原本只是爲調用者提供一個可讀的集合,例如數據的查詢和展現,但當方法返回IList<T>時,無疑隱式地開放了集合可寫的權限。此時,咱們沒法阻止調用者篡改集合元素。html

注意:將屬性設定爲IList<T>類型時,即便聲明爲只讀的,咱們仍然沒法避免集合元素的篡改。
例如 public IList<Person> People{get; private set;},People屬性是一個IList集合,它雖然是隻讀的,可是People集合裏面的元素能夠被修改。

這種狀況下,咱們應該使用IEnumerable<T>來代替IList<T>。
IList<T>和IEnumerable<T>均可以遍歷集合的元素,IList<T>擁有集合的全部操做方法,包括集合元素的增長、修改和刪除。
而IEnumerable<T>則只有一個GetEnumerator方法(擴展方法除外),它返回一個可用於循環訪問集合的IEnumerator<T>對象。ide

示例

重構前

下面這段代碼定義了兩個類:Order和OrderLine。this

image

  • OrderLines屬性是隻讀的
  • Order提供了AddOrderLine()和RemoveOrderLine()方法,用於添加和刪除訂單明細

當調用者在取到Order實例時,仍然能夠經過IList<T>的Add()或Remove()方法修改OrderLines集合。
假設調用者篡改了OrderLines中的元素,而且經過另外的方法回傳了Order實例,則可能會產生一些bug。spa

/// <summary>
/// 訂單
/// </summary>
public class Order
{
    private readonly List<OrderLine> _orderLines = new List<OrderLine>();
    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(item => item == orderLine);
        if (orderLine == null)
            return;

        _orderTotal -= orderLine.Total;
        _orderLines.Remove(orderLine);
    }
}

/// <summary>
/// 訂單明細
/// </summary>
public class OrderLine
{
    public double Total { get; set; }
}

重構後

重構後,不只OrderLines集合是隻讀的,並且也只能經過AddOrderLine()和RemoveOrderLine()方法來增長或刪除訂單明細。code

/// <summary>
/// 訂單
/// </summary>
public class Order
{
    private readonly List<OrderLine> _orderLines;
    private double _orderTotal;

    public Order(List<OrderLine> orderLines)
    {
        _orderLines = orderLines;
    }

    /// <summary>
    /// 訂單明細集合是隻讀的,只能經過AddOrderLine()和RemoveOrderLine()來增長或刪除訂單明細
    /// </summary>
    public IEnumerable<OrderLine> OrderLines
    {
        get { return _orderLines; }
    }

    public double OrderTotal
    {
        get { return _orderTotal; }
    }

    public void AddOrderLine(OrderLine orderLine)
    {
        _orderTotal += orderLine.Total;
        _orderLines.Add(orderLine);
    }

    public void RemoveOrderLine(OrderLine orderLine)
    {
        orderLine = _orderLines.Find(item => item == orderLine);
        if (orderLine == null)
            return;

        _orderTotal -= orderLine.Total;
        _orderLines.Remove(orderLine);
    }
}

/// <summary>
/// 訂單明細
/// </summary>
public class OrderLine
{
    public double Total { get; set; }
}

注意:上述代碼有一些瑕疵,OrderLine類沒有重寫Object類的Equals()、GetHashCode()方法。
這行代碼orderLine = _orderLines.Find(item => item == orderLine)會使得orderLine每次都是null。
較爲完整的OrderLine以下:htm

public class OrderLine
{
    public int Id { get; set; }
    public int OrderId { get; set; }
    public double Total { get; set; }


    /// <summary>
    /// 重寫Equals方法
    /// </summary>
    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is OrderLine))
            return false;

        if (ReferenceEquals(this, obj))
            return true;

        var other = (OrderLine) obj;
        if (IsTransient() && other.IsTransient())
            return false;

        var typeOfThis = GetType();
        var typeOfOther = other.GetType();

        if (!typeOfThis.IsAssignableFrom(typeOfOther) && !typeOfOther.IsAssignableFrom(typeOfThis))
        {
            return false;
        }

        return Id.Equals(other.Id);
    }

    /// <summary>
    /// 重寫GetHashCode方法
    /// </summary>
    /// <returns></returns>
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }

    /// <summary>
    /// 是否爲瞬時對象
    /// </summary>
    /// <returns></returns>
    public virtual bool IsTransient()
    {
        return EqualityComparer<int>.Default.Equals(Id, default(int));
    }

    /// <summary>
    /// 提供==操做符,可用於對象比較
    /// </summary>
    public static bool operator ==(OrderLine left, OrderLine right)
    {
        if (Equals(left, null))
        {
            return Equals(right, null);
        }

        return left.Equals(right);
    }

    /// <summary>
    /// 提供!=操做用,可用於對象比較
    /// </summary>
    public static bool operator !=(OrderLine left, OrderLine right)
    {
        return !(left == right);
    }
}

小結

當集合做爲返回參數時,應使用適合業務需求的集合類型,不宜提供過多的集合操做給調用者。對象

相關文章
相關標籤/搜索