GURU Of THE WEEK Terms 15: The relationship between classes (Part 2)

zhaozj2021-02-08  462

Gotw # 15 Class Relationshipships Part II

Author: Herb Sutter

Translation: Kingofark

[Declaration]: This article takes the Guru of The Week column on www.gotw.ca website, and its copyright belongs to the original person. Translator Kingofark translated this article without the consent of the original person. This translation is only for self-study and reference, please read this article, do not reprint, spread this translation; people download this translation content, please delete its backup immediately after reading. Translator Kingofark is not responsible for people who violate the above two principles. This declaration.

Revision 1.0

GURU Of THE WEEK Terms 15: The relationship between classes (Part 2)

Difficulty: 6/10

(The design mode is an important tool to write the reuse of the code. Can you recognize the mode used by the code in this Territine? Can you improve it?)

[problem]

A program for manipulating a database often needs to apply a certain operation in a given table (Table) to one or more records (RECORD). This generally involves two consecutive processes: First, in read-only mode, the entire table is to collect information, determine which records need to be manipulated; then the table is second visits to implement real operations.

In order to avoid repeatedly preparing those conventional operational code, a programmer attempts to provide a universal DCEWORK by the following abstraction class. He hopes that the abstract class can encapsulate those repetitive code, first generate a list (List), which is used to record the record row that needs to be processed in the table; second, for each table in the list The item is processed. Various specific processing code details are implemented by each derived class.

/ / -------------------------------------------------------------------------------------------- ---

// gta.h file

/ / -------------------------------------------------------------------------------------------- ---

Class genericTableAlgorithm {

PUBLIC:

GenericTableAlgorithm (Const string & Table);

Virtual ~ genericTableAlgorithm ();

// process () returns true if it is successful.

// It did all work, including: a) Physical Reads

// a) Read the record in the table on the physical device, then call Filter () for each record

// Check that it is a record that needs to be processed;

// b) After the list of records that need to be processed, the record that needs to be processed is required.

// Call ProcessRow ().

Bool process ();

Private:

// If the current record is a record that needs to be processed, Filter () returns True.

// The default behavior is to include all records in the table in.

Virtual Bool Filter (Const Record &) {

Return True;

}

// Record for each need to be processed, ProcessRow () is called once.

// This is where it is a particular class actually used for its specific operation.

// (Note: It can be seen that each record is read twice before and after each record.

/ / Here we assume that this is necessary, not an efficient problem. )

Virtual Bool ProcessRow (const primarykey "; class genericTableAlgorithMimpl * pimpl_; // myob

}

This class is derived from its class, which may write using code like this:

Class myalgorithm: public genericTableAlgorithm {

// ... overwritten Filter () and ProcessRow (), do some

// Specific specific operation ...

}

INT main (int, char * []) ​​{

MyAlgorithm A ("Customer");

A.Process ();

}

There are 3 questions now:

1. This is a nice design that achieves a common design mode. Excuse me, what model is used here? Why can this pattern be used here?

2. This design is actually implemented in the case where it does not change its design. Can you use some ways to be different? Why is the PIMPL_ member designed?

3. In fact, this design can be improved. What is the responsibility responsible for genericTableAlGorithm? How should these responsibilities be better encapsulated if they are responsible for more than one responsibility? Explain how the method you use is how to affect the reuse of the class, especially the scalability of the class.

[answer]

1. This is a nice design that achieves a common design mode. Excuse me, what model is used here? Why can this pattern be used here?

This model is called Template Method (you can confuse with the Template template in C ). [Note 1] This design pattern is very useful because we can thus extract those steps to be performed from the algorithm, abstract it out, and only leave some detail of the local system to derived class.

(Note: PIMPL_ ingredients are very similar to the Bridge method [Note 1], but here, it is only used as a firewall against compilation dependencies; it hides the specific implementation details of each particular class, which is operated At the time, it is still not the same as the real scarabable Bridge.)

2. This design is actually implemented in the case where it does not change its design. Can you use some ways to be different? Why is the PIMPL_ member designed?

This design uses the BOOL variable as the return value, and it is also lost the ability to use other methods - such as status code, or exception processing - error reporting. It may be based on the consideration of some specific needs, but usually we should still understand and notice this.

That (less easy pronunciation) PIMPL_ member is very good to achieve details, hidden behind a mysterious pointer. The structure pointed to by PIMPL_ includes a private member function and a member variable. In this way, any change in them is not used to recompile the user code (Client Code). This is a very important technique described by Lakos et al. [Note 2]. It is important to say that this technique makes up for C lack of module system (Module System) from a certain extent without giving code. 3. In fact, this design can be improved. What is the responsibility responsible for genericTableAlGorithm? How should these responsibilities be better encapsulated if they are responsible for more than one responsibility?

GenericTableAlgorithm can also make great improvements, because he is still two jobs. This is the same as the ordinary person needs to withstand an additional burden, and the pressure will be large. So we can imagine, ease and change genericTableAlgorithm, which must be good for the class.

In the original code, GenericTableAlgorithm is responsible for two completely different and unrelated responsibility. These two responsibilities can be completely valid, because they face different role objects. Simply put, these two responsibilities are:

