⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ei43.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 2 页
字号:
  strcpy(value, valueDelimOpen());  append to the string in value this object's name field  // write closing delimiter  strcat(value, valueDelimClose());  return value;}One might quibble with the design of PersonInfo::theName (especially the use of a fixed-size static buffer see Item 23), but set your quibbles aside and focus instead on this: theName calls valueDelimOpen to generate the opening delimiter of the string it will return, then it generates the name value itself, then it calls valueDelimClose. Because valueDelimOpen and valueDelimClose are virtual functions, the result returned by theName is dependent not only on PersonInfo, but also on the classes derived from PersonInfo.As the implementer of MyPerson, that's good news, because while perusing the fine print in the Person documentation, you discover that name and its sister member functions are required to return unadorned values, i.e., no delimiters are allowed. That is, if a person is from Madagascar, a call to that person's nationality function should return "Madagascar", not "[Madagascar]".The relationship between MyPerson and PersonInfo is that PersonInfo happens to have some functions that make MyPerson easier to implement. That's all. There's no isa or has-a relationship anywhere in sight. Their relationship is thus is-implemented-in-terms-of, and we know that can be represented in two ways: via layering (see Item 40) and via private inheritance (see Item 42). Item 42 points out that layering is the generally preferred approach, but private inheritance is necessary if virtual functions are to be redefined. In this case, MyPerson needs to redefine valueDelimOpen and valueDelimClose, so layering won't do and private inheritance it must be: MyPerson must privately inherit from PersonInfo.But MyPerson must also implement the Person interface, and that calls for public inheritance. This leads to one reasonable application of multiple inheritance: combine public inheritance of an interface with private inheritance of an implementation: class Person {                        // this class specifiespublic:                               // the interface to be  virtual ~Person();                  // implemented  virtual string name() const = 0;  virtual string birthDate() const = 0;  virtual string address() const = 0;  virtual string nationality() const = 0;};class DatabaseID { ... };             // used below; details                                      // are unimportantclass PersonInfo {                    // this class has functionspublic:                               // useful in implementing  PersonInfo(DatabaseID pid);         // the Person interface  virtual ~PersonInfo();  virtual const char * theName() const;  virtual const char * theBirthDate() const;  virtual const char * theAddress() const;  virtual const char * theNationality() const;  virtual const char * valueDelimOpen() const;  virtual const char * valueDelimClose() const;  ...};class MyPerson: public Person,        // note use of                private PersonInfo {  // multiple inheritancepublic:  MyPerson(DatabaseID pid): PersonInfo(pid) {}  // redefinitions of inherited virtual delimiter functions  const char * valueDelimOpen() const { return ""; }  const char * valueDelimClose() const { return ""; }  // implementations of the required Person member functions  string name() const  { return PersonInfo::theName(); }  string birthDate() const  { return PersonInfo::theBirthDate(); }  string address() const  { return PersonInfo::theAddress(); }  string nationality() const  { return PersonInfo::theNationality(); }};Graphically, it looks like this:This kind of example demonstrates that MI can be both useful and comprehensible, although it's no accident that the dreaded diamond-shaped inheritance graph is conspicuously absent.Still, you must guard against temptation. Sometimes you can fall into the trap of using MI to make a quick fix to an inheritance hierarchy that would be better served by a more fundamental redesign. For example, suppose you're working with a hierarchy for animated cartoon characters. At least conceptually, it makes sense for any kind of character to dance and sing, but the way in which each type of character performs these activities differs. Furthermore, the default behavior for singing and dancing is to do nothing.The way to say all that in C++ is like this: class CartoonCharacter {public:  virtual void dance() {}  virtual void sing() {}};Virtual functions naturally model the constraint that dancing and singing make sense for all CartoonCharacter objects. Do-nothing default behavior is expressed by the empty definitions of those functions in the class (see Item 36). Suppose a particular type of cartoon character is a grasshopper, which dances and sings in its own particular way: class Grasshopper: public CartoonCharacter {public:  virtual void dance();    // definition is elsewhere  virtual void sing();     // definition is elsewhere};Now suppose that after implementing the Grasshopper class, you decide you also need a class for crickets: class Cricket: public CartoonCharacter {public:  virtual void dance();  virtual void sing();};As you sit down to implement the Cricket class, you realize that a lot of the code you wrote for the Grasshopper class can be reused. However, it needs to be tweaked a bit here and there to account for the differences in singing and dancing between grasshoppers and crickets. You are suddenly struck by a clever way to reuse your existing code: you'll implement the Cricket class in terms of the Grasshopper class, and you'll use virtual functions to allow the Cricket class to customize Grasshopper behavior!You immediately recognize that these twin requirements an is-implemented-in-terms-of relationship and the ability to redefine virtual functions mean that Cricket will have to privately inherit from Grasshopper, but of course a cricket is still a cartoon character, so you redefine Cricket to inherit from both Grasshopper and CartoonCharacter: class Cricket: public CartoonCharacter,               private Grasshopper {public:  virtual void dance();  virtual void sing();};You then set out to make the necessary modifications to the Grasshopper class. In particular, you need to declare some new virtual functions for Cricket to redefine: class Grasshopper: public CartoonCharacter {public:  virtual void dance();  virtual void sing();protected:  virtual void danceCustomization1();  virtual void danceCustomization2();  virtual void singCustomization();};Dancing for grasshoppers is now defined like this: void Grasshopper::dance(){  perform common dancing actions;  danceCustomization1();  perform more common dancing actions;  danceCustomization2();  perform final common dancing actions;}Grasshopper singing is similarly orchestrated.Clearly, the Cricket class must be updated to take into account the new virtual functions it must redefine: class Cricket:public CartoonCharacter,      private Grasshopper {public:  virtual void dance() { Grasshopper::dance(); }  virtual void sing() { Grasshopper::sing(); }protected:  virtual void danceCustomization1();  virtual void danceCustomization2();  virtual void singCustomization();};This seems to work fine. When a Cricket object is told to dance, it will execute the common dance code in the Grasshopper class, then execute the dance customization code in the Cricket class, then continue with the code in Grasshopper::dance, etc.There is a serious flaw in your design, however, and that is that you have run headlong into Occam's razor, a bad idea with a razor of any kind, and especially so when it belongs to William of Occam. Occamism preaches that entities should not be multiplied beyond necessity, and in this case, the entities in question are inheritance relationships. If you believe that multiple inheritance is more complicated than single inheritance (and I hope that you do), then the design of the Cricket class is needlessly complex.Fundamentally, the problem is that it is not true that the Cricket class is-implemented-in-terms-of the Grasshopper class. Rather, the Cricket class and the Grasshopper class share common code. In particular, they share the code that determines the dancing and singing behavior that grasshoppers and crickets have in common.The way to say that two classes have something in common is not to have one class inherit from the other, but to have both of them inherit from a common base class. The common code for grasshoppers and crickets doesn't belong in the Grasshopper class, nor does it belong in the Cricket class. It belongs in a new class from which they both inherit, say, Insect: class CartoonCharacter { ... };class Insect: public CartoonCharacter {public:  virtual void dance();    // common code for both  virtual void sing();     // grasshoppers and cricketsprotected:  virtual void danceCustomization1() = 0;  virtual void danceCustomization2() = 0;  virtual void singCustomization() = 0;};class Grasshopper: public Insect {protected:  virtual void danceCustomization1();  virtual void danceCustomization2();  virtual void singCustomization();};class Cricket: public Insect {protected:  virtual void danceCustomization1();  virtual void danceCustomization2();  virtual void singCustomization();};Notice how much cleaner this design is. Only single inheritance is involved, and furthermore, only public inheritance is used. Grasshopper and Cricket define only customization functions; they inherit the dance and sing functions unchanged from Insect. William of Occam would be proud.Although this design is cleaner than the one involving MI, it may initially have appeared to be inferior. After all, compared to the MI approach, this single-inheritance architecture calls for the introduction of a brand new class, a class unnecessary if MI is used. Why introduce an extra class if you don't have to?This brings you face to face with the seductive nature of multiple inheritance. On the surface, MI seems to be the easier course of action. It adds no new classes, and though it calls for the addition of some new virtual functions to the Grasshopper class, those functions have to be added somewhere in any case.Imagine now a programmer maintaining a large C++ class library, one in which a new class has to be added, much as the Cricket class had to be added to the existing CartoonCharacter/Grasshopper hierarchy. The programmer knows that a large number of clients use the existing hierarchy, so the bigger the change to the library, the greater the disruption to clients. The programmer is determined to minimize that kind of disruption. Mulling over the options, the programmer realizes that if a single private inheritance link from Grasshopper to Cricket is added, no other change to the hierarchy will be needed. The programmer smiles at the thought, pleased with the prospect of a large increase in functionality at the cost of only a slight increase in complexity.Imagine now that that maintenance programmer is you. Resist the seduction. Back to Item 42: Use private inheritance judiciously.Continue to Item 44: Say what you mean; understand what you're saying. 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -