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

来源:http://www.cnblogs.com/keepfool/archive/2016/06/01/5548305.html
-Advertisement-
Play Games

概述 當方法返回類型或屬性類型為集合時,有些開發者會千篇一律地使用IList集合。然而IList具有集合的所有操作,這意味著調用者不僅可以讀取集合信息,還能夠修改集合。業務需求本來只是為調用者提供一個可讀的集合,例如數據的查詢和展示,但當方法返回IList時,無疑隱式地開放了集合可寫的許可權。此時,我... ...


概述

當方法返回類型或屬性類型為集合時,有些開發者會千篇一律地使用IList<T>集合。然而IList<T>具有集合的所有操作,這意味著調用者不僅可以讀取集合信息,還能夠修改集合。業務需求本來只是為調用者提供一個可讀的集合,例如數據的查詢和展示,但當方法返回IList<T>時,無疑隱式地開放了集合可寫的許可權。此時,我們無法阻止調用者篡改集合元素。

註意:將屬性設定為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>對象。

示例

重構前

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

image

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

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

隱藏代碼
/// <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()方法來增加或刪除訂單明細。

隱藏代碼
/// <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如下:

隱藏代碼
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);
    }
}

小結

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

【關註】keepfool
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SecondaryNameNode是用來合併fsimage和edits文件來更新NameNode和metadata的。 其工作流程為: 1.secondary通知namenode切換edits文件 2.secondary從namenode獲得fsimage和edits(通過http) 3.secon ...
  • DAYOFWEEK(date) 返回日期date是星期幾(1=星期天,2=星期一,……7=星期六,ODBC標準)mysql> select DAYOFWEEK('1998-02-03'); -> 3 WEEKDAY(date) 返回日期date是星期幾(0=星期一,1=星期二,……6= 星期天)。  ...
  • (1) 選擇最有效率的表名順序(只在基於規則的優化器中有效): Oracle的解析器按照從右到左的順序處理FROM子句中的表名,FROM子句中寫在最後的表(基礎表 driving table)將被最先處理,在FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作為基礎表。假如有3個以上的表連 ...
  • 簡介 死鎖的本質是一種僵持狀態,是多個主體對於資源的爭用而導致的。理解死鎖首先需要對死鎖所涉及的相關觀念有一個理解。 一些基礎知識 要理解SQL Server中的死鎖,更好的方式是通過類比從更大的面理解死鎖。比如說一個經典的例子就是汽車(主體)對於道路(資源)的徵用,如圖1所示。 圖1.對於死鎖的直 ...
  • 引導工具GRUB詳解 引導工具GRUB詳解 引導工具GRUB詳解 導讀 引導程式是駐留在硬碟第一個扇區(MPR、主引導記錄)的程式。GRUB是一個功能強大的多系統引導程式,專門處理Linux與其它操作系統共存的問題。下麵就由我介紹一下grub.conf文件里的具體內容及其含義。 使用一下命令可以查看 ...
  • 人們對決策樹的使用 決策樹常常被應用於數據挖掘之中,是最基礎的演算法之一,幾乎每一個學習過數據挖掘的朋友都知道決策樹。但還原決策樹本來的用途,它被用於一些決策或決定時,還是比較實用和直觀的。其樹型結構指導人們進行在面對某個決策時,先關註其中幾個最重要的方向,這幾方向定下來後,再細分下去。近年來泳道路, ...
  • 公司產品中一直是採用 flash 實現文件上傳功能,但用戶的需求多了以後遇到了越來越多難以解決的問題,最後試著用碩正提供的freeform、小型頁面控制項來解決。 碩正文件上傳的實現途徑有3、4種,由於公司產品發佈的需要,就選擇了其中的 httpPost 方案,其它的象 ftp、Http put儘管也 ...
  • MSDN中的描述: Visual Studio 項目對程式的發佈和調試版本分別有單獨的配置。顧名思義,生成調試版本的目的是用於調試,而生成發佈版本的目的是用於版本的最終分發。 如果在 Visual Studio 中創建程式,Visual Studio 將自動創建這些配置並設置適當的預設選項和其他設置 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...