Translation tipatterns-pattern refactoring)

Source: Internet
Author: User

Pattern refactoring)

In this chapter, we will focus on solving problems through gradually evolving application design patterns. That is to say, at the beginning, we will use a rough design as the initial solution, then test the solution, and then use different design patterns to address this problem (some models are feasible, some are not suitable ). In the process of finding a better solution, the most critical question is, "What is changing ?"
This process is a bit like Martin Fowler mentioned in refactoring: improving the design of existing code (although he discusses refactoring through code snippets rather than pattern-level design ). Starting with a solution, you can fix it when you find that the solution cannot meet your needs. Of course, this is a natural practice, but it is quite difficult for procedural computer programming to complete it, the acceptance of code refactoring and design refactoring demonstrates that object-oriented programming is a "good thing ".

Simulate a waste recycling Generator
The essence of this problem is that waste is thrown into the garbage bin without classification, so that specific category information is lost. However, in order to classify the waste products correctly, the specific category information must be restored. At the beginning, we used rtti (thinking in Java chapter 2) as a solution.
This is not an easy design because it has some additional limitations. It is precisely because of these limitations that this problem is more interesting-it is more like the tricky problems you may encounter at work. Additional restrictions refer to the mixing of the garbage to trash reconfiguring plant. Our program must simulate waste classification. This is exactly where rtti is needed. You have a lot of waste fragments that cannot be named, and our program needs to find out their exact type.

//: Refactor: recyclea. Java

// Reconfiguring with rtti.

Package refactor. recyclea;

Import java. util .*;

Import java. Io .*;

Import JUnit. Framework .*;

 

Abstract class trash {

Private double weight;

Trash (double wt) {Weight = wt ;}

Abstract double getvalue ();

Double getweight () {return weight ;}

// Sums the value of trash in a bin:

Static void sumvalue (iterator it ){

Double val = 0.0f;

While (it. hasnext ()){

// One kind of rtti:

// A dynamically-checked cast

Trash t = (trash) it. Next ();

// Polymorphism in action:

Val + = T. getweight () * T. getvalue ();

System. Out. println (

"Weight of" +

// Using rtti to get type

// Information about the class:

T. getclass (). getname () +

"=" + T. getweight ());

}

System. Out. println ("total value =" + val );

}

}

 

Class aluminum extends trash {

Static double val = 1.67f;

Aluminum (double wt) {super (wt );}

Double getvalue () {return val ;}

Static void setvalue (double newval ){

Val = newval;

}

}

 

Class paper extends trash {

Static double val = 0.10f;

Paper (double wt) {super (wt );}

Double getvalue () {return val ;}

Static void setvalue (double newval ){

Val = newval;

}

}

 

Class glass extends trash {

Static double val = 0.23f;

Glass (double wt) {super (wt );}

Double getvalue () {return val ;}

Static void setvalue (double newval ){

Val = newval;

}

}

 

Public class recyclea extends testcase {

Collection

Bin = new arraylist (),

Glassbin = new arraylist (),

Paperbin = new arraylist (),

Albin = new arraylist ();

Private Static random Rand = new random ();

Public recyclea (){

// Fill up the trash bin:

For (INT I = 0; I <30; I ++)

Switch (RAND. nextint (3 )){

Case 0:

Bin. Add (New

Aluminum (RAND. nextdouble () * 100 ));

Break;

Case 1:

Bin. Add (New

Paper (RAND. nextdouble () * 100 ));

Break;

Case 2:

Bin. Add (New

Glass (RAND. nextdouble () * 100 ));

}

}

Public void test (){

Iterator sorter = bin. iterator ();

// Sort the trash:

While (sorter. hasnext ()){

Object T = sorter. Next ();

// Rtti to show class membership:

If (T instanceof aluminum)

Albin. Add (t );

If (T instanceof paper)

Paperbin. Add (t );

If (T instanceof glass)

Glassbin. Add (t );

}

Trash. sumvalue (Albin. iterator ());

Trash. sumvalue (paperbin. iterator ());

Trash. sumvalue (glassbin. iterator ());

Trash. sumvalue (bin. iterator ());

}

Public static void main (string ARGs []) {

JUnit. textui. testrunner. Run (recyclea. Class );

}

}///:~


In the source code list matching this book, the above file is located in the recyclea subdirectory, and recyclea is a branch of the refactor subdirectory. The Unpacking tool puts it in a proper subdirectory. The reason for doing so is that this chapter has rewritten this particular example many times and put each version in their own directory (by using the default package of each directory, the program call is also very simple) avoid class name conflicts.

