Site Map  Search

OOA Home OOD content UML Corrections book Code object Exercise Solutions oriented Resources tutorial Miscellany textbook

Exercise Solutions (Unrestricted)

Chapters 1 2 3 4 5 6 7 8 9 11 12

Chapter 12

Question 12.1
In Choosing abstract types on page 340, we mentioned a change case where House instances, acting under the type Dwelling were joined, later, by Apartment instances. Then I casually threw in Caravan (or Trailer) instances. Sketch out some simple Dwelling message signatures, and assure yourself that House instances would happily implement responses to them. Now picture Caravan (Trailer) instances joining in. Are you happy that the Dwelling type survives this change case or might it be broken?

One must ensure that the messages we try out are truly messages that a dwelling could be expected to accept. Obviously fourWheelsOrTwo() is an unfair test. So what types of messages are as specific as "dwelling" but no more specific than that? Mine were:
  int occupants() [the UML would say occupants() : int]
  Occupant eldest()
  Occupant youngest()
  Date lastCensus()

I am happy that these are as appropriate for trailers (caravans) as they are for apartments or houses.

Question 12.2
In Figure 12.20 an InterviewTape class was depicted with the method:
  lengthInMins() : int

Write a few words on the units problem suggesting alternatives to the above.

We can reasonably complain that a date is a simple concept that ought to be simply measurable, but that the length and variety of human history has produced many, many ways of indicating the value of a date. However we measure it, though, one aspect will involve an arbitrary unit of measurement, be it the time a cesium atom takes to execute a few quantum thingamabobs or be it the duration of the king's sneeze.

In addition to time, however, there are several other dimensions of measurement (the study of which is known as dimensional analysis), length and mass being the two others of the three most basic. Then there are combinations like speed or acceleration. Thus when something hands over a simple, primitive number as the answer to a question, we must wonder whether or not it's in seconds, or grams or meters per second per second.

The adjective "primitive" above gives a clue as to one way of keeping the answer simple (low coupling) while handing over the extra necessary information:

  • use a Duration object instead.

The other possibilities are:

  • as it began, the method name can indicate the unit;
  • there can be an underlying default unit for each kind of quantity;
  • there can be an underlying default unit for each kind of quantity, along with a bunch of constant, symbolic multipliers, such that one can write things like:
       currentTape.length() * minutes
    if the base unit happened to be minutes then the "minutes" constant would be 1; if the base unit happened to be hours then the "minutes" constant would be 60. One gains flexibility and readability.

 

Question 12.5
Describe the advantages and disadvantages of deferring the planning of implementation inheritance relationships until late in the design.

There aren't really many disadvantages. I only buy into one:

  • Poor notes, or poor attention to tricks such as using #include, could lead to missed opportunities for factoring out common implementation.

The advantages of doing the implementation inheritance as late as possible are many (only the optimization comes later):

  • Implementation inheritance will be based on evidence rather than supposition and surmise;
  • Many of the changes that would have eroded away at similarities should have already happened;
  • I think it's a good idea to do the easy, fact-based stuff before the difficult, speculative stuff; and it seems to me that the evidence for types and concrete classes arrives earlier, and is clearer, than that for superclasses (base classes);
  • Developers are unable to build superclass (base class) names into their own source if those names don't exist until the very end of the development; and it's a very good thing if superclass names are mentioned just once, in subclass (derived class) declarations.

 

Question 12.7
Compare and contrast overloading and overriding in languages like Java, C++ and C#.

Overloading is a fairly trivial convenience feature, albeit one that we are grateful for most of the time. It simply means that we don't have to invent unique names for every single method, even methods within a single class. There are more features than just its name that contribute to specifying a particular method or function being messaged for or called. In most languages the ordered types of the method's or function's parameters are matched (bound) with the ordered types of the method's or call's argument types.

Other things might also contribute to identification (binding). In C++, for example, the const-ness of an object helps determine whether a const member function, otherwise identical to another non-const member function, should be used.

Overloading has been around since before object-orientation.

Overriding, on the other hand, is more specifically an object-orientation feature. Overriding concerns superclass (base class) method (member function) declarations and subclass (derived class) decisions as to what to do about them. If a superclass has an available (e.g. non-private in C++), non-abstract method then a subclass can simply do nothing at all, whereupon a message that was declared in the superclass, being received by an instance of a subclass, will be answered by the superclass method. A subclass can choose to do something rather than nothing. A subclass can provide a method with the same distinguishing characteristics as the superclass method in question, whereupon the subclass method is said to override the superclass method, and upon receipt of an appropriate message it would be the subclass method that would be used to answer the message rather than the superclass method. (The distinguishing characteristics referred to a moment ago are often called the "signature". The signature of a message is matched to the signature of an available method.)

There is a more subtle, and very important, variety of overriding -- compulsory overriding. This concerns abstract methods, or pure virtual member functions as they are known in C++. If a superclass declares a message rather than a method, i.e. it declares what starts off looking like a method but there is, and there is declared to be, no code, then we often call that an abstract method. The more abstract declarations that a class makes, then the less concrete and more abstract the class. A concrete subclass (one that can be instantiated) of a superclass with abstract methods, must code up the abstract methods, i.e. there is compulsory overriding going on.

