Effective C# 原則24:選擇申明式編程而不是命令式編程(譯)

來源:互聯網
上載者:User

Effective C# 原則24:選擇申明式編程而不是命令式編程

Item 24: Prefer Declarative to Imperative Programming

與命令式編程相比,申明式編程可以用更簡單,更清楚的方法來描述軟體的行為。申明式編程就是說用申明來定義程式的行為,而不是寫一些指令。在C#裡,也和其它大多數語言一樣,你的大多數程式都是命令式的:在程式中寫一個方法來定義行為。在C#中,你在編程時使用特性就是申明式編程。你添加一個特性到類,屬性,資料成員,或者是方法上,然後.Net運行時就會為你添加一些行為。這樣申明的目的就是簡單易用,而且易於閱讀和維護。

讓我們以一個你已經使用過的例子開始。當你寫你的第一個ASP.Net Web服務時,嚮導會產生這樣的代碼:

[WebMethod]
public string HelloWorld()
{
  return "Hello World";
}

VS.net的Web服務嚮導添加了[WebMethod]特性到HelloWorld()方法上,這就定義了HelloWorld是一個web方法。ASP.net運行時會為你產生代碼來響應這個特性。運行時產生的Web服務描述語言(WSDL)文檔,也就是包含了對SOAP進行描述的文檔,調用HelloWorld方法。ASP.net也支援運行時發送SOAP請求HelloWorld方法。另外,ASP.net運行時動態產生HTML面頁,這樣可以讓你在IE裡測試你的新Web服務。而這些全部是前面的WebMethod特性所響應的。這個特性申明了你的意圖,而且運行時確保它是被支援的。使用特性省了你不少時間,而且錯誤也少了。

這並不是一個神話,ASP.net運行時使用反射來斷定類裡的哪些方法是web服務,當它們發現這些方法時,ASP.net運行時就添加一些必須的架構代碼到這些方法上,從而使任何添加了這些代碼的方法成為web方法。

[WebMethod] 特性只是.Net類庫眾多特性之一,這些特性可能協助你更快的建立正確的程式。有一些特性協助你建立序列化類別型(參見原則25)。正如你在原則4裡看到的,特性可以控制條件編譯。在這種情況以下其它一些情況下,你可以使用申明式編程寫出你所要的更快,更少錯誤的代碼。

你應該使用.Net架構裡內建的一些特性來申明你的意圖,這比你自己寫要好。因為這樣花的時間少,更簡單,而且編譯器也不會出現錯誤。

如果預置的特性不適合你的需求,你也可以通過定義自己的特性和使用反射來使用申明式編程結構。做為一個例子,你可以建立一個特性,然而關聯到代碼上,讓使用者可以使用這個特性來建立預設可以排序的類型。一個例子示範了如何添加這個特性,該特性定義了你想如何在一個客戶集合中排序:

[DefaultSort( "Name" )]
public class Customer
{
  public string Name
  {
    get { return _name; }
    set { _name = value; }
  }

  public decimal CurrentBalance
  {
    get { return _balance; }
  }

  public decimal AccountValue
  {
    get
    {
      return calculateValueOfAccount();
    }
  }
}

DefaultSort特性,Nane屬性,這就暗示了任何Customer的集合應該以客戶名字進行排序。DefaultSort特性不是.Net架構的一部份,為了實現它,你建立一個DefaultSortAttribute類:
[AttributeUsage( AttributeTargets.Class |
  AttributeTargets.Struct )]
public class DefaultSortAttribute : System.Attribute
{
  private string _name;
  public string Name
  {
    get { return _name; }
    set { _name = value; }
  }

  public DefaultSortAttribute( string name )
  {
    _name = name;
  }
}

同樣,你還必須寫一些代碼,來對一個集合運行排序,而該集合中的元素是添加了DefaultSort特性的對象。你將用到反射來發現正確的屬性,然後比較兩個不同對象的屬性值。一個好訊息是你只用寫一次這樣的代碼。

