Improve program structure using design patterns (1)

Source: Internet
Author: User
Tags case statement

The design pattern is an elegant solution that can solve a specific problem after numerous experience summaries. However, it is not enough to know what the design pattern is and how it is implemented if you really want to make the design pattern play its biggest role, that means you cannot have a real understanding of the design patterns, and you cannot use the design patterns correctly and appropriately in your own design. This article tries to look at the design pattern from another perspective (the intent and motivation of the design pattern). With this new idea, the design pattern will become very close to your design process, in addition, it can guide and simplify your design, and will eventually export a good solution.

1. Introduction

Some designs work well at the beginning of the project during project development activities. However, as the project progresses, it is found that the existing code needs to be modified or expanded, the reasons for this are: new functional requirements and further understanding of the system. At this time, we often find it difficult to do this job, even if it can be completed, it will have to pay a lot. At this point, we must refactoring the existing code to make the subsequent work easier.

Refactoring is to improve the internal structure of the software system without changing the external behavior of the software system code. The goal of refactoring is to make the code structure more reasonable and flexible, and adapt to new needs and changes. The design pattern that provides beautiful solutions to specific problems will often become the goal of reconstruction. Once we can identify the design pattern that can solve our problems, it will greatly simplify our work, because we can reuse the work that others have already done. However, the transition between our original design and the final design model may not be smooth, but there is a gap. The result is that even if we already know a lot of design patterns and face our actual problems, we do not have an effective way to determine which design patterns are suitable for our systems, how should we apply it.

The cause of the above problems is often due to the overemphasis on the results of the solution given by the design model, but the intention of the design model and its motivation are ignored. However, it is the intention and motivation of the design model that prompted people to give a solution to a type of problem. The motivation and intention of the design model reflect the idea of the formation of the model, therefore, it is closer to our actual problems and will effectively guide our restructuring process. This document uses an instance to demonstrate this process.

The example is simplified in this article to highlight the essence of the problem and make our thinking clearer. The idea itself is the most important and fundamental. The simplified examples will not reduce the applicability of the ideas and methods we demonstrate.

2. Problem Description

A complete software system must handle errors accordingly. Only in this way can the system be robust enough. I am going to take the handling of errors in the software system as an example, to demonstrate my ideas and methods.

In a distributed network management system, an operation often fails because of one or more reasons. At this time, we must handle the failure accordingly, to minimize the impact of errors, it is best to restore the system without affecting the normal operation of the system. Another important point is to process errors while, do not forget to inform the system administrator, because only the administrator can perform further analysis on the error, so as to find out the root cause of the error and fundamentally solve the error.

Next I will start with the error handling announcement manager section to start our journey. Assume that an operation to access the database in a distributed environment may fail due to communication or database reasons. In this case, the Administrator should be notified of the error through the user interface. The simplified code example is as follows:

/* Error Code definition */
Class errorconstant
{
Public static final int error_dbaccess = 100;
Public static final int error_communication = 101;
}
/* Other functions in the user interface are omitted */
Class guisys
{
Public void announceerror (INT errcode ){
Switch (errcode ){
Case errorconstant. error_dbaccess:
/* Notify the administrator of database access errors */
Break;
Case errorconstant. error_communication:
/* Notify the administrator of a communication error */
Break;
}
}
}

At the beginning, this code works well and can complete the functions we need. However, this Code lacks the corresponding flexibility and is difficult to adapt to changes in requirements.

3. Problem Analysis

Familiar with object-oriented readers will soon find that the above Code is a typical structured method, the structured method is to organize the program structure with specific functions as the core, its encapsulation level is only 1, that is, it only encapsulates specific functions ). This makes it difficult for structured methods to adapt to changes in requirements. object-oriented methods are superior to structured methods at this point. In the object-oriented field, a program structure is composed of objects. An object has its own responsibilities and completes system functions through interaction between objects, this makes it encapsulate at least two levels, namely, the methods and data to fulfill their responsibilities. In addition, object-oriented methods also support high-level encapsulation. For example, by describing the common conceptual behaviors of different specific objects, we can achieve three levels of encapsulation-abstract classes (interfaces in Java ). The higher the level of encapsulation, the higher the level of abstraction, the higher the flexibility of design and code, and the easier to adapt to changes.