C++ does provide us with some headaches in this area because overloading and overriding interact with a much older and inconvenient concept: name hiding. Older languages, including Pascal and C, had the idea that it would be useful to allow programmers to employ another kind of name reuse -- to allow a nested scope block to use, and hide, a name that was already in use in a nesting scope block. When C++ comes along and considers that a derived class is a nested scope, the name hiding inherited from C and the overloading added by C++ interact in ways that can be surprising and confusing.

Java, rightly in my opinion, says that name hiding is more of an irritation than a convenience, and makes it illegal: names must be capable of being distinguished by more than just their scope in Java.

Question 12.8
I have in front of me some object-oriented programming notes that I claim I wrote long ago when I didn’t know any better. They talk about a vegetable superclass with a cook method, and a root vegetable subclass with an overriding cook method. They then go on to talk about sending the cook message first to an instance of the vegetable class and then to an instance of the root vegetable class. How many things can you find wrong with that example?

Well first-off it's a rather silly example, typical of textbooks, and one that I feel thoroughly ashamed of having used myself (long ago, honestly). I am hard-pressed to think of any system in which a vegetable object is going to be likely. However, that could just be my lack of imagination. If we assume that a vegetable object was indeed necessary, then an even bigger leap of the imagination is required to justify sending it a cook() message. [You should be similarly suspicious of examples concerning Camel and Mammal classes, Panda and Bear classes or Car and Boat classes.] Are we talking about embedded software here? Are we considering a microprocessor embedded in a vegetable and with a heater attached, in order that we can have self cooking vegetables? Or should we have sent the cook() message to an object embedded in a saucepan or an oven, with a vegetable as argument?

That's rather a subjective objection, however, so onto a more objective objection. It's not a good idea to instantiate superclasses (base classes) even if one could. Theoretically in many languages it would be possible to create an instance of a superclass if it had no abstract methods. There are good reasons not to do so under normal circumstances, however. Superclasses by their very nature take a long time to stabilize. Allowing another part of your code to create an object instance from an unstable class isn't a good idea. Some of the characteristics of implementation inheritance that make superclasses slow to stabilize have been discussed in the answer to question 12.5. It's best to keep superclass names out of parameter, return and variable declarations and out of new statements. The only place where a superclass name should normally be uttered is in the superclass declaration of a subclass definition.

[Use interfaces or pABCs for types (parameter, return and variable declarations), use concrete classes or factories to get objects, and keep the superclasses secret from all but their subclasses.]

 

Question 12.9
What was your conclusion about whether the CriminalRecord class should inherit from the LinkedList class, or whether CriminalRecord instances should contain a LinkedList instance (Section 12.9.8)? Why?

The CriminalRecord class should define that its instances contain a list class instance. Why?

  • First of all note that we don't have to say what kind of list class. Whatever it is, it will be in a private instance variable and will not (should not) affect the interface of the CriminalRecord class. Inheriting from LinkedList would be a much more difficult decision to undo, as it would have an effect on the inheriting class' interface.
  • Use of inheritance here would fail the substitutability test (sometimes known as PoS, the Principle of Substitutability; or LSP, Liskov's Substitutability Principle): someone (some object) expecting a list to turn up, and finding instead that a criminal record had turned up would be somewhat disconcerted.
  • Another way of phrasing the above objection is that inheritance gives the CriminalRecord class the LinkedList type along with list mechanisms when it is actually just the mechanisms that are wanted. One could argue that, in C++ at least, it is possible to use a variety of inheritance (private inheritance) that doesn't donate type; but doing that means that the alternatives of composition ("has a") and inheritance ("works like" -- remember we've switched off "is a kind of" in this kind of inheritance) demonstrate no unique gains; and since inheritance has several unique drawbacks, composition is preferable.
  • The biggest objection to the use of unnecessary inheritance is the fairly massive increase in coupling -- class coupling. We can't even talk about instance coupling because the two classes are producing a single object. Imagine that highly-likely scenario in which the superclass turns out to be not quite right. Rats! we should have used a hash table and not a linked list. Unless no code anywhere happens to have used the CriminalRecord class yet, making such a change is highly likely to break things. Or imagine that a superclass requires some maintenance. Again, any changes are likely to break subclasses. And in a large, or library, or framework development, one can't even be sure what those subclasses are, and where they are. And that's just the method impact. No maintenance at all would be possible in the face of non-private instance variables in the superclass. I.e. one effect of this coupling is that superclasses are fragile creatures. Contrast all this dire stuff with a use of component instances. If a composite instance contains a component instance, they retain their separate existences, their separate interfaces and their separate identities. And the interaction between them is explicit rather than implicit, and it uses only the normal, minimal coupling, object-oriented coupling -- messages. If one wanted to change or maintain the component, one's only dependence is on its interface.

 

Question 12.12
Summarize the Law of Demeter. Picture that you are adding a few paragraphs about the Law to a developers’ handbook you are writing for your team.

Don't have your objects asking to access objects whose location or existence they shouldn't know about.

That was the very short version. A longer version might talk about empowerment. One of the most frequent mistakes of even long-term, object-oriented developers is making their objects too dumb; one suspects that they are still hankering after dumb data structures. Instead of saying, "Give me your real part and your imaginary part so that I can calculate your inverse", ask a complex number object to simply give you its inverse. Instead of asking a student object to give you its record object in order that you can add an event; give the event to the student and ask it to remember it. Instead of asking a table how big it is and what kind of table it is, present the table to the booking and ask the booking if a table is suitable.