(1) Client Code uses a specific general algorithm (Generic Algorithm);

(2) For specific actual conditions, GenericTableAlgorithm uses classes with specific implementation details to make them specialization.

Ok, the saying is finished, now let's take a look at the code after the improvement:

/ / -------------------------------------------------------------------------------------------- ---

// gta.h file

/ / -------------------------------------------------------------------------------------------- ---

// Responsible # 1: Provide a common interface to make it possible to use common functions as

// Template Method is packaged. This is independent of inheritance relationship, and

// is isolated in a class that implements a specific function. This is a facing

// genericTableAlgorithm's external users (External users)

// Interface.

Class gtaclient;

Class genericTableAlgorithm {

PUBLIC:

// Constructor now acquires an object with specific implementation.

GenerictableAlgorithm (Const String & Table,

GTACLIENT & WORKER);

// Since we isolate inheritance relationships, the destructor does not have to be Virtual.

// In fact, we may not need it if we are in the roots.

~ GenericTableAlgorithm ();

Bool process (); // This line does not change

Private:

Class genericTableAlgorithMimpl * PIMPL_; // myob

}

/ / -------------------------------------------------------------------------------------------- ---

// gtaclient.h file

/ / -------------------------------------------------------------------------------------------- --- // Responsible # 2: provides an abstract interface for scalability. it's here,

The details of // genericTableAlgorithm have nothing to do with external user code.

/ / And can be isolated into a more clear abstract protocol class.

// The interface here is to write for those who use genericTableAlgorithm.

// The code writer of the class that can be used.

Class gtaclient {

PUBLIC:

Virtual ~ gtaclient () = 0;

Virtual Bool Filter (Const Record &) {

Return True;

}

Virtual Bool ProcessRow (const primarykey);

}

It can be seen that the above two classes need to be placed in different header files. So after these changes, what might code can be like? The answer is that the user code is basically no change, and it is almost the same as the original:

Class myWorker: public gtaclient {

// ... overwritten Filter () and ProcessRow (), do some

// Specific specific operation ...

}

INT main (int, char * []) ​​{

GenericesAbleAlgorithm A ("Customer", MyWorker ());

A.Process ();

}

Although the code looks not much, but must consider the following three effects generated after the improvement:

1. What if the public interface of GenericTableAlGorithm change? The result is: In the original version, all the specific client classes need to be recompiled because they are derived from genericTableAlGorithm; and in the improved version, any changes to the genericTableAlgorithm public interface are well isolated. Get up, does not affect the specific classes used by the client.

2. If the expansion protocol of GenericTableAlGorithm is changed, how is the new default parameter added in Filter () or ProcessRow ()? The result is: In the original version, even if the genericTableAlGorithm public interface does not change, all external code using GenerictableAlGorithm must be recompiled. This is because the DeriVation Interface is visible in the class definition. In the improved version, any changes to the GenericTableAlgorithm extended protocol interface are well isolated, and do not affect the external user code.

3. In an improved version, any particularly used class can be used in any algorithm with filter () or processrow (), but not only in GenerictableAlGorithm.

In fact, we use the Pattern (Pattern) with Strategy Pattern [Note 1] in the improved code.

To remember a motto in the field of computer science: Most Any Problem Can Be Solved by Adding a Level of Indirection (Most problems can be solved by increasing the indirect hierarchy). Of course, consider "Occam's Razor" principle is also very wise. "Occam's Razor" principle said: Don't multiply enttive more Than Necessary (do not do additional movement exceeding the demand). Grasping the balance between these two, allowing you to increase the reuse and maintainability of the code in the case of being costly or even free - this is a cost-effective sale. You may notice that genericTableAlgorithm is actually a function (actually, some people will refer to Operator () (), because the class at this time is just a functor (Function) ). The reason here can be replaced with a function because there is no statement that needs to be saved before and after calling Process (). For example, we can replace the code to this:

Bool generictablealgorithm

Const string & Table,

GTACLIENT & METHOD) {

// ... The original process () is placed here ...

}

INT main (int, char * []) ​​{

GenericTableAlgorithm ("Customer", MyWorker ());

}

The code here is actually a generic function, which can be specialized according to actual needs (Specialized). If you find the "Method" object, you don't need to save status information (), you can make the "Method" object into a non-class template parameter (non-Class template parameter):

Template

Bool genericTableAlgorithm (Const string & Table) {

// ... The original process () is placed here ...

}

INT main (int, char * []) ​​{

GenericTableAlgorithm ("Customer");

}

This function version is only a comma than the above. Of course, in the question discussed in this Territor, less comma will not give you a lot of benefits, so the first function may be better. After all, I can resist the temptation, I don't write such a good thing to show like a show, it is always a good thing.

In any case, choose the use function or use the type of implementation depends entirely on the purpose you want to achieve. In this issue of this Territor, use the function to achieve better.

[Note 1]: E. Gamma et al., Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995). (Chinese version: "Design mode: can be used for object-oriented software foundations")

[Note 2]: J. Lakos. Large-Scale C Software Design (Addison-Wesley, 1996). (Finish)

转载请注明原文地址:https://www.9cbs.com/read-375.html

New Post(0)