The program creates several arraylist objects to store references to trash objects. Of course, arraylists actually stores objects so that they can store anything. They store trash objects (or objects derived from trash) only because you are careful and you do not pass anything other than trash to them. If you store "errors" in the arraylist, you will not receive warnings or errors at the time of compilation-you can only find these errors at the runtime through exceptions.

When the references of trash objects are added to the arraylist, they lose the specific category information and become only references to object objects (they are referenced by upcast ). However, due to polymorphism, when the iterator sorter calls the dynamic binding method, it will still produce appropriate behavior. Once the final object is cast back to the trash object, trash. sumvalue () also uses an iterator to perform operations on each object in the arraylist.

It looks silly to put different types of trash objects upcast into a container that can store base class references and then downcast them. Why not directly place the trash object in the appropriate container at the beginning? (In fact, this is a confusing example of garbage collection ). It is easy to make such changes to the above program, but sometimes downcasting is good for the structure and flexibility of some systems.

The above program meets the design requirements: it can work. If only one-time solution is required, the above method can be used. But utility programs usually need to evolve over time, so you must ask, "What if the situation changes ?" For example, if hard cart is a useful reusable item, how can we integrate it into the above system (especially when the program is large and complex ). In the preceding example, the type check code in the switch statement is scattered in the entire program. Each time you add a new type, you need to find the code for all types of checks, if you miss a compiler, it will not help you report errors.

Tests are conducted for each type, which is a misuse of rtti. If you test a child type just because it requires special treatment, it may be appropriate. However, if you are testing each type of the switch statement, you may miss some important things, and this will make your code more difficult to maintain. In the next section, we will look at how this program becomes more flexible through several stages of evolution. This is a valuable example of program design.

Improve existing Design Improving the design
The design patterns book focuses on "What will change as the program evolves ?" This problem is used to organize the solution. This is usually the most important issue for any design. If you can build your system around the answer to this question, it will bring two-pronged advantages: not only your system is easy to maintain (and cheap ), in addition, you may also create reusable components, which makes it easier for other systems to build. This is an inherent benefit of object-oriented programming, but it won't happen automatically; it requires your thinking and insight into the problem. This section shows how the system is completed.

For our waste recycling system, "What is changing ?" The answer to this question is very common: more types of waste products will be added to the system. That is to say, the ultimate goal of this design is to make it as convenient as possible to add new types. What we want to do with the waste recycling program is to encapsulate all the places that involve specific types of information, so that (if there is no other reason) any changes can be put in those encapsulated places. The final result is that this process also makes the rest of the code of the program clean to a considerable extent.

Get more objects "make more objects"
This will lead to a common object-oriented design principle, which I first heard from Grady booch: "If your design is too complex, get more objects ." This principle is not only intuitive but simple and almost absurd, but it is the most useful guiding principle I have ever seen. (You may have noticed that "getting more objects" is often equivalent to "adding another intermediate layer .") Generally, if you find that the code is very messy, you can consider what classes you can add to clean it up. Organizing code often brings another benefit: the system has better structure and flexibility.

The trash object was initially created in the switch statement of the main () function,

For (INT I = 0; I <30; I ++)

Switch (INT) (RAND. nextint (3 )){

Case 0:

Bin. Add (New

Aluminum (RAND. nextdouble () * 100 ));

Break;

Case 1:

Bin. Add (New

Paper (RAND. nextdouble () * 100 ));

Break;

Case 2:

Bin. Add (New

Glass (RAND. nextdouble () * 100 ));

}

There is no doubt that the above Code is a little messy, and you have to change this code when adding new types. If you often need to add new types, a better solution is to use a separate method, which generates a reference for a suitable type of object using all required information, this reference is first made into a trash object by upcast. The design patterns book mentions this method in general and calls it the creational pattern (there are actually several creation patterns ). The specific mode to be used here is a variant of the factory method. Here, the factory method is a static member function of trash, and in more cases it exists as a method overwritten by the derived class.

For (INT I = 0; I <30; I ++)

Switch (INT) (RAND. nextint (3 )){

Case 0:

Bin. Add (New

Aluminum (RAND. nextdouble () * 100 ));

Break;

Case 1:

Bin. Add (New

Paper (RAND. nextdouble () * 100 ));

Break;

Case 2:

Bin. Add (New

Glass (RAND. nextdouble () * 100 ));

}

There is no doubt that the above Code is a little messy, and you have to change this code when adding new types. If you often need to add new types, a better solution is to use a separate method, which generates a reference for a suitable type of object using all required information, this reference is first made into a trash object by upcast. The design patterns book mentions this method in general and calls it the creational pattern (there are actually several creation patterns ). The specific mode to be used here is a variant of the factory method. Here, the factory method is a static member function of trash, and in more cases it exists as a method overwritten by the derived class.

