Design Patterns: Solidify Your C # Application Architecture With Design Patterns Chinese Edition (middle)
Author: Samir Bajaj
Translator: glory
[Decoration: C # advanced article. The translator made a simple finishing of the C # example provided by Samir (the author provided in the translator's environment) and writes the corresponding C example, and placed in the translation. All C #, C program debugging environments in the translation are Microsoft Visual Studio.Net 7.0 Beta2]
Decorator client applications often need to strengthen the services provided by certain classes. You may need to insert some pre-processed and subsequent processing from before and after the method call. To achieve this goal, an approach is to simply do different ways to call. However, this way is not only troublesome, but also is not conducive to the expansion of the framework. For example, if you have different pre-processed and subsequent processing tasks for different customers, application logic will become unclear because of conditional statements and difficult to maintain. The problem is how to enhance the features provided by the class without affecting the customer code, and the Decorator model is just the required.
Let's take an example of a class with a remote file transfer function. Such class code may be shown in Table 5.
table 5
Class FileTransfer
{
Public Virtual Void Download (String Url, Byte [] Data, Int Size
{
// download file
}
Public Virtual Void Upload (String Url, Byte [] Data, Int size)
{
// upload files
}
} Assume that a client is interested in this feature. In addition to being able to upload and download files, client applications also want to write all file transfer requests and execute access to log. One implementation based on Decorator mode is a class and overloading the virtual method from the FileTransfer class, and inserts additional code before or after calling the base class method. As shown in Table 6.
Table 6
Class Decorator: FileTransfer
{
Private filetransfer ft = new filetransfer ();
Private bool isaccessallowed (String URL)
{
BOOL RESULT = True;
/ / Decide whether to authorize the requested URL
Return Result;
}
Private void logaccess (String URL)
{
// write information such as URL, time, user identity to the database
Console.writeline ("Logging Access To {0}", URL);
}
Public Override Void Download (String Url, Byte [] Data, Int size)
{
IF (! isaccessallowed (URL))
Return;
FT.Download (URL, DATA, SIZE);
Logaccess (URL);
}
} The client can continue to work with the same interface [translation: not C # semantic interface]. In fact, it is also possible to further improve this solution to change the FileTransfer class and Decorator classes to classes that implement the same UPLOAD and DOWNLOAD methods. In this way, the client can make only according to the interface, and the same implementation is thoroughly decoupled.
When a scope of extension and tasks can be performed on the basis of existing classes, and all expansions are defined as classes are unrealistic, using Decorator mode can dynamically, transparently add or remove functions without affecting Customer code. [Translation: The following is a complete example of Decorator mode
C # example:
Using system;
Class FileTransfer
{
Public Virtual Void Download (String Url, Byte [] Data, Int Size
{
// download file
}
Public Virtual Void Upload (String Url, Byte [] Data, Int size)
{
// upload files
}
}
Class Decorator: FileTransfer
{
Private filetransfer ft = new filetransfer ();
Private bool isaccessallowed (String URL)
{
BOOL RESULT = True;
/ / Decide whether to authorize the requested URL
Return Result;
}
Private void logaccess (String URL)
{
// write information such as URL, time, user identity to the database
Console.writeline ("Logging Access To {0}", URL);
}
Public Override Void Download (String Url, Byte [] Data, Int size)
{
IF (! isaccessallowed (URL)) Return;
FT.Download (URL, DATA, SIZE);
Logaccess (URL);
}
Public override void upload (String Url, Byte [] Data, Int size)
{
IF (! isaccessallowed (URL)) Return;
ft.upload (URL, DATA, SIZE);
Logaccess (URL);
}
}
Class Application
{
Public static void main ()
{
Console.write ("Enter Url to Access:");
String Url = console.readline ();
Console.write ("Enable Logging and Access Check?");
String Input = console.readline ();
CHAR CH = char.parse (input);
Bool Decoration = (CH == 'Y' || CH == 'Y');
FileTransfer ft = NULL;
IF (! Decoration)
FT = New FileTransfer ();
Else
ft = new decorator ();
BYTE [] BUF = New byte [1024];
ft.download (URL, BUF, 1024);
}
}
/ * The following is a certain runtime output result:
Enter Url to Access: www.9cbs.net
ENABLE Logging and Access Check? Y
Logging Access to www.9cbs.net
* /
C example: [Translation: The reason why the following example mixed std :: string and byte array are just to make it easy for the C # program]
#include "stdafx.h";
#include
Using namespace std;
TYPEDEF UNSIGNED CHAR BYTE
Class FileTransfer
{
PUBLIC:
Virtual Void Download (String Url, Byte Data [], INT Size
{
// download file
}
Virtual Void Upload (String Url, Byte Data [], INT Size)
{
// upload files
}
}
Class Decorator: Public FileTransfer // Decorated File Transfer
{
Private:
FileTransfer * ft;
Bool isaccessallowed (String URL)
{
BOOL RESULT = True;
/ / Decide whether to authorize the requested URL
Return Result;
}
Void logaccess (String URL)
{
// write information such as URL, time, user identity to the database
COUT << "Logging Access to" << URL << Endl;
}
PUBLIC:
Decorator ()
{
FT = New FileTransfer ();
}
~ Decorator ()
{
IF (ft)
{
Delete ft;
ft = NULL;
}
}
Void Download (String Url, Byte Data [], INT Size
{
IF (! isaccessallowed (URL)) Return;
FT-> Download (URL, DATA, SIZE);
Logaccess (URL);
}
Void Upload (String Url, Byte Data [], INT Size
{
IF (! isaccessallowed (URL)) Return;
FT-> UPLOAD (URL, DATA, SIZE);
Logaccess (URL);
}
}
INT _Tmain (int Argc, _tchar * argv [])
{
COUT << "Enter Url to Access:";
String URL;
CIN >> URL;
Cout << "Enable Logging and access check? type y or y to payue:";
CHAR CH;
CIN >> CH;
Bool Decoration = (CH == 'Y' || CH == 'Y');
FileTransfer * ft = NULL;
IF (! Decoration)
FT = New FileTransfer ();
Else
ft = new decorator ();
BYTE * BUF = New byte [1024];
FT-> Download (URL, BUF, 1024);
DELETE [] BUF;
IF (ft! = null)
{
Delete ft;
ft = NULL;
}
Return 0;
}
/ * The following is a certain runtime output result:
Enter Url to Access: www.9cbs.net
ENABLE Logging and Access Check? Y
Logging Access to www.9cbs.net
* /
】
Composite
When you need to handle aggregation objects and individual objects in a consistent manner, the Composite mode is sent. [Translation: This "aggregation" is not COM semantic gathering] A common example is to list the contents of the folder. Folders may not include files, or may have subfolders. Recursive lists of a top folder can use conditional statements to distinguish files and subfolders, and can print all files in the subfolders by traversing the directory tree. A better solution for this problem is to use the Composite mode. Using this method, each item in a folder, whether it is file, subfolder, network printer, or any of the alias of any directory element, is an instance of a class of the same interface, which provides a certain The method describes the user-friendly names of these elements. In this way, customer applications do not have to distinguish every different elements, which also reduces the complexity of application logic. Another example, is also what I have to use C # language is a drawing application. It extracts basic and combined graphical elements from the object database and draws them on the canvas. Assume that the database can accommodate Line, Circle, and Drawing (inclusive LINE and CIRCLE). Let us see the interface shown in Table 7.
Table 7
Interface shape
{
Void Draw ();
}
Interface Shape has a method DRAW. Simple graphics objects such as Line can implement this interface, overload method DRAW [translation: INTERFACE in C # and its implementation, does not need Virtual or Override keyword, so it is said to be "overload", it is better to say is Implementation to draw lines on the canvas. See Table 8.
Table 8
Class Line: Shape
{
PRIVATE DOUBLE X1, Y1, X2, Y2;
Public line (Double X1, Double Y1, Double X2, Double Y2)
{
THIS.X1 = X1; this.y1 = Y1;
THIS.X2 = x2; this.y2 = y2;
}
Public void Draw ()
{
/ / Draw a line from (x1, y1) to (x2, y2)
}
} The Circle class is similar to the Line class. In order to be able to consistently handle the aggregation class and simple physical classes, the aggregation objects should also implement the Shape interface. Drawing is a collection class of a graphic object that implements a DRAW method, which lists all of its basic graphics objects, and draws them one by one. The code of Table 9 shows its working principle.
Table 9
Class Drawing: Shape
{
PRIVATE ARRAYLIST Shapes;
Public Drawing () PUBLIC DRAWING ()
{
Shapes = new arraylist ();
}
Public Void Add (Shape S)
{
Shapes.Add (s);
}
Public void Draw ()
{
IEnumerator Enumerator = shapes.GeteNumerator ();
While (enumerator.movenext ())
(Shape) enumerator.current) .draw ();
}
} Note that customers do not need to care about a graphic object belong to a basic type and a collection type. A common interface across them [translation: here is Shape] Make we can add new objects to it [Translation: New Type Object] without any impact on the client.
Through the common interface, the Composite mode avoids the customer program to distinguish individual objects and container objects, which promotes code reuse in considerable extent and simplifies customer program logic.
Note that the DRAW method of the Drawing class uses the class in the System.Collections namespace. For more knowledge of class libraries, see the relevant documents for the .NET Framework SDK. [Translation: The following is a complete example of the Composite mode
C # example:
Using system;
Using system.collections;
Interface shape
{
Void Draw ();
}
Class Line: Shape
{
PRIVATE DOUBLE X1, Y1, X2, Y2;
Public line (Double X1, Double Y1, Double X2, Double Y2)
{
THIS.X1 = x1;
THIS.Y1 = Y1;
THIS.X2 = x2;
THIS.Y2 = Y2;
}
Public void Draw ()
{
/ / Draw a line from (x1, y1) to (x2, y2)
Console.Writeline ("Drawing a line");
}
}
Class Circle: Shape
{
PRIVATE DOUBLE X, Y, R;
Public Circle (Double X, Double Y, Double Radius)
{
THIS.X = X;
THIS.Y = Y;
THIS.R = R;
}
Public void Draw ()
{
// With (x, y) is round, R is a radius
Console.writeline ("Drawing a circle");
}
}
Class Drawing: Shape
{
PRIVATE ARRAYLIST Shapes;
Public Drawing () PUBLIC DRAWING ()
{
Shapes = new arraylist ();
}
Public Void Add (Shape S)
{
Shapes.Add (s);
}
Public void Draw ()
{
IEnumerator Enumerator = shapes.GeteNumerator ();
While (enumerator.movenext ())
(Shape) enumerator.current) .draw ();
}
}
Class Application
{
Public static void main ()
{
Shape [] array = new shape [3];
Array [0] = New line (0, 0, 10, 12);
Array [1] = New Circle (2, 3, 5.5);
Drawing DWG = New Drawing ();
DWG.Add (New Line (3, 4, 3, 5));
DWG.Add (New Circle (5, 6, 7.7));
Array [2] = DWG;
/ / Draw all the graphics, pay attention: Access all objects in a consistent way
For (int i = 0; i <3; i)
Array [i] .draw ();
}
}
/ * The following is the program output result:
Drawing a line
Drawing a circle
Drawing a line
Drawing a circle
* /