Objective C # Principle 24: Declarative Programming instead of imperative Programming
Item 24: prefer declarative to imperative Programming
Compared with imperative programming, Declarative Programming can describe software behavior in a simpler and clearer way. Declarative Programming is defined by declarativeProgramInstead of writing some commands. In C #, like most other languages, most of your programs are imperative: Write a method in the program to define behavior. In C #, the feature you use in programming is Declarative Programming. You add a feature to a class, attribute, data member, or method, and then some actions will be added for you during. Net runtime. This statement is intended to be easy to use and easy to read and maintain.
Let's start with an example that you have already used. When you write your first ASP. NET web service, the wizard will generateCode:
[Webmethod]
Public String helloworld ()
{
Return "Hello World ";
}
In the Web Service Wizard of vs.net, the [webmethod] feature is added to the helloworld () method, which defines helloworld as a web method. The Asp.net runtime will generate code for you to respond to this feature. The Web Service Description Language (WSDL) document generated at runtime, that is, the document containing the description of soap, calls the helloworld method. Asp.net also supports sending the SOAP request helloworld method at runtime. In addition, Asp.net dynamically generates HTML pages during runtime, so that you can test your new Web Service in IE. All of these are the responses of the previous webmethod features. This feature declares your intent and ensures that it is supported during runtime. Using features saves you a lot of time and reduces errors.
This is not a myth. Asp.net uses reflection to determine which methods in the class are Web services. When they discover these methods, when Asp.net is running, some necessary framework code is added to these methods, so that any method that adds the code becomes a web method.
[Webmethod] features are only one of the many features of the. NET class library. These features may help you create the right program faster. Some features help you create serialization types (see Principle 25 ). As you can see in principle 4, features can control Conditional compilation. In the following situations, you can use Declarative Programming to write code that is faster and less error-prone.
You should use some of the features in the. NET Framework to declare your intention, which is better than writing it yourself. This is because it takes less time and is simpler, and the compiler will not make any errors.
If the preset features are not suitable for your needs, you can also use Declarative Programming structures by defining your own features and using reflection. As an example, you can create a feature that is associated with the code so that you can use this feature to create a default sortedtype. An example demonstrates how to add this feature, which defines how you want to sort in a customer set:
[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 feature and Nane attribute imply that any customer set should be sorted by the customer name. The defaultsort feature is not part of the. NET Framework. to implement it, you create a 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;
}
}
Similarly, you must write some code to sort a set, and the elements in the set are the objects with the defaultsort feature added. You will use reflection to find the correct attributes and then compare the attribute values of two different objects. The good news is that you only need to write such code once.
Next, you need to write a class that implements the icomparer interface. (The comparison will be discussed in detail in principle 26 .) Icompare has a compareto () method to compare two objects of the given type and place the features on the class implementing icomparable to define the sorting order. The constructor can find the default sorting attribute tag Based on the compared type. The compare method sorts two objects of any type and uses the default sorting attribute:
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;
}
}
This general comparison can sort any MERs set, and this customers is stated using the defaultsort feature:
Customerlist. Sort (New genericcomparer (
Typeof (customer )));
The Code implementing genericcomparer utilizes some advanced technologies to use reflection (see principle 43 ). But you must write this code again. From this point of view, all you have to do is add an empty attribute to any other class, but you can sort the set of these objects in usable order. If you modify the defaultsort parameter, You need to modify the class behavior. Instead of modifying allAlgorithm.
This declarative habit is very useful. When a simple statement can demonstrate your intention, it can help you avoid repeated code. Refer to the genericcomparer class. You should be able to write a different (and straightforward) Sorting Algorithm for any type you create. The advantage of this Declarative Programming is that you only need to write a usable type once, and then you can use a simple declarative statement to create behavior for each type. The key is that behavior changes are based on a single declaration, not any algorithm. Genericcomparer can work on any type modified with the defaultsort feature. If you only need to use the sort function once or twice in the program, simply write it in a simple way. However, if your program may need to implement the same behavior on dozens of types, the algorithm and declarative solution that can be used will save a lot of time, in addition, it is very powerful in a long period of operation. You should not write all the code for the webmethod feature. You should expand this technology on your own algorithm. In principle 42, an example is discussed: How to Use features to create an additional command handle. Other examples may include other content when defining additional packages to create dynamic Web UI pages.
Declarative Programming is a powerful tool. When you can use features to express your intentions, you can use them, to reduce the possibility of logical errors in a large number of similar handwritten algorithms. Declarative Programming creates code that is easier to read and clearer. This means that there will be fewer errors in the present and future. If you can use the features defined in the. NET Framework, you can directly use them. If not, consider creating your own features so that you can use them to create the same behavior in the future.
======================================
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 your other versions, 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. 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. 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 your attributes that. 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 shoshould 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 MERs:
[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 MERs shocould be ordered by the customer name. the defaultsort attribute is not part of. 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, leader 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]
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 MERs 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 repetivecode when a simple declaration can specify your intent. look at the generic comparer class again. you coshould 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 your 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 shoshould 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. 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.