下一步,你要寫一個實現了IComparer介面的類。(在原則26中會詳細的充分討論比較。) ICompare有一個CompareTo()方法來比較兩個給定類型的對象,把特性放在實現了IComparable的類上,就可以定義排序次序了。建構函式對於通用的比較,可以發現預設的排序屬性標記,而這個標記是基於已經比較過的類型。Compare方法對任何類型的兩個對象進行排序,使用預設的排序屬性:

internal class GenericComparer : IComparer
{
  // Information about the default property:
  private readonly PropertyDescriptor _sortProp;

  // Ascending or descending.
  private readonly bool _reverse = false;

  // Construct for a type
  public GenericComparer( Type t ) :
    this( t, false )
  {
  }

  // Construct for a type
  // and a direction
  public GenericComparer( Type t, bool reverse )
  {
    _reverse = reverse;
    // find the attribute,
    // and the name of the sort property:

    // Get the default sort attributes on the type:
    object [] a = t.GetCustomAttributes(
      typeof( DefaultSortAttribute ),false );

    // Get the PropertyDescriptor for that property:
    if ( a.Length > 0 )
    {
      DefaultSortAttribute sortName = a[ 0 ] as   DefaultSortAttribute;
      string name = sortName.Name;

      // Initialize the sort property:
      PropertyDescriptorCollection props =
        TypeDescriptor.GetProperties( t );
      if ( props.Count > 0 )
      {
        foreach ( PropertyDescriptor p in props )
        {
          if ( p.Name == name )
          {
            // Found the default sort property:
            _sortProp = p;
            break;
          }
        }
      }
    }
  }

  // Compare method.
  int IComparer.Compare( object left,
    object right )
  {
    // null is less than any real object:
    if (( left == null ) && ( right == null ))
      return 0;
    if ( left == null )
      return -1;
    if ( right == null )
      return 1;

    if ( _sortProp == null )
    {
      return 0;
    }

    // Get the sort property from each object:
    IComparable lField =
      _sortProp.GetValue( left ) as IComparable;
    IComparable rField =
      _sortProp.GetValue( right ) as IComparable;
    int rVal = 0;
    if ( lField == null )
      if ( rField == null )
        return 0;
      else
        return -1;
    rVal = lField.CompareTo( rField );
    return ( _reverse ) ? -rVal : rVal;
  }
}

這個通用的比較對任何Customers 集合可以進行排序,而這個Customers是用DefaultSort特性申明了的:

CustomerList.Sort( new GenericComparer(
  typeof( Customer )));

實現GenericComparer的代碼利用了一些進階的技術,使用反射(參見原則43)。但你必須寫一遍這樣的代碼。從這個觀點上看,你所要做的就是添加空上屬性到其它任何類上,然而你就可以對這些對象的集合進行能用的排序了。如果你修改了DefaultSort特性的參數,你就要修改類的行為。而不用修改所有的演算法。

這種申明式習慣是很有用的,當一個簡單的申明可以說明你的意圖時,它可以協助你避免重複的代碼。再參考GenericComparer類,你應該可以為你建立的任何類型,寫一個不同的(而且是是直接了當的)排序演算法。這種申明式編程的好處就是你只用寫一次能用的類型,然後就可以用一個簡單的申明為每個類型建立行為。關鍵是行為的改變是基於單個申明的,不是基於任何演算法的。GenericComparer可以在任何用DefaultSort特性修飾了的類型上工作,如果你只須要在程式裡使用一兩次排序功能,就按常規簡單的方法寫吧。然而,如果你的程式對於同樣的行為,可能須要在幾十個類型上實現,那麼能用的演算法以及申明式的解決方案會省下很多時間,而且在長時間的運行中也是很有力的。你不應該為WebMethod特性寫代全部的代碼,你應該把這一技術展開在你自己的演算法上。原則42裡討論了一個例子:如何使用特性來建立一個附加命令控制代碼。其它的例子可能還包括一些在定義附加元件封裝建立動態web UI面頁時的其它內容。