For (INT I = 0; I <30; I ++)

Switch (INT) (RAND. nextint (3 )){

Case 0:

Bin. Add (New

Aluminum (RAND. nextdouble () * 100 ));

Break;

Case 1:

Bin. Add (New

Paper (RAND. nextdouble () * 100 ));

Break;

Case 2:

Bin. Add (New

Glass (RAND. nextdouble () * 100 ));

}

There is no doubt that the above Code is a little messy, and you have to change this code when adding new types. If you often need to add new types, a better solution is to use a separate method, which generates a reference for a suitable type of object using all required information, this reference is first made into a trash object by upcast. The design patterns book mentions this method in general and calls it the creational pattern (there are actually several creation patterns ). The specific mode to be used here is a variant of the factory method. Here, the factory method is a static member function of trash, and in more cases it exists as a method overwritten by the derived class.

For (INT I = 0; I <30; I ++)

Switch (INT) (RAND. nextint (3 )){

Case 0:

Bin. Add (New

Aluminum (RAND. nextdouble () * 100 ));

Break;

Case 1:

Bin. Add (New

Paper (RAND. nextdouble () * 100 ));

Break;

Case 2:

Bin. Add (New

Glass (RAND. nextdouble () * 100 ));

}

There is no doubt that the above Code is a little messy, and you have to change this code when adding new types. If you often need to add new types, a better solution is to use a separate method, which generates a reference for a suitable type of object using all required information, this reference is first made into a trash object by upcast. The design patterns book mentions this method in general and calls it the creational pattern (there are actually several creation patterns ). The specific mode to be used here is a variant of the factory method. Here, the factory method is a static member function of trash, and in more cases it exists as a method overwritten by the derived class.

The idea of the factory method mode is like this. You pass the key information required to create an object to it, and then it will reference (which has been upcast as a base class) as the return value to you. Then, you can exploit the polymorphism of this object. In this way, you do not even need to know the exact type of the object to be created. In fact, the factory method hides its type information to prevent accidental misuse of the created object. If you want to manipulate objects without polymorphism, you must explicitly use rtti and casting.

But there will be some minor issues, especially when you use more complex methods (not listed here), define factory method in the base class and override it in the derived class.

If the information required by the (create) derived class requires more or different parameters than the base class, what should we do?

"Create more objects" can solve this problem. To implement the factory method mode, the trash class adds a new method called factory. To hide the data required for object creation, a new messenger class is added, it carries all the information necessary for the factory method to create a suitable trash object (we called messenger a design pattern at the beginning of this book, but it is really too simple, maybe you don't want to raise it to such a high level ). The following is a simple implementation of messenger:

Class messenger {

Int type;

// Must change this to add another type:

Static final int max_num = 4;

Double data;

Messenger (INT typenum, double Val ){

Type = typenum % max_num;

Data = val;

}

}

The only task of the messenger object is to save the required information for the factory () method. Now, if the factory method requires more or different information to create a new type of trash object, the factory () interface does not need to be changed. The messenger class can be changed by adding new data and new constructor, or by using subclassing, a more typical object-oriented method.

The factory () method in this simple example looks like the following:
 

Static trash Factory (messenger I ){

Switch (I. Type ){

Default: // to quiet the Compiler

Case 0:

Return new aluminum (I. data );

Case 1:

Return new paper (I. data );

Case 2:

Return new glass (I. data );

// Two lines here:

Case 3:

Return new cardboard (I. data );

}

}

Here, we can easily determine the exact type of an object, but you can imagine a more complex system in which the factory () method uses complex and obscure algorithms. The key issue is that these items are hidden in the same place now. When you add a new type, you should change it here.

Creating a new object is much easier than creating a new object in the main () function.

For (INT I = 0; I <30; I ++)

Bin. Add (

Trash. Factory (

New Messenger (

Rand. nextint (messenger. max_num ),

Rand. nextdouble () * 100 )));


The messenger object is created to use it to pass data to the factory () method, and then the factory () method will be on the heap) create a trash object of a certain type and returns the reference that's added to the arraylist bin.

Of course, if you change the number and type of parameters, the above Code still needs to be modified, but if the messenger object is automatically generated, this change can be avoided. For example, you can use an arraylist containing the required parameters to send the constructor to the messenger object (or directly pass it to the factory () method ). This requires parsing and verifying the input parameters at runtime, but it does provide the maximum flexibility.

From this code, you can see which kind of "a series of changes" the factory is responsible for solving: If you add new types to the system (so-called changes ), only the internal code of the factory must be changed. That is to say, the factory isolates the impact of this change.

To be continued ......


Directory

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.