|< Free Open Study >|
5.3 The Use of the Protected Section of a Base Class
Getting back to our question on protected data, let us add some real-world names to our classes A and B. Let us say that A is the class fruit, which has, as its data members, a real number called weight and a string called color. In addition, all fruit have a print operation that outputs strings like, "Hi, I'm a .3-pound red fruit." That is the best the fruit class can do for a good default print function. Assume that class B is an apple that contains the additional data member variety. Should apple objects be able to see their own weight? Another way of asking the question is, "Should the weight data member of the fruit class be in the protected section?"
At first glance, this seems perfectly reasonable. If you do make weight a protected data member, then you are stating that if the weight data member needs to change in the future, you are willing to examine all methods of derived classes as well as the methods of the base class. This is a weakening of data hiding that should be avoided.
Just as I argued against those designers who favored public data, the designers should ask themselves the question, "What am I doing with the protected data, and why doesn't the class that owns the data (namely the base class) do it for me?" When asking the analogous question concerning public data, there is never a good answer. The designer is clearly missing an operation on the class through which he or she wants access to the data. In the case of protected data, there may be a valid answer. Why do I want to make the weight of fruit protected? Because the apple class is overriding the print method of fruit with its own print method. This method prints strings like, "Hi, I'm an apple and I weigh .3 pounds." In order to define this function at the apple level, I need access to the weight. Why not let the fruit class handle this behavior? Because the fruit class is not supposed to know of any derived class-specific data (Heuristic 5.2). When stuck in these cases, it is best to create a protected access function called get_weight, which simply returns the weight data member. In this case, the methods of apple are dependent only on the protected interface of fruit and not on the implementation of fruit. The protected interface of fruit is much easier to maintain than the implementation. While I railed against public accessor methods in Chapter 3 which often demonstrate a design flaw, there is nothing wrong with defining protected accessor methods. They are allowing derived class implementors access to base class data in an implementation-safe way. The implementors of derived classes have a right to access the data of their base class; however, the users of a class do not have a right to the data of the class they are using.
Beware of the class designer who claims that the weights of fruits have been a real number since time immemorial; therefore we can make the data member protected since we know it will never change. Murphy's 79th law of programming will see us eating fruit on 30 different planets 20 years from now, and the weight data member will no longer be a real number but an object of type planetweight that contains a mass and an acceleration. Our protected access function could easily be updated to accommodate the new implementation, but the direct users of protected data will have to examine each of the methods on their derived classes for possible modifications.
Similarly, watch out for the class designers who claim that they are willing to look at the methods of only three additional classes (apple, banana, and orange) in order to win the right to make weight protected (see Figure 5.7). Twenty years from now, if the fruit class is worth anything, there will be many derived classes hanging off it. These designers will find themselves looking at the kiwi, which inherits from TropicalCitrusFruit, which inherits from CitrusFruit, which inherits from Fruit. Without a doubt, this assumption could become very dangerous, with formidable expense at maintenance time.
Some languages do not support the notion of a protected class access mechanism. These languages are deficient in that they compensate for the missing access protection either by making private access behave as protected, or by forcing the user to put all protected members in the public interface. The first solution opens up the implementations of all base classes to their derived classes, resulting in numerous maintenance headaches. The second solution forces the user to place implementation details in the public interface of the class, thereby forcing the user to violate Heuristic 2.5, which states that class designers should never put in the public interface items that users of the class do not require. In either event, such a language is not expressive enough to capture what we are trying to describe at design time.
|< Free Open Study >|