申明式編程是一個很有力的工具,當你可以使用特性來表明你的意圖時,你可以通過使用特性,來減少在大量類似的手寫演算法中出現邏輯錯誤的可能。申明式編程建立了更易於閱讀,清晰的代碼。這也就意味著不管是現在還是將來,都會少出現錯誤。如果你可以使用.Net架構裡定義的特性,那就直接使用。如果不能,考慮選擇建立你自己的特性,這樣你可以在將來使用它來建立同樣的行為。
===========================

   

Item 24: Prefer Declarative to Imperative Programming
Declarative programming can often be a simpler, more concise way to describe the behavior of a software program than imperative programming. Declarative programming means that you define the behavior of your program using declarations instead of by writing instructions. In C#, as in many other languages, most of your programming is imperative: You write methods that define the behavior of your programs. You practice declarative programming using attributes in C#. Youattach attributes to classes, properties, data members, or methods, and the .NET runtime adds behavior for you. This declarative approach is simpler to implement and easier to read and maintain.

Let's begin with an obvious example that you've already used. When you wrote your first ASP.NET web service, the wizard generated this sequence of code:

[WebMethod]
public string HelloWorld()
{
  return "Hello World";
}

 

The VS .NET Web Service wizard added the [WebMethod] attribute to the HelloWorld() method. That declared HelloWorld as a web method. The ASP.NET runtime creates code for you in response to the presence of this attribute. The runtime created the Web Service Description Language (WSDL) document, which contains a description for the SOAP document that invokes the HelloWorld method. ASP.NET also adds support in the runtime to route SOAP requests to your HelloWorld method. In addition, the ASP.NET runtime dynamically creates HTML pages that enable you to test your new web service in IE. That's all in response to the presence of the WebMethod attribute. The attribute declared your intent, and the runtime ensured that the proper support was there. Using the attribute takes much less time and is much less error prone.

It's really not magic. The ASP.NET runtime uses reflection to determine which methods in your class are web methods. When they are found, the ASP.NET runtime can add all the necessary framework code to turn any function into a web method.

The [WebMethod] attribute is just one of many attributes that the .NET library defines that can help you create correct programs more quickly. A number of attributes help you create serializable types (see Item 25). As you saw in Item 4, attributes control conditional compilation. In those and other cases, you can create the code you need faster and with less chance for errors using declarative programming. You should use these .NET Framework attributes to declare your intent rather than write your own code. It takes less time, it's easier, and the compiler doesn't make mistakes.

If the predefined attributes don't fit your needs, you can create your own declarative programming constructs by defining custom attributes and using reflection. As an example, you can create an attribute and associated code to let users create types that define the default sort order using an attribute. A sample usage shows how adding the attribute defines how you want to sort a collection of customers:

[DefaultSort( "Name" )]
public class Customer
{
  public string Name
  {
    get { return _name; }
    set { _name = value; }
  }

  public decimal CurrentBalance
  {
    get { return _balance; }
  }

  public decimal AccountValue
  {
    get
    {
      return calculateValueOfAccount();
    }
  }
}

 

The DefaultSort attribute e, the Name property. The implication is that any collection of Customers should be ordered by the customer name. The DefaultSort attribute is not part of the .NET Framework. To implement it, you need to create the DefaultSortAttribute class:

[AttributeUsage( AttributeTargets.Class |
  AttributeTargets.Struct )]
public class DefaultSortAttribute : System.Attribute
{
  private string _name;
  public string Name
  {
    get { return _name; }
    set { _name = value; }
  }

  public DefaultSortAttribute( string name )
  {
    _name = name;
  }
}

 

You must still write the code to sort a collection of objects based on the presence of the DefaultSort attribute. You'll use reflection to find the correct property and then compare values of that property in two different objects. The good news is that you need to write this code only once.

