1. Trang chủ
  2. » Công Nghệ Thông Tin

Thinking in Cplus plus (P29) pot

50 56 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 50
Dung lượng 163,58 KB

Nội dung

Chapter 16: Design Patterns 501 public: void visit(Aluminum* al) { double v = al->weight() * al->value(); out << "value of Aluminum= " << v << endl; alSum += v; } void visit(Paper* p) { double v = p->weight() * p->value(); out << "value of Paper= " << v << endl; pSum += v; } void visit(Glass* g) { double v = g->weight() * g->value(); out << "value of Glass= " << v << endl; gSum += v; } void visit(Cardboard* c) { double v = c->weight() * c->value(); out << "value of Cardboard = " << v << endl; cSum += v; } void total(ostream& os) { os << "Total Aluminum: $" << alSum << "\n" << "Total Paper: $" << pSum << "\n" << "Total Glass: $" << gSum << "\n" << "Total Cardboard: $" << cSum << endl; } }; class WeightVisitor : public Visitor { double alSum; // Aluminum double pSum; // Paper double gSum; // Glass double cSum; // Cardboard public: void visit(Aluminum* al) { alSum += al->weight(); out << "weight of Aluminum = " << al->weight() << endl; } Chapter 16: Design Patterns 502 void visit(Paper* p) { pSum += p->weight(); out << "weight of Paper = " << p->weight() << endl; } void visit(Glass* g) { gSum += g->weight(); out << "weight of Glass = " << g->weight() << endl; } void visit(Cardboard* c) { cSum += c->weight(); out << "weight of Cardboard = " << c->weight() << endl; } void total(ostream& os) { os << "Total weight Aluminum:" << alSum << endl; os << "Total weight Paper:" << pSum << endl; os << "Total weight Glass:" << gSum << endl; os << "Total weight Cardboard:" << cSum << endl; } }; int main() { vector<Trash*> bin; // fillBin() still works, without changes, but // different objects are prototyped: fillBin("Trash.dat", bin); // You could even iterate through // a list of visitors! PriceVisitor pv; WeightVisitor wv; vector<Trash*>::iterator it = bin.begin(); while(it != bin.end()) { (*it)->accept(pv); (*it)->accept(wv); it++; } pv.total(out); wv.total(out); Chapter 16: Design Patterns 503 purge(bin); } ///:~ Note that the shape of main( ) has changed again. Now there’s only a single Trash bin. The two Visitor objects are accepted into every element in the sequence, and they perform their operations. The visitors keep their own internal data to tally the total weights and prices. Finally, there’s no run-time type identification other than the inevitable cast to Trash when pulling things out of the sequence. One way you can distinguish this solution from the double dispatching solution described previously is to note that, in the double dispatching solution, only one of the overloaded methods, add( ) , was overridden when each subclass was created, while here each one of the overloaded visit( ) methods is overridden in every subclass of Visitor . More coupling? There’s a lot more code here, and there’s definite coupling between the Trash hierarchy and the Visitor hierarchy. However, there’s also high cohesion within the respective sets of classes: they each do only one thing ( Trash describes trash, while Visitor describes actions performed on Trash ), which is an indicator of a good design. Of course, in this case it works well only if you’re adding new Visitor s, but it gets in the way when you add new types of Trash . Low coupling between classes and high cohesion within a class is definitely an important design goal. Applied mindlessly, though, it can prevent you from achieving a more elegant design. It seems that some classes inevitably have a certain intimacy with each other. These often occur in pairs that could perhaps be called couplets , for example, containers and iterators. The Trash-Visitor pair above appears to be another such couplet. RTTI considered harmful? Various designs in this chapter attempt to remove RTTI, which might give you the impression that it’s “considered harmful” (the condemnation used for poor goto ). This isn’t true; it is the misuse of RTTI that is the problem. The reason our designs removed RTTI is because the misapplication of that feature prevented extensibility, which contravened the stated goal of adding a new type to the system with as little impact on surrounding code as possible. Since RTTI is often misused by having it look for every single type in your system, it causes code to be non-extensible: when you add a new type, you have to go hunting for all the code in which RTTI is used, and if you miss any you won’t get help from the compiler. However, RTTI doesn’t automatically create non-extensible code. Let’s revisit the trash recycler once more. This time, a new tool will be introduced, which I call a TypeMap . It inherits from a map that holds a variant of type_info object as the key, and vector<Trash*> as the value. The interface is simple: you call addTrash( ) to add a new Trash pointer, and the map class provides the rest of the interface. The keys represent the types contained in the associated vector . The beauty of this design (suggested by Larry O’Brien) is that the TypeMap dynamically adds a new key-value pair whenever it encounters a new type, so Chapter 16: Design Patterns 504 whenever you add a new type to the system (even if you add the new type at runtime), it adapts. The example will again build on the structure of the Trash types, and will use fillBin( ) to parse and insert the values into the TypeMap . However, TypeMap is not a vector<Trash*> , and so it must be adapted to work with fillBin( ) by multiply inheriting from Fillable . In addition, the Standard C++ type_info class is too restrictive to be used as a key, so a kind of wrapper class TypeInfo is created, which simply extracts and stores the type_info char* representation of the type (making the assumption that, within the realm of a single compiler, this representation will be unique for each type). //: C09:DynaTrash.cpp //{L} TrashPrototypeInit //{L} fillBin Trash TrashStatics // Using a map of vectors and RTTI // to automatically sort Trash into // vectors. This solution, despite the // use of RTTI, is extensible. #include "Trash.h" #include "fillBin.h" #include "sumValue.h" #include " /purge.h" #include <iostream> #include <fstream> #include <vector> #include <map> #include <typeinfo> using namespace std; ofstream out("DynaTrash.out"); // Must adapt from type_info in Standard C++, // since type_info is too restrictive: template<class T> // T should be a base class class TypeInfo { string id; public: TypeInfo(T* t) : id(typeid(*t).name()) {} const string& name() { return id; } friend bool operator<(const TypeInfo& lv, const TypeInfo& rv){ return lv.id < rv.id; } }; class TypeMap : public map<TypeInfo<Trash>, vector<Trash*> >, Chapter 16: Design Patterns 505 public Fillable { public: // Satisfies the Fillable interface: void addTrash(Trash* t) { (*this)[TypeInfo<Trash>(t)].push_back(t); } ~TypeMap() { for(iterator it = begin(); it != end(); it++) purge((*it).second); } }; int main() { TypeMap bin; fillBin("Trash.dat", bin); // Sorting happens TypeMap::iterator it; for(it = bin.begin(); it != bin.end(); it++) sumValue((*it).second); } ///:~ TypeInfo is templatized because typeid( ) does not allow the use of void* , which would be the most general way to solve the problem. So you are required to work with some specific class, but this class should be the most base of all the classes in your hierarchy. TypeInfo must define an operator< because a map needs it to order its keys. Although powerful, the definition for TypeMap is simple; the addTrash( ) member function does most of the work. When you add a new Trash pointer, the a TypeInfo<Trash> object for that type is generated. This is used as a key to determine whether a vector holding objects of that type is already present in the map . If so, the Trash pointer is added to that vector . If not, the TypeInfo object and a new vector are added as a key-value pair. An iterator to the map, when dereferenced, produces a pair object where the key ( TypeInfo ) is the first member, and the value ( Vector<Trash*> ) is the second member. And that’s all there is to it. The TypeMap takes advantage of the design of fillBin( ) , which doesn’t just try to fill a vector but instead anything that implements the Fillable interface with its addTrash( ) member function. Since TypeMap is multiply inherited from Fillable , it can be used as an argument to fillBin( ) like this: fillBin("Trash.dat", bin); An interesting thing about this design is that even though it wasn’t created to handle the sorting, fillBin( ) is performing a sort every time it inserts a Trash pointer into bin . When the Trash is thrown into bin it’s immediately sorted by TypeMap ’s internal sorting mechanism. Stepping through the TypeMap and operating on each individual vector becomes a simple matter, and uses ordinary STL syntax. Chapter 16: Design Patterns 506 As you can see, adding a new type to the system won’t affect this code at all, nor the code in TypeMap . This is certainly the smallest solution to the problem, and arguably the most elegant as well. It does rely heavily on RTTI, but notice that each key-value pair in the map is looking for only one type. In addition, there’s no way you can “forget” to add the proper code to this system when you add a new type, since there isn’t any code you need to add, other than that which supports the prototyping process (and you’ll find out right away if you forget that). Summary Coming up with a design such as TrashVisitor.cpp that contains a larger amount of code than the earlier designs can seem at first to be counterproductive. It pays to notice what you’re trying to accomplish with various designs. Design patterns in general strive to separate the things that change from the things that stay the same . The “things that change” can refer to many different kinds of changes. Perhaps the change occurs because the program is placed into a new environment or because something in the current environment changes (this could be: “The user wants to add a new shape to the diagram currently on the screen”). Or, as in this case, the change could be the evolution of the code body. While previous versions of the trash-sorting example emphasized the addition of new types of Trash to the system, TrashVisitor.cpp allows you to easily add new functionality without disturbing the Trash hierarchy. There’s more code in TrashVisitor.cpp , but adding new functionality to Visitor is cheap. If this is something that happens a lot, then it’s worth the extra effort and code to make it happen more easily. The discovery of the vector of change is no trivial matter; it’s not something that an analyst can usually detect before the program sees its initial design. The necessary information will probably not appear until later phases in the project: sometimes only at the design or implementation phases do you discover a deeper or more subtle need in your system. In the case of adding new types (which was the focus of most of the “recycle” examples) you might realize that you need a particular inheritance hierarchy only when you are in the maintenance phase and you begin extending the system! One of the most important things that you’ll learn by studying design patterns seems to be an about-face from what has been promoted so far in this book. That is: “OOP is all about polymorphism.” This statement can produce the “two-year-old with a hammer” syndrome (everything looks like a nail). Put another way, it’s hard enough to “get” polymorphism, and once you do, you try to cast all your designs into that one particular mold. What design patterns say is that OOP isn’t just about polymorphism. It’s about “separating the things that change from the things that stay the same.” Polymorphism is an especially important way to do this, and it turns out to be helpful if the programming language directly supports polymorphism (so you don’t have to wire it in yourself, which would tend to make it prohibitively expensive). But design patterns in general show other ways to accomplish the basic goal, and once your eyes have been opened to this you will begin to search for more creative designs. Since the Design Patterns book came out and made such an impact, people have been searching for other patterns. You can expect to see more of these appear as time goes on. Here Chapter 16: Design Patterns 507 are some sites recommended by Jim Coplien, of C++ fame ( http://www.bell-labs.com/~cope ), who is one of the main proponents of the patterns movement: http://st-www.cs.uiuc.edu/users/patterns http://c2.com/cgi/wiki http://c2.com/ppr http://www.bell-labs.com/people/cope/Patterns/Process/index.html http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns http://st-www.cs.uiuc.edu/cgi-bin/wikic/wikic http://www.cs.wustl.edu/~schmidt/patterns.html http://www.espinc.com/patterns/overview.html Also note there has been a yearly conference on design patterns, called PLOP, that produces a published proceedings. The third one of these proceedings came out in late 1997 (all published by Addison-Wesley). Exercises 1. Using SingletonPattern.cpp as a starting point, create a class that manages a fixed number of its own objects. Assume the objects are database connections and you only have a license to use a fixed quantity of these at any one time. 2. Create a minimal Observer-Observable design in two classes, without base classes and without the extra arguments in Observer.h and the member functions in Observable.h . Just create the bare minimum in the two classes, then demonstrate your design by creating one Observable and many Observer s, and cause the Observable to update the Observer s. 3. Change InnerClassIdiom.cpp so that Outer uses multiple inheritance instead of the inner class idiom. 4. Add a class Plastic to TrashVisitor.cpp. 5. Add a class Plastic to DynaTrash.cpp. 6. Explain how AbstractFactory.cpp demonstrates Double Dispatching and the Factory Method . 7. Modify ShapeFactory2.cpp so that it uses an Abstract Factory to create different sets of shapes (for example, one particular type of factory object creates “thick shapes,” another creates “thin shapes,” but each factory object can create all the shapes: circles, squares, triangles etc.). 8. Create a business-modeling environment with three types of Inhabitant : Dwarf (for engineers), Elf (for marketers) and Troll (for managers). Now create a class called Project that creates the different inhabitants and causes them to interact( ) with each other using multiple dispatching. 9. Modify the above example to make the interactions more detailed. Each Inhabitant can randomly produce a Weapon using getWeapon( ) : a Dwarf uses Jargon or Play , an Elf uses InventFeature or Chapter 16: Design Patterns 508 SellImaginaryProduct , and a Troll uses Edict and Schedule . You must decide which weapons “win” and “lose” in each interaction (as in PaperScissorsRock.cpp ). Add a battle( ) member function to Project that takes two Inhabitant s and matches them against each other. Now create a meeting( ) member function for Project that creates groups of Dwarf , Elf and Manager and battles the groups against each other until only members of one group are left standing. These are the “winners.” 10. Implement Chain of Responsibility to create an “expert system” that solves problems by successively trying one solution after another until one matches. You should be able to dynamically add solutions to the expert system. The test for solution should just be a string match, but when a solution fits, the expert system should return the appropriate type of problemSolver object. What other pattern/patterns show up here? 509 11: Tools & topics Tools created & used during the development of this book and various other handy things The code extractor The code for this book is automatically extracted directly from the ASCII text version of this book. The book is normally maintained in a word processor capable of producing camera- ready copy, automatically creating the table of contents and index, etc. To generate the code files, the book is saved into a plain ASCII text file, and the program in this section automatically extracts all the code files, places them in appropriate subdirectories, and generates all the makefiles. The entire contents of the book can then be built, for each compiler, by invoking a single make command. This way, the code listings in the book can be regularly tested and verified, and in addition various compilers can be tested for some degree of compliance with Standard C++ (the degree to which all the examples in the book can exercise a particular compiler, which is not too bad). The code in this book is designed to be as generic as possible, but it is only tested under two operating systems: 32-bit Windows and Linux (using the Gnu C++ compiler g++ , which means it should compile under other versions of Unix without too much trouble). You can easily get the latest sources for the book onto your machine by going to the web site www.BruceEckel.com and downloading the zipped archive containing all the code files and makefiles. If you unzip this you’ll have the book’s directory tree available. However, it may not be configured for your particular compiler or operating system. In this case, you can generate your own using the ASCII text file for the book (available at www.BruceEckel.com ) and the ExtractCode.cpp program in this section. Using a text editor, you find the CompileDB.txt file inside the ASCII text file for the book, edit it (leaving it the book’s text file) to adapt it to your compiler and operating system, and then hand it to the ExtractCode program to generate your own source tree and makefiles. You’ve seen that each file to be extracted contains a starting marker (which includes the file name and path) and an ending marker. Files can be of any type, and if the colon after the comment is directly followed by a ‘!’ then the starting and ending marker lines are not reproduced in the generated file. In addition, you’ve seen the other markers {O} , {L} , and {T} that have been placed inside comments; these are used to generate the makefile for each subdirectory. Appendix B: Programming Guidelines 510 If there’s a mistake in the input file, then the program must report the error, which is the error( ) function at the beginning of the program. In addition, directory manipulation is not supported by the standard libraries, so this is hidden away in the class OSDirControl . If you discover that this class will not compile on your system, you must replace the non-portable function calls in OSDirControl with equivalent calls from your library. Although this program is very useful for distributing the code in the book, you’ll see that it’s also a useful example in its own right, since it partitions everything into sensible objects and also makes heavy use of the STL and the standard string class. You may note that one or two pieces of code might be duplicated from other parts of the book, and you might observe that some of the tools created within the program might have been broken out into their own reusable header files and cpp files. However, for easy unpacking of the book’s source code it made more sense to keep everything lumped together in a single file. //: C10:ExtractCode.cpp // Automatically extracts code files from // ASCII text of this book. #include <iostream> #include <fstream> #include <string> #include <vector> #include <map> #include <set> #include <algorithm> using namespace std; string copyright = "// From Thinking in C++, 2nd Edition\n" "// Available at http://www.BruceEckel.com\n" "// (c) Bruce Eckel 1999\n" "// Copyright notice in Copyright.txt\n"; string usage = " Usage:ExtractCode source\n" "where source is the ASCII file containing \n" "the embedded tagged sourcefiles. The ASCII \n" "file must also contain an embedded compiler\n" "configuration file called CompileDB.txt \n" "See Thinking in C++, 2nd ed. for details\n"; // Tool to remove the white space from both ends: string trim(const string& s) { if(s.length() == 0) return s; [...]... CodeFile::headerLine(const string& s) { int start = s.find('\"'); int end = s.find('\"', start + 1); int len = end - start - 1; _compile.push_back(s.substr(start + 1, len)); } void CodeFile::dependLine(const string& s) { const string linktag("//{L} "); Appendix B: Programming Guidelines 515 string deps = trim(s.substr(linktag.length())); while(true) { int end = deps.find(' '); string dep = deps.substr(0, end); _link.push_back(dep);... Programming Guidelines 523 ' ' + cf.testArgs() + ' '; makeTest.push_back( CompilerData::adjustPath( compiler,line)); } // Create the link command: int linkdeps = cf.link().size(); string linklist; for(int i = 0; i < linkdeps; i++) linklist += cf.link().operator[](i) + obj + " "; line = cf.targetName() + exe + ": " + linklist + "\n\t$(CPP) $(OFLAG)" + cf.targetName() + exe + ' ' + linklist + "\n\n"; linkCmd.push_back(... line that was already read, since it contains valuable information This first line is dissected for the file name information and the target type The beginning of the file is Appendix B: Programming Guidelines 528 written (source and copyright information is added) and the rest of the file is read, until the ending tag The top few lines may contain information about link dependencies and command line... work done in designing the classes (and this was an iterative process; it didn’t just pop out this way), main( ) is quite straightforward to read After opening the input file, the getline( ) function is used to read each input line until the line containing CompileDB.txt is found; this indicates the beginning of the compiler database listing Once that has been Appendix B: Programming Guidelines 530... Guidelines 514 error(s, "Missing extension"); exit(1); } _base = _file.substr(0, lastDot); // Determine the type of file and target: if(s.find(".h") != string::npos || s.find(".H") != string::npos) { _targetType = header; _tname = _file; return; } if(s.find(".txt") != string::npos || s.find(".TXT") != string::npos || s.find(".dat") != string::npos || s.find(".DAT") != string::npos) { // Text file, not involved... " "Thinking in C++, 2nd Ed by Bruce Eckel\n" "# at http://www.BruceEckel.com\n" "# Compiles all the code in the book\n" "# Copyright notice in Copyright.txt\n\n" "help: \n" "\t@echo To compile all programs from \n" "\t@echo Thinking in C++, 2nd Ed., type\n" "\t@echo one of the following commands,\n" "\t@echo according to your compiler:\n"; set& n = CompilerData::compilerNames(); set::iterator... } } int main(int argc, char* argv[]) { if(argc < 2) { error("Command line error", usage); exit(1); } // For development & testing, leave off notice: if(argc == 3) if(string(argv[2]) == "-nocopyright") copyright = ""; // Open the input file to read the compiler // information database: ifstream in( argv[1]); if( !in) { error(string("can't open ") + argv[1],usage); exit(1); } string s; while(getline (in, ... C10:Tracetst.cpp Appendix B: Programming Guidelines 532 // Test of trace.h #include " /require.h" #include #include using namespace std; #define TRACEON #include "Trace.h" int main() { ifstream f("Tracetst.cpp"); assure(f, "Tracetst.cpp"); cout . it inserts a Trash pointer into bin . When the Trash is thrown into bin it’s immediately sorted by TypeMap ’s internal sorting mechanism. Stepping through the TypeMap and operating. fillBin( ) like this: fillBin("Trash.dat", bin); An interesting thing about this design is that even though it wasn’t created to handle the sorting, fillBin( ) is performing. book. #include <iostream> #include <fstream> #include <string> #include <vector> #include <map> #include <set> #include <algorithm> using namespace

Ngày đăng: 05/07/2014, 19:20