Consider the code in the previous section. If a new error is detected during system development, such as a user authentication error, how can we increase the demand for this function in our system? A Simple and Direct method is to add a case statement to handle this error. Yes, this method does work, but it is costly to do so.

First, with the further development of the system, more error types may occur, which leads to lengthy code processing for errors, which is not conducive to maintenance. Second, it is also the most fundamental point. It is easy to introduce errors by modifying code that can already work. In many cases, errors are introduced inadvertently, it is difficult to locate errors of this type. Some surveys show that there is not much time for error correction during the development process, and most of the time is for debugging and error discovery. In the Object-Oriented field, there is a well-known principle: OCP (open-closed principle). Its core implication is that a good design should be able to accommodate the increase of new functional requirements, however, you can add a new module (class) instead of modifying another module (class. If a design can follow the OCP, the above problems can be effectively avoided.

If a design conforms to the OCP principle, we are required not to simply focus on functions during design. The key to implementing OCP is abstraction, which represents a fixed behavior, but there are many different implementation methods for this behavior. Through abstraction, we can use a fixed abstract concept to replace specific concepts that are easy to change, and make the original modules dependent on which concepts are easy to change, depending on this fixed abstract concept, the result is that the increase in new requirements of the system will only increase the number of specific concepts, it does not affect other modules that depend on the abstract body of a specific concept. At the implementation level, the abstract body is described by abstract classes and interfaces in Java ). For more information about OCP, see [2].

Now that we know the nature of the problem and the corresponding solution, we will improve our code structure.

4. Preliminary plan

Let's review the code to see how to abstract it. In error handling, you need to handle different types of errors. Each specific error has its own information, but they are consistent in concept, for example: you can obtain internal error information through a specific method interface, and each error has its own processing method. A preliminary scheme can be obtained: an abstract error base class can be defined, and some methods that apply to all different specific errors are defined in this base class, each specific error can have its own different implementation of these methods. The sample code is as follows:

Interface errorbase
{
Public void handle ();
Public String getinfo ();
}
Class dbaccesserror implements errorbase
{
Public void handle (){
/* Handle database access errors */
}

Public String getinfo (){
/* Construct and return information about database access errors */
}
}
Class communicationerror implements errorbase
{
Public void handle (){
/* Handle communication errors */
}
Public String getinfo (){
/* Construct and return information about communication errors */
}
}

In this way, we can construct an actual error object in the case of an error and reference it with errorbase. Then, it is handed over to the error processing module. At this time, the error processing module only knows a type of errorbase, and does not need to know each specific error type. In this way, errors can be handled in a unified manner. Sample Code: Class guisys
{
Public void announceerror (errorbase error ){
/* Handle errors in the same way */
Error. Handle ();
}
}

As you can see, for the increase of new error types, you only need to add a specific error class, which has no effect on the error handling part. It looks perfect and complies with the OCP principles, but further analysis will show that this solution has problems. We will explain it in the next section.

5. Further analysis

The previous section provides a solution. It is perfect for a single error handler, guisys, but this is often not the case. As mentioned above, in addition to notifying users of the system, you must handle errors, such as trying to recover and recording logs. It can be seen that these processing methods are very different from releasing errors to users, and there is no way to unify all the different processing methods with only one handle method. However, if we add different processing method declarations in errorbase and implement these methods according to our own needs in specific error classes, it seems to be a good solution. The sample code is as follows:

Interface errorbase
{
Public void guihandle ();
Public void loghandle ();
}
Class dbaccesserror implements errorbase
{
Public void guihandle (){
/* Notify the user interface of Database Access Error Handling */
}
Public void loghandle (){
/* Notify the log system about how to handle database access errors */
}
}
Class communicationerror implements errorbase
{
Public void guihandle (){
/* Handle communication errors on the notification User Interface */
}
Public void loghandle (){
/* Handle communication errors of the log system */
}
}
Class guisys
{
Public void announceerror (errorbase error ){
Error. guihandle ();
}
}
Class logsys
{
Public void announceerror (errorbase error ){
Error. loghandle ();
}
}

