小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

條款7:將值類型盡可能實現(xiàn)為具有常量性和原子性的類型

 蘭亭文藝 2018-01-17
具有常量性的類型很簡單,它們自創(chuàng)建后便保持不變。如果在構(gòu)造的時候就驗證了參數(shù)的有效性,我們就可以確保從此之后它都處于有效的狀態(tài)。因為我們不可能再更改其內(nèi)部狀態(tài)。通過禁止在構(gòu)建對象之后更改對象狀態(tài),我們實際上可以省卻許多必要的錯誤檢查。具有常量性的類型同時也是線程安全的:多個reader可以訪問同樣的內(nèi)容。因為如果內(nèi)部狀態(tài)不可能改變,那么不同線程也就沒有機會獲得同一數(shù)據(jù)的不同值。具有常量性的類型也可以安全地暴露給外界,因為調(diào)用者不可能改變對象的內(nèi)部狀態(tài)。具有常量性的類型在基于散列(hash)的集合中也表現(xiàn)得很好,因為由Object.GetHashCode()方法返回的值必須是一個不變量(參見條款10),而具有常量性的類型顯然可以保證這一點。
然而,并非所有類型都可以為常量類型。如果那樣的話,我們將需要克隆對象來改變程序的狀態(tài)。這也就是為什么本條款同時針對具有常量性和原子性的值類型。我們應該將我們的類型分解為各種可以自然形成單個實體的結(jié)構(gòu)。比如,Address類型就是這樣的例子。一個Address對象是由多個相關字段組成的單一實體。其中一個字段的更改可能意味著需要更改其他字段。而Customer類型就不具有原子性。一個Customer類型可能包含許多信息:地址(address)、名稱(name)以及一個或者多個電話號碼(phone number)。這些獨立信息中的任何一個都可能更改。一個Customer對象可能要更改它的電話號碼,但并不需要更改地址;也可能更改它的地址,而仍然保留同樣的電話號碼;也可能更改它的名稱,但保留同樣的電話號碼和地址。因此,Customer對象并不具有原子性。但它由各個不同的原子類型組成:一個地址、一個名稱或者一組電話號碼/類型對[13]。具有原子性的類型都是單一的實體:我們通常會直接替換一個原子類型的整個內(nèi)容。但有時候也有例外,比如更改構(gòu)成它的幾個字段。
下面是一個典型的可變類型Address的實現(xiàn):
// 可變結(jié)構(gòu)Address。
public struct Address
{
  private string  _line1;
  private string _line2;
  private string  _city;
  private string _state;
  private int    _zipCode;
  // 依賴系統(tǒng)產(chǎn)生的默認構(gòu)造器。
  public string Line1
  {
    get { return _line1; }
    set { _line1 = value; }
  }
  public string Line2
  {
    get { return _line2; }
    set { _line2 = value; }
  }
  public string City
  {
    get { return _city; }
    set { _city= value; }
  }
  public string State
  {
    get { return _state; }
    set
    {
      ValidateState(value);
      _state = value;
    }
  }
  public int ZipCode
  {
    get { return _zipCode; }
    set
    {
      ValidateZip( value );
      _zipCode = value;
    }
  }
  // 忽略其他細節(jié)。
}
// 應用示例:
Address a1 = new Address( );
a1.Line1 = "111 S. Main";
a1.City = "Anytown";
a1.State = "IL";
a1.ZipCode = 61111 ;
// 更改:
a1.City ="Ann Arbor"; // ZipCode、State 現(xiàn)在無效。
a1.ZipCode = 48103; // State 現(xiàn)在仍然無效。
a1.State = "MI"; // 現(xiàn)在整個對象正常。
內(nèi)部狀態(tài)的改變意味著有可能違反對象的不變式 (invariant)——至少是臨時性地違反。在我們將City字段更改之后,a1就處于無效的狀態(tài)了。City更改后便不再與State或者ZipCode匹配。上面的代碼看起來好像沒什么問題,但是假設這段代碼是一個多線程程序的一部分,那么任何在City更改過程中的上下文切換都可能導致 另一個線程看到不一致的數(shù)據(jù)視圖。
即使我們并不是在編寫多線程應用程序,上面的代碼仍然存在問題。假設ZipCode的值無效,因此拋出了一個異常。這時候我們實際上僅做了一部分改變,對象將處于一個無效的狀態(tài)。為了修復這個問題,我們需要在Address結(jié)構(gòu)中添加相當多的內(nèi)部校驗代碼。這無疑將增加代碼的體積和復雜性。為了完全實現(xiàn)異常安全,我們還需要在所有改變多個字段的代碼塊處放上防御性的代碼。線程安全也要求我們在每一個屬性訪問器(get和set)上添加線程同步檢查??偠灾?,這將是一個相當可觀的工作——而且我們還要考慮隨著時間的推移,功能的增加,以及代碼可能的擴展。
相反,讓我們將Address結(jié)構(gòu)實現(xiàn)為常量類型。首先,要將所有的實例字段都更改為只讀字段:
public struct Address
{
  private readonlystring  _line1;
  private readonly string  _line2;
  private readonly string  _city;
  private readonly string  _state;
  private readonly int   _zipCode;
  // 忽略其他細節(jié)。
}
同時要刪除每個屬性的所有set訪問器:
public struct Address
{
  // ...
  public string Line1
  {
    get { return _line1; }
  }
  public string Line2
  {
    get { return _line2; }
  }
  public string City
  {
    get { return _city; }
  }
  public string State
  {
    get { return _state; }
  }
  public int ZipCode
  {
    get { return _zipCode; }
  }
}
現(xiàn)在我們得到了一個常量類型。為了讓其可用,我們還需要添加必要的構(gòu)造器來徹底初始化Address結(jié)構(gòu)。目前看來,Address結(jié)構(gòu)只需要一個構(gòu)造器來為其每一個字段賦值。復制構(gòu)造器就不必要了, 因為C#默認的賦值操作符已經(jīng)足夠高效了。記住,默認的構(gòu)造器仍然是有效的。使用默認構(gòu)造器創(chuàng)建的Address對象中所有的字符串將為null,而 zipCode將為0:
public struct Address
{
  private readonly string  _line1;
  private readonly string  _line2;
  private readonly string  _city;
  private readonly string  _state;
  private readonly int   _zipCode;
  public Address( string line1, stringline2,  string city, string state, int zipCode)
  {
    _line1 = line1;
    _line2 = line2;
    _city = city;
    _state = state;
    _zipCode = zipCode;
    ValidateState( state );
    ValidateZip( zipCode );
  }
  // 忽略其他細節(jié)。
}
要改變常量類型,我們需要創(chuàng)建一個新對象,而非在現(xiàn)有的實例上做修改:
// 創(chuàng)建一個Address:
Address a1 = new Address( "111 S. Main", "", "Anytown", "IL",61111 );
//使用重新初始化的方式來改變對象:
a1 = new Address( a1.Line1, a1.Line2, "Ann Arbor","MI", 48103 );
現(xiàn)在a1只可能處于以下兩個狀態(tài)中的一個:原來位于Anytown的位置,或者位于Ann Arbor的新位置。我們將不可能再像前面的例子中那樣把一個現(xiàn)有的Address對象更改為任何無效的臨時狀態(tài)。那些無效的中間態(tài)只可能存在于 Address構(gòu)造器的執(zhí)行過程中,不可能出現(xiàn)在構(gòu)造器之外。只要一個Address對象被構(gòu)造好后,它的值將保持恒定不變。新版的Address也是異常安全的:a1或者為原來的值,或者為新構(gòu)造的值。如果有異常在新的Address對象的構(gòu)造過程中被拋出,那么a1將保持原來的值。
對于常量類型,我們還要確保沒有任何漏洞會導致其內(nèi)部狀態(tài)被更改。由于值類型不支持派生類型,因此我們不必擔心派生類型會更改其字段。但我們需要注意常量類型中的可變引用類型字段。當我們?yōu)檫@樣的類型實現(xiàn)構(gòu)造器時,需要對其中的可變類型進行防御性的復制。下面的例子假設Phone為一個具有常量性的值類型,因為我們只關心值類型的常量性:
// 下面的類型為狀態(tài)的改變留下了漏洞。
public struct PhoneList
{
  private readonly Phone[] _phones;
  public PhoneList( Phone[] ph )
  {
    _phones = ph;
  }
  public IEnumerator Phones
  {
    get
    {
      return_phones.GetEnumerator();
    }
  }
}
Phone[] phones = new Phone[10];
// 初始化phones
PhoneList pl = new PhoneList( phones );
// 改變phones數(shù)組:
// 同時也改變了常量類型的內(nèi)部狀態(tài)。
phones[5] = Phone.GeneratePhoneNumber( );
我們知道,數(shù)組是一個引用類型。這意味著 PhoneList結(jié)構(gòu)內(nèi)部引用的數(shù)組和外部的phones數(shù)組引用著同一塊內(nèi)存空間。這樣開發(fā)人員就有可能通過修改phones來修改常量結(jié)構(gòu) PhoneList。為了避免這種可能性,我們需要對數(shù)組做一個防御性的復制。上面的例子展示的是一個可變集合類型可能存在的漏洞。如果Phone為一個可變的引用類型,那么將更具危害性。在這種情況下,即使集合類型可以避免更改,集合中的值仍然可能會被更改。這時候,我們就需要對這樣的類型在所有構(gòu)造器中做防御性的復制了——事實上只要常量類型中存在任何可變的引用類型,我們都要這么做:
// 常量類型: 構(gòu)造時對可變的引用類型進行復制。
public struct PhoneList
{
  private readonly Phone[] _phones;
  public PhoneList( Phone[] ph )
  {
     _phones = new Phone[ph.Length ];
     // 因為Phone是一個值類型,所以可以直接復制值。
     ph.CopyTo(_phones, 0 );
  }
  public IEnumerator Phones
  {
    get
    {
      return_phones.GetEnumerator();
    }
  }
}
Phone[] phones = new Phone[10];
// 初始化phones
PhoneList pl = new PhoneList( phones );
// 改變phones數(shù)組:
// 不會改變pl中的副本。
phones[5] = Phone.GeneratePhoneNumber( );
當要返回一個可變的引用類型時,我們也要遵循同樣的規(guī)則。例如,如果我們要添加一個屬性來從PhoneList結(jié)構(gòu)中獲取整個數(shù)組,那么其中的訪問器也要創(chuàng)建一個防御性的復制。更多細節(jié)可參見條款23。
初始化常量類型通常有三種策略,選擇哪一種策略依賴于一個類型的復雜度。定義一組合適的構(gòu)造器通常是最簡單的策略。例如,上述的Address結(jié)構(gòu)就是通過定義一個構(gòu)造器來負責初始化工作。
我們也可以創(chuàng)建一個工廠方法(factory method)來進行初始化工作。這種方式對于創(chuàng)建一些常用的值比較方便。.NET框架中的Color類型就采用了這種策略來初始化系統(tǒng)顏色。例如,靜態(tài)方法Color.FromKnownColor()和Color.FromName()可以根據(jù)一個指定的系統(tǒng)顏色名,來返回一個對應的顏色值。
最后,對于需要多個步驟操作才能完整構(gòu)造出一個常量類型的情況,我們可以通過創(chuàng)建一個可變的輔助類來解決。.NET中的String類就采用了這種策略,其輔助類為System.Text.StringBuilder。我們可以使用StringBuilder類通過多步操作來創(chuàng)建一個String對象。在執(zhí)行完所有必要的操作后,我們便可以通過StringBuilder類來獲取期望的String對象。
具有常量性的類型使得我們的代碼更加易于編寫和維護。我們不應該盲目地為類型中的每一個屬性都創(chuàng)建get和set訪問器。對于目的是存儲數(shù)據(jù)的類型來說,我們應該盡可能地將它們實現(xiàn)為具有常量性和原子性的值類型。在這些類型的基礎上,我們可以很容易地構(gòu)建更復雜的結(jié)構(gòu)。

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多