Next, you create a class that implements IComparer. (Comparers are discussed in more detail in Item 26.) IComparerhas a version of CompareTo() that compares two objects of a given type, letting the target class, which implements IComparable, define the sort order. The constructor for the generic comparer finds the default sort property descriptor based on the type being compared. The Compare method sorts two objects of any type, using the default sort property:

internal class GenericComparer : IComparer
{
  // Information about the default property:
  private readonly PropertyDescriptor _sortProp;

  // Ascending or descending.
  private readonly bool _reverse = false;

  // Construct for a type
  public GenericComparer( Type t ) :
    this( t, false )
  {
  }

  // Construct for a type
  // and a direction
  public GenericComparer( Type t, bool reverse )
  {
    _reverse = reverse;
    // find the attribute,
    // and the name of the sort property:

    // Get the default sort attributes on the type:
    object [] a = t.GetCustomAttributes(
      typeof( DefaultSortAttribute ),
      false );

    // Get the PropertyDescriptor for that property:
    if ( a.Length > 0 )
    {
      DefaultSortAttribute sortName = a[ 0 ] as
        DefaultSortAttribute;
      string name = sortName.Name;

      // Initialize the sort property:
      PropertyDescriptorCollection props =
        TypeDescriptor.GetProperties( t );
      if ( props.Count > 0 )
      {
        foreach ( PropertyDescriptor p in props )
        {
          if ( p.Name == name )
          {
            // Found the default sort property:
            _sortProp = p;
            break;
          }
        }
      }
    }
  }

  // Compare method.
  int IComparer.Compare( object left,
    object right )
  {
    // null is less than any real object:
    if (( left == null ) && ( right == null ))
      return 0;
    if ( left == null )
      return -1;
    if ( right == null )
      return 1;

    if ( _sortProp == null )
    {
      return 0;
    }

    // Get the sort property from each object:
    IComparable lField =
      _sortProp.GetValue( left ) as IComparable;
    IComparable rField =
      _sortProp.GetValue( right ) as IComparable;
    int rVal = 0;
    if ( lField == null )
      if ( rField == null )
        return 0;
      else
        return -1;
    rVal = lField.CompareTo( rField );
    return ( _reverse ) ? -rVal : rVal;
  }
}

 

The Generic comparer sorts any collection of Customers based on the property declared in the DefaultSort attribute:

CustomerList.Sort( new GenericComparer(
  typeof( Customer )));

 

The code to implement the GenericComparer makes use of advanced techniques, such as reflection (see Item 43). But you need to write it only once. From that point on, all you need to do is add the attribute to any class, and you can sort a collection of those objects using the generic comparer. If you change the parameter on the DefaultSort attribute, you change the class's behavior. You don't need to change any algorithms anywhere in your code.

This declarative idiom is useful to avoid writing repetitivecode when a simple declaration can specify your intent. Look at the Generic Comparer class again. You could write different (and slightly simpler) versions of the sort algorithm for every type you created. The advantage to using declarative programming is that you can write one generic class and let a simple declaration create the behavior for each type. The key is that the behavior changes based on a single declaration, not based on any algorithm changes. The GenericComparer works for any type decorated with the DefaultSort attribute. If you need sorting functionality only once or twice in your application, write the simpler routines. However, if you might need the same behavior for many tens of different types in your program, the generic algorithm and the declarative solution will save you time and energy in the long run. You'd never write all the code generated by the WebMethod attribute. You should expand on that technique for your own algorithms. Item 42 discusses one example: how to use attributes to build add-on command handlers. Other examples might include anything from defining add-on packages to building dynamic web page UIs.

Declarative programming is a powerful tool. When you can use attributes to declare your intent, you save the possibility of logic mistakes in multiple similar hand-coded algorithms. Declarative programming creates more readable, cleaner code. That means fewer mistakes now and in the future. If you can use an attribute defined in the .NET Framework, do so. If not, consider the option of creating your own attribute definition so that you can use it to create the same behavior in the future.
 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.