I. Internal Design and Implementation of the class
? Defining a reasonable interface for a class plays a key role in creating high-quality programs. However, the design and implementation of the class are equally important. This section mainly describes the inclusion, inheritance, member functions, data members, coupling between classes, constructor, value object, and reference object.
1. Include ("there is a..." relationship) -- "has"
? Contains is a very simple concept, which indicates that a class contains a basic data element or object. Inclusion is the main technology in object-oriented programming.
1.1 implement a "One/has a" relationship through inclusion. 1.2 alert to Class 2 with more than 7 data members. inheritance ("is... "Link) --" is"
? The concept of inheritance is that a class is a special feature of another class. The purpose of inheritance is to write more streamlined code by "defining a base class that provides a common element for two or more Derived classes. The common elements can be subprogram interfaces, internal implementations, data members, or data types. Inheritance can aggregate these elements into a base class, which helps avoid repeated code and data in multiple places.
? When deciding to use inheritance, you must make the following decisions.
- Should a member function be visible to a derived class? Should it be implemented by default? Can this default implementation be overwritten?
Should each data member (including variables, named constants, enumeration, etc.) be visible to the derived class?
2.1 use public inheritance to implement the relationship "is ..."
? When a programmer decides to create a new class by inheriting an existing class, he indicates that the new class is a more special version of the existing class. The base class not only sets the expected class for the derived class, but also limits the operation of the derived class.
? If the derived classes do not fully comply with the same interface contract defined by the base class, inheritance is not the correct implementation technology. Consider using the include method or modifying the upper layer of the inheritance system.
2.2 either use inheritance and describe it in detail, or do not use it
? Inheritance adds complexity to the program, so it is a dangerous technology. "Either use inheritance and describe it in detail, or do not use it ".
2.3 follow the liskov replacement principle
? Barbara liskov proposed in a pioneering paper on object-oriented programming that, unless a derived class is really a special base class, it should not be inherited from the base class. That is, "The derived class must be used through the interface of the base class", and the user does not need to understand the differences between the two. In other words, all subroutines defined in the base class should have the same meaning in any of its derived classes.
? If the program follows the liskov replacement principle, inheritance can be a powerful tool to reduce complexity, because it allows programmers to focus on the general characteristics of objects without worrying about details. If the programmer must constantly think about the semantic differences between the implementations of different Derived classes, inheritance will only increase complexity.
2.4 ensure that only the part to be inherited is inherited
? A derived class can inherit the interfaces and/or implementations of member functions.
- Abstract and writable subprograms (such as pure virtual functions) mean that derived classes inherit only the subprograms but do not inherit their implementations.
- A writable subroutine (such as a non-pure virtual function) is a derived class that inherits the interface and default Implementation of the subroutine and can overwrite the default implementation.
- A subprogram that cannot be overwritten (such as a virtual function identified by override final) is a derived class that inherits the subprogram interface and its default implementation, but cannot overwrite the default implementation.
When you choose to implement a new class through inheritance, carefully consider the inheritance method you want for each subroutine. it is worth noting that only interfaces must be inherited, or interfaces must be inherited. if you only want to use the implementation of a class rather than interfaces, you should adopt the inclusion method instead of inheritance.
2.5 do not overwrite a member function that cannot be overwritten
? Both C ++ and Java allow programmers to "override" member functions that cannot be covered. If a member function is private in the base class, its derived class can create a member function with the same name. This function is confusing for programmers who want to read the code of a derived class, because it seems to be a multi-state function, but in fact it is missing, but it is just the same name.
2.6 place shared interfaces, data, and operations in the highest possible position in the inheritance tree
? The higher the position of interfaces, data, and operations in the inheritance system, the easier it is for derived classes to use them. How high is it? Let's decide based on abstraction. If you find that moving a child program to a higher level will undermine the abstraction of the layer object, you should stop.
2.7 only one instance class is questionable
? Only one instance is required, which may confuse objects with classes in the table name design. Consider whether you can create only one new object instead of a new class. Can the differences in a derived class be expressed by data instead of the new class? Singleton is a special case of this guideline.
2.8 It is also doubtful that only the base class of a derived class has one
? Whenever I see a base class with only one derived class, I suspect that a programmer is "Designing in advance"-that is, trying to predict the future needs, and often do not really understand what the future needs. The best way to prepare for future work is not to create several layers of additional base classes that may be used in the future, instead, the current work results should be as clear, simple, and straightforward as possible. That is to say, do not create any inheritance structures that are not absolutely necessary.
2.9 if a subprogram is overwritten after derivation, but no operation is performed in the subprogram, this situation is doubtful.
? This usually indicates that the design of the base class is incorrect. For example, assume that you have a cat class, which has a scratch () member function, but you finally find that some of the cats are missing and cannot catch them. You may want to derive a class called scratchlesscat from the cat class, and overwrite the scratch () method so that it does nothing. However, you may have the following questions:
It modifies the semantics expressed by CAT class interfaces, and thus destroys the abstraction (interface contract) represented by CAT classes ).
When you derive other derived classes from it, using this method quickly gets out of control. What if you find that a cat has no tail?
After using this method for a period of time, the code will gradually become chaotic and difficult to maintain, because the interface and behavior of the base class can hardly be understood by the behavior of its derived class.
The problem was fixed not in a derived class, but in the initial cat class. You should create a claw class and let the cat class include it. The root cause of the problem lies in the assumption that all cats can grasp. Therefore, we should understand the problem from the source rather than fix the problem.
2.10 avoid making the inheritance system too deep
? The object-oriented programming method provides a large number of techniques that can be used to manage complexity. However, every powerful tool is at risk, and even some object-oriented technologies are increasing, rather than decreasing, the complexity trend.
? Arthur riel suggested limiting the hierarchy of inheritance to a maximum of six layers. Arthur made this suggestion based on the "magic number 7 +-2" theory, but I think this is too optimistic. In my experience, most people have trouble dealing with Layer 2 to Layer 3 inheritance at the same time.
? People have discovered that a deep integration level will significantly increase the error rate. Every person who has debugged complex inheritance relationships should be aware of the reasons. A deep hierarchy of inheritance increases complexity, which is the opposite of what inheritance should solve. Remember the primary technical mission. Make sure that you use inheritance to avoid code duplication and minimize complexity.
2.11 use polymorphism whenever possible to avoid a large number of type checks
? Frequent repeated case statements sometimes imply that using inheritance may be a better design option-though not always. The following is a typical example of code that urgently requires more object-oriented methods:
// The case statement switch (shape. type) {Case shape_circle: shape. drawcircle (); break; Case shape_circle: shape. drawsquare (); break ;...}
? In this example, the call to shape. drawcircle () and shape. drawsquare () should be replaced by a method named shape. Draw. This method can be called to draw a circle or place a shape.
? In addition, case statements are sometimes used to separate objects or actions of different types. The following is an example of using case statements in Object-Oriented Programming:
// The case statement switch (UI. command () {Case command_openfile: openfile (); break; Case command_print: Print (); break; Case command_exit: Shutdown (); break ;...}
? At this time, you can also create a base class and derive some Derived classes, and then use the multi-state docommand () method to implement each command (just like the command mode ). However, in the case where the item is as simple as the item, docommand () is of little significance, so the case statement is easier to understand.
2.12 make all data private (rather than protected)
? As Joshua Bloch said, "inheritance will destroy encapsulation ". When you inherit from an object, you have the privilege to access the protected data in the object. If the derived class really needs to access the properties of the base class, it should provide the protected accesser function.
2.13 multi-Inheritance
? Inheritance is a powerful tool. Just like using a chainsaw to replace a handsaw for logging, it is very useful when used with caution, but it also becomes very dangerous for people who have not yet understood the things to be aware.
? If we compare inheritance to chainsaw, multiple inheritance is the dangerous chainsaw in 1950s that neither has a protective cover nor can be automatically shut down. Sometimes this tool is useful, but in most cases, you 'd better put it in a repository-at least here it will not cause any damage.
? Although some experts have suggested the widespread use of multi-inheritance, in my personal experience, the purpose of Multi-inheritance is to define a "hybrid", that is, some simple classes that can add a set of attributes to objects. It is called a mixture because they can "mix" some attributes into the derived class. "Hybrid" can be a class such as displayable, persistant, serializable, or sortable. They are almost always abstract and do not intend to be instantiated independently of other objects.
? Hybrid systems require multi-inheritance, but as long as they are completely independent, they do not cause typical diamond inheritance problems. By combining a type of attributes, the design scheme can be easier to understand. It is easier for programmers to understand an object that uses a mixture of displayable and peristent-because this requires only two properties-rather than an object that requires 11 more specific subprograms.
? Both Java and VB recognize the value of the hybrid, because they allow multiple inheritance, but can only inherit the implementation of one class. C ++ supports multiple inheritance of interfaces and implementations at the same time. Before deciding to use multi-inheritance, programmers should carefully consider other schemes and carefully evaluate their possible impact on system complexity and comprehensibility.
2.14 why are there so many inheritance rules?
? This section provides many rules that help you stay away from inheritance-related troubles. All the original lines behind these rules are said, and inheritance often leads to fit the programmer's primary technical mission (that is, management complexity. From the perspective of control complexity, you should have a very discriminatory attitude towards inheritance. Next we will summarize when inheritance can be used and when it should be used:
- If multiple classes share data rather than behavior, you should create shared objects that can be contained in these classes.
- If multiple classes share behavior rather than data, they should be inherited from the common base class and the shared subprograms should be defined in the base class.
- If multiple classes share both data and behavior, they should be inherited from a common base class and shared data and subroutines should be defined in the base class.
- Use inheritance when you want to control interfaces by the base class; use include when you want to control interfaces by yourself.
Ii. member functions and data members
###### 1. Minimize the number of subprograms
? A study of c ++ programs found that the larger the number of subprograms in the class, the higher the error rate. However, it is also found that other competitive factors have a more significant impact, including a deep integration system, calling a large number of subroutines in a class, and strong coupling between classes. Evaluate the pros and cons between keeping the minimum number of subroutines and other factors.
2. It is forbidden to generate implicit member functions and operators you do not need.
? Sometimes you may find that some member functions should be disabled-for example, you want to disable assignment, or you do not want an object to be constructed. You may feel that since the compiler automatically generates these operators, you can only release them. In this case, you can define constructors, value assignment operators, or other member functions or operators as private, thus, the caller code is prohibited from accessing them (defining the constructor as private is also the standard technology for defining single-piece classes ).
3. Reduce the number of different subprograms called by the class
? A study found that the number of errors in the class is related to the total number of subprograms called by the class. Unified research also found that the higher the number of other classes used by the class, the higher the error rate.
4. Indirect calls to subprograms of other classes should be as few as possible
? Direct association is dangerous enough. Indirect associations, such as account. contactperson (). daytimecontactinfo (). phonenumber (), are more dangerous. The researchers have come up with a "Demeter rule", which basically means that object A can call all its own subroutines at will. If object a creates an object B, it can also call any (public) subroutine of object B, but it should avoid calling the subroutine in the object provided by object B. In the previous account example, the call account. contactperson () is suitable, but the call account. contactperson (). daytimecontactinfo () is not suitable.
In general, we should minimize the scope of mutual cooperation between small classes and classes-that is, we should minimize the number below.
- Type of the instantiated object
- Number of different subprograms directly called on the instantiated object
- Number of subprograms that call objects returned by other objects
Iii. constructor 1. If possible, all data members should be initialized in all Constructor
? Initializing all data members in all constructors is not difficult to implement in defensive programming.
2. Use a private constructor to forcibly implement the single-piece Mode
? If you want to define a class and require that it only have one unique object instance, you can hide all constructors of the class, then, a static getinstance () subroutine is provided to the outside world to access the unique instance of the class.
3. Deep copues is preferred. Shallow copies is used unless the argument is feasible)
? When designing complex objects, you need to make a major decision, that is, to achieve deep copy (to obtain deep copy) or shallow copy (to obtain the shallow copy) of objects ). A deep copy of an object is the result of one-by-one copying of the object's member data. Its shallow copy usually only points to or references the same instance object, of course, the specific meanings of "deep" and "Shallow" can be somewhat different.
? The reason for achieving a shallow copy is generally to improve performance. Although it is not aesthetic to copy multiple copies of large objects, doing so rarely leads to significant performance loss. A few objects may cause performance problems, but as we all know, programmers are not good at predicting the code that actually causes problems.
? It is inappropriate to increase the complexity for uncertain performance improvement. Therefore, when you choose to implement deep copy or shallow copy, A reasonable method is to give priority to deep copy-unless it can be demonstrated that the shortest copy is better.
? Deep replicas are easier to develop and maintain than shallow replicas. In addition to the Code required by both methods, you need to add a lot of code for reference counting, ensuring secure copying of objects, secure object comparison, and secure object deletion. These codes are prone to errors. You should avoid them unless you have full geographical advantages.
08 questions about class design and implementation (class structure)