Readers may have noticed that this approach is not very in line with OCP. Although it limits changes to the errorbase class hierarchy, it adds new processing methods, or changed the existing errorbase class. In fact, this design method violates another well-known object-oriented design principle: SRP (single responsibility principle ). The core implication of this principle is that a class should have only one responsibility. About the meaning of duties, the object-oriented Master Robert. c Martin has a well-known definition: the responsibility of a class refers to the cause of this class change. If a class has more than one responsibility, there will be many different reasons for this type of change. In fact, the coupling of multiple irrelevant responsibilities will reduce the cohesion of this class. The responsibility of the error class is to save the error States related to you and provide methods for obtaining these statuses. In the above design, different handling methods are also put into the error class, which increases the responsibility of the error class, so that even if the error class is irrelevant to the change of the error handling method, adding or modifying errors will result in modification of error classes. This design method will bring unexpected problems when demand changes. Can we remove the error handling methods from it? If the reader is familiar with the design pattern (here the familiarity refers to the intent and motivation of the design pattern, rather than how to implement a specific design pattern ), A better design is coming soon.

6. Design Patterns surfaced

Let's re-describe the problem: we already have an error class hierarchy, now we need to allow us to add new processing methods for this class level without changing the class hierarchy. It sounds familiar. Good. This is exactly the description of the intent of the visitor design pattern. Through analyzing the motivation of this mode, we can easily know that to use the visitor mode, we need to define two levels of classes: one corresponds to the class hierarchy of the elements that receive operations (that is, our error classes), and the other corresponds to the visitor who defines the operations on the elements (that is, our different methods for handling errors ). In this way, we have transformed the problem perspective, that is, converting the problem that requires different error handling methods into different error handling classes, the result is that we can add new error handling methods by adding new modules (classes), rather than adding new error handling methods (in this way, it is necessary to modify existing classes ).

Once this part is complete, the following work is relatively simple, because the visitor mode has already set up a design context for us, now we can focus on the Implementation part of the visitor mode to guide our specific implementation below. The following is only a UML diagram of the final program structure and sample code. The methods of errors in the error class are ignored, each specific error handling method interacts with a specific error object to complete their respective processing functions.

Final program structure

Final code example

Interface errorbase
{
Public void handle (errorhandler handler );
}
Class dberror implements errorbase
{
Public void handle (errorhandler handler ){
Handler. Handle (this );
}
}
Class commerror implements errorbase
{
Public void handle (errorhandler handler ){
Handler. Handle (this );
}
}
Interface errorhandler
{
Public void handle (dbrror dberror );
Public void handle (commerror );
}
Class guisys implements errorhandler
{
Public void announceerror (errorbase error ){
Error. Handle (this );
}
Public void handle (dberror ){
/* Notify the user interface to handle database errors */
}
Public void handle (commerror ){
/* Notify the user interface to handle communication errors */
}
}
Class logsys implements errorhandler
{
Public void announceerror (errorbase error ){
Error. Handle (this );
}
Public void handle (dberror ){
/* Notify the log system to handle database errors */
}
Public void handle (commerror ){
/* Notify the log system to handle communication errors */
}
}

7. Conclusion

The design pattern is not just the result of a solution to a specific problem. Its intent and motivation are often more important, because once we understand the intent and motivation of a design pattern, in the design process, we can easily find our own design patterns, greatly simplifying the design work, and get an ideal design solution.

In addition, you should pay more attention to the things behind the design pattern in the process of learning the design pattern, that is, some excellent guiding principles shared by the specific design pattern, these principles are described in detail in Chapter 1 of the Reference Document [1]. There are basically two points:

  • Discover and encapsulate changes
  • Use composition first, instead of Inheritance

If you learn and understand the design patterns from these aspects, you will get some more useful knowledge than a single specific design pattern itself, and even if there is no ready-to-use pattern available, we can also design a good system.

References

[1] design patterns, gamma, et. al., Addison Wesley

[2] design principles and design patterns, Robert C. Martin, www.objectmentor.com

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

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.