Design Patterns: Solidify Your C # Application Architecture with Design Patterns Chinese Edition (Part 1)
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]
[Summary: By providing a framework, design mode can solve many of the issues in application development. The mode makes the design process more clear and efficient, it is especially suitable for C # program development because C # is an object-oriented language. [Translation] Because the origin of the design mode is to describe object-oriented (reusable) software design] existing design model provides excellent template for your own class, usage models can shorten software development cycles. This article will describe a few popular design patterns, including Singleton, Strategy, Decorator, Composite, and State, you can use them in your own app, thereby increasing the application's scalability, and makes the class easier to reuse. 】
As is an unbelievable software design architecture, it is incredible to discuss software design architectures. If not all, most software applications, tools, and systems use at least one or more design patterns. The design pattern is a description of a set of interactions that provide a framework for solving general problems in a particular context environment. In other words, the mode provides a solution for specific issues in object-oriented software development. In addition, patterns generally pay attention to restricting constraints and other factors of their adaptation solutions. Connection and communication between classes and classes, and context details define a pattern that provides a solution to the problem of characteristic and essential conditions in terms of objective software design.
I must admit that I am a warm supporter in design patterns. Since I read Gamma, Helm, Johnson and Vlissides, I rarely designed software in any model. In fact, I spent considerable time in the early stage of software design to determine the model that can be naturally consistent with future architectures. After all, the model is a solution to some problems that have been tested in time and application, those issues have been resolved by experienced designers, developers and language experts. For those who are performing software design, the knowledge and expert experience of making us use is wise. It is often a good idea to use a solution that has been repeatedly proven to be a success rather than a new year.
Almost no developers can enjoy only the extravagance of only a small program. Modern applications and systems are complicated, they tend to consist of thousands of row code, and the code above these basic code is even more large. The simple mastery of tools and languages is not enough to compete for program design requirements. The company's software development generally requires great elasticity in design and architecture to adapt to the changing needs of customers in different stages of product development, and even This is often the case after the product is released. This dynamicity requires software design to be strong. It should be able to adapt to changes and will not bring any unnecessary chain reactions - should not require rewritten potential, non-coherent (sub) systems. Adding features and components to modules that do not have extended capabilities are frustrating and difficult to achieve the expected goals. Closed, no elastic design is so late, will be crushed by changes. The design pattern helps the foundation layout of the elastic architecture, and this is the common characteristics of each excellent object-oriented design.
The design pattern has been catally classified for solving the problem-level problems from fine problems and even large-scale architectures. This article will introduce several popular design patterns, in my own project, I found them very useful. Although the concept of familiar with object-oriented design helps understand this article, I don't assume that you have a preliminary knowledge of any design mode. Although any programming that is suitable for object-oriented development can be used to clarify the model, I will only use C # to write an example [Translation: But I am not j I gave a complete example of C corresponding to C # to be familiar with C The reader's comparison j], also takes this way to show the power of this language. I won't discuss any Microsoft .NET class library detail. Instead, I will focus on the tool-oriented software tools using C # languages. C # and design patterns
C # is a modern programming that promotes object-oriented software development by providing a direct mapping of object-oriented syntactic structural concepts. This is very different from C , and C supports process, object-oriented and generic programming. Even so, if you are a C programmer, follow-up C # is very easy. For C programmers, this learning curve is quite flat. Even if you have never seen any C # code before, it is understood that the sample code of this article should not have any problems. In fact, if you find that C # is clearer with the implementation of the design mode, I will not have any surprises, especially if you used the design mode to write code. General discussion of books and articles in design patterns and articles will describe the problems and context details to solve in the mode, and then provide a description of a specification solution. This article is not so strict, I only pay attention to the nature of the pattern, and supplemented by appropriate C # examples.
Let us start from the simplest design pattern: Singleton.
Singleton
Any developer who writes MFC applications (regardless of the application of the application is small), know what is Singleton. Singleton is the only instance of the class. When using the MFC, the global instance of the application class derived from the CWINAPP is Singleton. Of course, in the MFC application, although the second instance of the application class is not allowed, there is nothing to stop you. [Translation: In fact, whether it is VC6.0 or VC7.0Beta2, their compilers can limit you a second instance of you to a certain extent. It is sure to a certain extent, because the compiler, such as this case does not help you check - attempting to create a second instance of the application class in a button event in the form, in this case, when you need some A specific class shows that a better alternative is that this class is responsible for ensuring that only one is created and only one instance. Go back to MFC, we know that the responsibility to ensure the uniqueness of the application class instance is left to the developed programmer, he (she) must be careful not to create the second instance of the application class.
Now let's take a look at the classes shown in Table 1. Singleton's access is limited to the must pass the static method INSTANCE. In most cases, Singleton should have global visibility, which can be implemented by creating methods public. Unlike the global variable simulation Singleton, this mode prevents the creation of excess instances while both global visibility. Note that the constructor of this class is placed to private, which means that there is no way to bypass the static method instance to directly create an instance of the class.
Table 1
Class Singleton
{
Private static singleleton singleton = null;
Public Static Singleton Instance ()
{
IF (null == singleleton)
Singleton = new singleleton ();
Return Singleton;
Private singleton ()
{
}
}
The role of the Singleton mode is more than this, especially if it can be extended to create a variable number of variable numbers. Assume that there is an application that needs to schedule a worker thread when a specific task is required. Considering the saving system resources, we use Singleton to implement this thread class. Soon, the task that needs Singleton thread processing has become delayed. If we decide to extend this app, we can easily increase the number of worker threads because all logic of thread creation and all logic of their access authorization are defined in one. Class.
Another advantage of the Singleton mode is that when Singleton's creation can be delayed to really need, as shown in Table 1. No matter whether it is necessary, the global variable is created at the beginning, but this global object is not necessarily required. C # does not support global variables, but it is also possible to create an object on a plurality of beginnings and use it for a long time. As such, the Singleton mode provides an elegant solution for this case.
In addition, as a tool, in the implementation of the Singleton mode, C # is better than C , although this advantage is very subtle, but absolutely important. Based on C implementation requires consideration of some of Singleton's tricky issues related to life management, while C # is automatically handled by runtime. This advantage is meaningful, in the C # implementation version of the Singleton mode, you only need to guarantee that you have a living reference when you need Singleton.
[Translation: The following is a complete example of the Singleton mode
C # example:
Using system;
Class Singleton
{
Private static singleleton singleton = null;
Public Static Singleton Instance ()
{
IF (null == singleleton)
Singleton = new singleleton ();
Return Singleton;
}
Private singleton ()
{
}
}
Class Application
{
Public static void main ()
{
Singleton s1 = singleton.instance ();
// Singleton S2 = New Singleton (); // Error: Constructor is not accessible
Singleton S2 = Singleton.instance ();
IF (S1.Equals (S2)) // Quote
Console.writeline ("Instances Are Identical");
}
}
/ * The following is the program output result:
Instances Are Identical
* /
C example: [Translation: Translator in its own program practice, almost never settled the statement and implementation of the classes, the example code here is so writing, just to facilitate everyone to read and compare]
#include "stdafx.h";
#include
Class Singleton
{
PUBLIC:
Static singleleton * instance ()
{
IF (null == singleton) singleton = new singleleton ();
Return Singleton;
}
Private:
Singleton ()
{
}
PRIVATE: STATIC SINGLETON * SINGLETON
}
Singleton * Singleton :: Singleton = NULL;
INT _Tmain (int Argc, _tchar * argv [])
{
Singleton * sgt1 = singleton :: instance ();
Singleton * sgt2 = singleleton :: instance ();
IF (SGT1 == SGT2)
COUT << "Instances are identical / n";
Delete sgt1; // [Translation: This simple example is the J] that does not have memory leaks or tricky life-management issues.
Return 0;
}
/ * The following is the program output result:
Instances Are Identical
* /
】
STRATEGY
Applications often do different tasks that are often implemented by user input, run platform, and deployment environments. For an asynchronous I / O for disk file, Win32 API under Windows NT / 2000 provides native support for asynchronous I / O, but Windows 95/98 is not the case. Therefore, the application that relies on asynchronous file I / O must implement two sets of different algorithms, depending on the deployment environment, a set of Win32 APIs can be used, and the other can use multi-thread technology from the header. For customers, it is generally not aware of the implementation of different algorithms, and he cares about the same result, he (she) only cares about this.
Another example is the download file from the remote server on the Internet. Provide a file download service app accepts a URL and an agreement (such as FTP or HTTP), then create an object using the protocol and remote server communication. Different algorithms (protocols) are used depending on the user's different inputs. But the result is the same: Download a file.
Let us look at a more specific example: prime test. The code shown in Table 2 declares an interface (one concept in C #), which only has one method: Isprime.
Table 2
Interface Strategy
{
Bool isprime (int N);
}
The interface is like a contract, which is a specification that all derived classes must follow. More specifically, it defines the signature of the method but does not implement them, and the specific class of the implementation interface must provide the implementation of these methods. At this point, C # is significantly better than C because C lacks the native support for the interface in the language. C programmers typically create an interface by defining an abstract class with a pure virtual member function. In C #, all interface members are public, and all the classes of all implementations must implement all the methods in the interface.
Now suppose we have three different prime test algorithms, each has its own performance and precision indicators. One of them belongs to the computational intensive, thorough testing the factor, and the other algorithm is relatively fast, but the result of the majority is not necessarily accurate. My application is to ask what executable to execute the user, and then call the corresponding algorithm based on its selection. I encapsulate the algorithm to a number of categories that implement the Strategy interface. See Table 3 Sample Code.
table 3
Class Fermat: Strategy
{
Public Bool Isprime (INT N)
{
Bool result = false;
// Test N is the number of prime numbers using the FERMAT method, then update the value of Result
Console.writeline ("Using Fermat's Test");
Return Result;
}
}
After all three algorithms are implemented in this way, I can design a client program with one way to implement detail with any particular algorithm. The customer holds an interface reference and does not have to know any details that the interface is implemented. See Table 4 Code. Table 4
Class Primality
{
PRIVATETEGY Strategy;
Public Primality (Strategy S)
{
Strategy = S;
}
Public Bool Test (Int N)
{
Return Strategy.isprime (n);
}
}
Finally, I created an instance of a primary class, and initialized it in accordance with the user input. The Test method of the Primality class calls the ISPrime method of the corresponding algorithm object (to realize the object of the STRATEGY interface).
It has many advantages to constructing an algorithm in this way, but the biggest advantage is that the customer program has no coupling relationship with a specific algorithm. This improves the scalability - can develop other algorithms and seamlessly insert them as long as they follow the basic interface specifications. This can dynamically change the algorithm. Moreover, the Strategy mode also avoids the possibility of making the customer code confusing because the conditional statement is used. [Translation: Do you understand the meaning of this sentence? J】
[Translation: The following is a complete example of the Strategy mode
C # example:
Using system;
Interface Strategy
{
Bool isprime (int N);
}
Class Miller: Strategy
{
Public Bool Isprime (INT N)
{
Bool result = false;
// Test the N whether the number is used by the MILLER method, and the result is updated.
Console.writeline ("Using Miller's Algorithm);
Return Result;
}
}
Class Fermat: Strategy
{
Public Bool Isprime (INT N)
{
Bool result = false;
// Test N whether the N is the number of prime numbers, if you use the FERMAT method, then update the result value
Console.writeline ("Using Fermat's Algorithm);
Return Result;
}
}
Class Mersenne: Strategy
{
Public Bool Isprime (INT N)
{
Bool result = false;
// Test N whether the N is the number of prime numbers, if you use the Mersenne method, then update the result value
Console.writeline ("Using Mersenne's Algorithm);
Return Result;
}
}
Class Primality
{
PRIVATETEGY Strategy;
Public Primality (Strategy S)
{
Strategy = S;
}
Public Bool Test (Int N)
{
Return Strategy.isprime (n);
}
}
Class Application
{
Public static void main ()
{
Console.write ("Number to Be Tested:");
String Input = console.readline ();
INT n = int32.parse (Input);
Console.Write ("Desired Algorithm Performance: Lo, Medium, Hi?"); Input = Console.Readline ();
CHAR CH = char.parse (input);
PRIMALITY PRIME = NULL;
Switch (CH)
{
Case 'L':
Case 'L':
PRIME = New primality (new miller ());
Break;
Case 'M':
Case 'M':
Prime = New primality (new fermat ());
Break;
Case 'h':
Case 'h':
PRIME = New primality (New Mersenne ());
Break;
}
IF (Prime! = NULL)
{
Bool result = prime.test (n);
}
Else
Console.writeline ("Bad Choice!");
}
}
/ * The following is a test output result:
Number to be tsted: 1
Desired Algorithm Performance: Lo, Medium, Hi? M
Using Fermat's Algorithm
* /
C example:
#include "stdafx.h";
#include
Class Strategy
{
PUBLIC:
Virtual bool isprime (int N) = 0;
}
Class Miller: Public Strategy
{
PUBLIC:
Bool isprime (int N)
{
Bool result = false;
// Test the N whether the number is used by the MILLER method, and the result is updated.
COUT << "Using Miller's Algorithm / N";
Return Result;
}
}
Class Fermat: Public Strategy
{
PUBLIC:
Bool isprime (int N)
{
Bool result = false;
// Test N whether the N is the number of prime numbers, if you use the FERMAT method, then update the result value
Cout << "Using Fermat's Algorithm / N";
Return Result;
}
}
Class Mersenne: Public Strategy
{
PUBLIC:
Bool isprime (int N)
{
Bool result = false;
// Test N whether the N is the number of prime numbers, if you use the Mersenne method, then update the result value
COUT << "Using Mersenne's Algorithm / N";
Return Result;
}
}
Class Primality
{
Private:
Strategy * statate;
PUBLIC:
PRIMALITY (Strategy * S)
{
Strategy = S;
}
~ Primality ()
{
IF (Strategy! = NULL)
{
DELETE STRATEGY;
Strategy = NULL;
}
}
Bool Test (int N)
{
Return strategy-> isprime (n);
}
INT _Tmain (int Argc, _tchar * argv [])
{
COUT << "Number to be tsted:";
Int n;
CIN >> N;
Cout << "Desired Algorithm Performance: Lo, Medium, Hi?"
CHAR CH;
CIN >> CH;
PRIMALITY * Prime = NULL;
Switch (CH)
{
Case 'L':
Case 'L':
PRIME = New primality (new miller ());
Break;
Case 'M':
Case 'M':
Prime = New primality (new fermat ());
Break;
Case 'h':
Case 'h':
PRIME = New primality (New Mersenne ());
Break;
}
IF (Prime! = NULL)
{
Bool result = prime-> test (n);
Delete prime;
Prime = NULL;
// This also demonstrates a bad design, you can't release the Miller or Fermat or Mersenne object, know the reason?
}
Else
COUT << "Bad Choice! / N";
Return 0;
}
/ * The following is a test output result:
Number to be tsted: 1
Desired Algorithm Performance: Lo, Medium, Hi? M
Using Fermat's Algorithm
* /
】