Book review by Ted Felix
As its title implies, OOSC2 is a book on software construction. Its focus is on object oriented programming and low-level object oriented design issues (single class and class-to-class). The examples are primarily ADTs and library classes. High level object oriented design and architecture are not covered.
Intellectually stimulating. Meyer occasionally makes forays into pure Computer Science, Math, Taxonomy, etc... that are interesting. Fortunately, he doesn't get carried away and go completely "Knuth" on the reader. The purely academic sections can be skipped and Meyer lets you know when it might be a good idea to do so.
The main strength of this book is its thorough coverage of the fundamental concepts of Object Oriented (O-O) software construction (classes, inheritance, polymorphism, generics). As it covers these concepts, it develops the Eiffel language. Eiffel is interesting as it is a language that is designed specifically with O-O in mind. As a result, it is very easy to do things in Eiffel that would otherwise be hard or almost impossible in other object oriented languages like C++, Java, or Ada95. If you want to see an O-O programming language done "right", this is the book.
A minor annoyance is the unbalanced presentation. The author tries (a little too hard) to convince the reader that O-O is the right way to go, and "top-down" is wrong. Yet the example design presented in Chapter 20 is clearly "top-down" all the way. The classes are top-down (he has an APPLICATION class with a STATE class controlled by it), and the functions are top-down within those classes (the top function in both the APPLICATION and STATE classes is called "execute"). This system clearly has a top. Contradictions like this are not unusual in this book. It is common to find a bold statement followed later by a contradiction with no apologies. It seems that to the author, everything is black and white, no matter how wrong that might be. There is no middle ground. In Chapter 19 he justifies this by saying that no one wants wishy-washy advice. His heart is in the right place, I think, but it's really hard not to be wishy-washy when computers are used in so many different ways. What works for one problem might not be best for another. The term "best practices" is a myth.
If you want a more balanced view of software design, you'll need to read a book on top-down (structured?) design along with this to get a well-rounded education. I don't yet know of a book that covers both equally, but I am definitely looking. Larman's Applying UML and Patterns is the best book on Object Oriented Design. I would definitely recommend reading it. Object-Oriented Modeling and Design with UML (Blaha/Rumbaugh 2005) leans toward O-O, but does mention the old way of doing things. McConnell's Code Complete covers software design in a more balanced way, but the discussion is limited as Code Complete is primarily about software construction, not design. A good software design book should take you through top-down and structured design along with O-O. The tools should be presented with a balanced view of the strengths and weaknesses. It should be up to the reader to form an opinion based on balanced knowledge. Also, a book on high-level software architecture (both object oriented and others) is needed to complement this book. Large examples are needed to show how well each approach scales up. Software Architecture in Practice (Len Bass 2003) is a good candidate.
Beginners at O-O will find this to be a tough book. They should plan to read it twice, and maintain a healthy dose of skepticism toward the benefits of O-O and the opinions of the author. The author appears to be trying to win over a hardcore top-down design crowd instead of gently introducing a beginner. Intermediate programmers should learn quite a bit from this book. Many suspicions will be verified, but skepticism is still a prerequisite. Experts will find this book to be fine if they are looking to learn Eiffel. It is a stimulating and fun refresher on the basics of O-O (Encapsulation, Inheritance, Polymorphism, Genericity...) if you feel you need one. If you want verification that your understanding of object oriented programming and low level object oriented design is complete, this book will do that.
This book has too many typos for a 14th printing. Some of them are critical and may confuse beginners.
Read from 1/1/2005 to 4/11/2005.
Author's Official Site - Don't trip over the overwhelming praise you'll find here. The table of contents and sample chapters are most valuable.
While reading this book, I took it upon myself to keep careful notes of the things that strike me as interesting. The following main questions were in my mind:
Now that I am finished with the book, I can answer these questions. The conflict between O-O and top-down design is not addressed in this book since the book goes no higher than low-level object oriented design (single class and class-to-class). The book doesn't focus on object oriented architecture, so the examples do not scale up at all. Finding the non-real-world objects is touched upon, but only a set of heuristics is provided to enable the reader to find them on their own. Few examples are provided. In summary, this was not the book I was looking for. Larman's Applying UML and Patterns was the book I was looking for.
After mentioning to Steve McConnell that it would be nice if C++ allowed you to restrict which classes can call another class's members, Steve recommended I read OOSC-2. Sure enough, Eiffel provides what are called "Selective Exports" (pg 191). This allows you to specify which classes may call a function.
(started 1/1/2005, completed 4/11/2005)
Unfortunately, I went into this book with the wrong goals. I wanted to read a book on software architecture, yet this is a book on software construction. My notes aren't completely a loss, however. I think that anyone reading the book will be helped somewhat by how I connect each topic Meyer discusses to a larger architectural picture. The notes expand on Meyer's presentation and include some further discussion of architectural issues one might encounter when architecting large systems.
These notes are most useful if you check on them as you are reading the book. Think of it as having a little book discussion club with me along for the ride.
p. 16 bottom, "the O-O method is not a panacea." Although throughout the rest of the book his tone tends to indicate the opposite.
p. 24, "In a pure object-oriented approach: Classes should be the only modules." No standalone subprograms, everything must be a part of a class.
p. 31, "Fast Update". I will agree that fast update is a nice thing to have, but the fact that this is a compiler feature indicates "marketing-oriented" rather then object-oriented. Several of his other "criteria for object-orientation" are similarly marketing-oriented.
I can't help but think that the guys who came up with Simula67 intended it to be used for simulation, not databases, word processing and all other possible uses for a computer. I need to get a Simula67-related book in my to-read list (Simula BEGIN [Birtwistle 1973]). See page 1122 and my note for that page.
p. 35, at the end of chapter two in the bibliographic notes, he recommends books that teach O-O from the start saying, "there is no reason to... take the poor students through the history of the hesitations and mistakes through which their predecessors arrived at the right ideas." (Emphasis mine) What was that I've heard about those who don't learn from history are condemned to repeat it? He is also implying that top-down and structured design were a "mistake". These are pretty strong words. I think this sentence is unnecessary and unhelpful. An unfortunate choice of words. To me it is a turn-off. There's no need to insult anyone or any methodology to further a point.
Rumbaugh, et al. 1991 Object-Oriented Modeling and Design takes a much more balanced approach as it suggests different methodologies for different kind of applications. It even talks to combining methodologies when needed to solve a problem.
p. 41, Chapter 3 gets even more interesting as it makes a direct frontal attack at top-down decomposition. He appears to be claiming that by making each class responsible for initializing its own data structures, there will be no need for a top-level controller of any sort to create objects and connect them together. That's just initialization. However, on page 195, we will see the root object and the creation procedure that creates all the system's objects and gets them running. Sounds like a top-level controller to me.
What about at runtime when the user asks for a function to be performed? Isn't there a place here for a top-level controller? Or should a command object be passed around as a "hot-potato"? Turns out in chapter 21, Meyer will present an interactive system with "UNDO". This system will indeed use command classes, but in an interesting and rather disturbing (due to its implications for modular continuity) twist, the command classes themselves will be responsible for performing actions on the "text document" class in question. So, essentially, he has taken the features out of the classes and moved them into command "classes". So much for modular continuity. A change to the "text document" class can mean a corresponding change to all the command classes that act on it.
If you want to get really decentralized, try a "hot-potato" design where a command object is tossed amongst hundreds of objects in the system until someone does something with it. This is decentralization taken to the limit. Any attempt at taming this would imply a "top" of some sort. Any attempt at maintaining such a system will be futile at best.
p. 43, but then he admits that the O-O requirement for decomposability and composability implies the need for a mix of top-down and bottom-up. How will he reconcile this conflict? In Chapter 20, we will see that he essentially combines the approaches as is realistic.
p. 47, the section "Few Interfaces" shows an interesting way to look at the centralized top-down approach vs. the decentralized O-O approach. This was kind of eye opening to me. In the decentralized O-O approach, there may be no way to find the high-level behavior of a system anywhere within its code. Is this really such a good thing? High-level behavior generally maps directly to requirements. Requirements change. Shouldn't it be easy to find the code that meets the requirements and be able to change it?
p. 50, Clusters are introduced as a higher organizational level above classes (the "only" modules).
p. 55, middle of the page, Meyer makes fun of the "heavy paper trail" involved in the ISO and CMM approaches. He insists that self-documenting code is better. Can't argue with that.
p. 57, Open-Closed Principle. Meyer argues that inheritance is useful for developing multiple versions of a class. This is a new idea to me.
p. 63 covers the "Single Choice" principle. This is the impetus for polymorphism. There should be only one case statement that switches on type in a system. Generally, this is found in a factory function of some sort that is used to create appropriate objects as needed based on runtime conditions. This approach is used in Eiffel as well.
p. 64 In the bibliographic notes, Parnas is mentioned.
p. 105, "The top-down approach has a number of advantages. It is a logical, well-organized thought discipline; it can be taught effectively; it encourages orderly development of systems; it helps the designer find a way through the apparent complexity that systems often present at the initial stages of their design." Flatter them before you flatten them...
Chapter 5 begins the assault on top-down decomposition. First, the premise is that functionality changes more than data (p. 105 "the properties that tend to change the most"), but there's no hard research data to back up this claim (p. 115). My personal experience is that new features bring along with them new data. For example, adding cut and paste brings along a clipboard. On p. 108, Meyer goes so far as to say, "Real systems have no top." I would say, "Real poorly-designed systems have no top." Interactive systems generally have a top that centers around the user and commands that are available to the user. To not have a top is to cloud the organization and meaning of the system so that finding the parts that need to be changed might be quite difficult.
p. 109, "Interfaces and Software Design". Here, Meyer says that we should forget about the user. The user is not important. What!? You've got to be kidding! But it makes perfect sense. There is no way to develop a system without a top if you keep worrying about the pesky user. If anything in a system is the top, it is ultimately, the user. Humans control machines. Machines are not autonomous. Simulations may contain autonomous objects. That is what Simula67 was all about. Who warped it into this?
p. 110, "Premature Ordering". The argument here is that top-down design focuses too much on the order of the steps in a process. With O-O we should instead focus on the objects and come up with as many operations as we can for each. But without a top-level process consisting of steps executed in a certain order, how do we have any idea what sort of operations our objects need? In the next section "Ordering and O-O Development," we see a non-programming example (I hate those!) which basically boils down to a spec that is deficient in the ordering department. Yeah, well, you're going to have ordering problems in that case, and I don't care how O-O you are. Meyer's presentation is deficient in two ways here. First, his O-O solution to having a bad spec is based on a good spec! Well, duh, anyone can fix the problem easily with a good spec in the first place. Second, he doesn't present the case where a spec is deficient in the object department. (e.g. Oh, we forgot to tell you, we want a spell checker in our word processor.) I'm sure Meyer wants us to believe that O-O is better from this example, however the lesson to be learned here is to make sure you understand all the possible permutations of process ordering. And, a good spec makes life a lot easier.
p. 111, contrast the Shopping List approach with Scott Meyers suggestion that classes contain the simplest possible set of features. This advice appeared in either Effective C++ or More Effective C++. I need to look up the page number.
p. 115. Here we have the foundation of this book: the argument that since the functions of a system are more likely to change than object types, it is better to design a system around object types (classes). "This argument is based on pragmatic observation, not on a proof that object types are more stable than functions. But experience seems to support it overwhelmingly." In other words, the argument is very weak with no supporting statistics of any kind. In the "pure" O-O system that Meyer envisions, there is no clear top, therefore there is no code that clearly describes the high-level functionality of the system. Consequently, a request for a new function might end up touching many objects. In a top-down approach, only the high-level controller function would need to change to support a new function. This is what Meyer calls "Modular Continuity" (a spec change has limited impact on the code).
How does one make the jump from "objects are stable" to "designing a system around objects is best" anyway? If anything, this promotes fragmenting the functions in such a way that the things that change the most are the hardest to figure out. Instead of basing an architecture on the things that change the least, why not base it on making it easy to change the things that change the most? One doesn't necessarily lead to the other.
p. 116. After all the arguing that bottom-up O-O is the only way to go, Meyer gives in and says that O-O requires a top (main function), hope, and good luck. He admits that O-O "may hold the key to reusability and extendibility." (emphasis mine) Well, at least he's realistic for one page. Although, the Meyer we will meet in Chapter 19 would want to eat this Meyer for lunch!
p. 118. What does he mean by "representation-independent descriptions" of types and objects (he claims this is the key to abstraction)? The "Key Concepts" section calls it "abstract descriptions of objects." What does that mean? Is he implying that by describing something in an abstract way we can get the proper level of abstraction? That doesn't sound right to me. This part needs some further explanation. As it stands, it is unintelligible.
Also note here the second time that top-down design is given credit for being good at abstraction. page 115 was the first: "a top-down functional decomposition...encourages abstraction".
p. 119. "Key Concepts" More back-pedalling. "A realistic system usually has more than one 'top' and is better described as providing a set of services." Compare this to "Real systems have no top" on p. 108. Boy, if he continues at this rate, he'll be back to top-down design before you know it. (See Chapter 20. That's exactly what happens.) "Bibliographic Notes", the two Jackson books (Principles of Program Design [1975 ISBN:0123790506] and System Development [1983 ISBN:0138803285]) look interesting as Meyer indicates they use similar arguments. I have them and should read them eventually.
At this point, I have to mention that I keep getting this feeling that by ignoring the top and focusing on the objects, one might be tempted to implement more functionality than is necessary (the "Shopping List" approach, page 111). The top provides a framework of limitations for the system. Without limitations, we could be developing wonderful reusable components forever, and never actually finish the software.
p. 125. Meyer says information hiding is a good approach because changes in data formats accounted for "more than 17% of software costs" according to Lientz and Swanson. In theory, information hiding should prevent changes in data format from affecting software outside of the class that contains the data. The classic example is the stack whose representation is changed from an array to a linked list. In practice, however, there are two kinds of data changes. The first is a change that can be hidden, such as the stack representation mentioned. The second is a change that cannot be hidden, like the adding of a new field to a database, or the expanding of the size of a data element. There are many more examples. I'm not sure that the Lientz and Swanson book [1980 ISBN: 0201042053] differentiated amongst these types of data format changes. I would have to read it. The fact that Meyer leaves this detail out is suspicious. Although he does follow up with a discussion of limiting middle initials to one character. But there's no solution presented. What's the point in presenting a problem, claiming O-O holds the key, then never showing how?
p. 191. Here's why McConnell suggested this book to me. Eiffel has a very nice "selective export" feature covered in section 7.8. I wish C++ had something like this.
p. 195 "System Execution". Here's the "top" I've been waiting for. A "root object" containing a "creation procedure". The programmer supplies this procedure to create all needed objects, connect them together, and get them started.
p. 197. Meyer insists the "root execution procedure" is not a "main" function because it should only create other objects and get them going. Also on this page, Meyer promises not to trash "top" any more. "You will not always build systems." Well, then of course a top isn't needed. Libraries need no top, but systems do.
p. 201 "Structure and order: the software developer as arsonist". Again, Meyer insists that order of execution is not important. Instead, he says the software developer is a "firework expert or perhaps arsonist" that "prepares a giant conflagration, ... lights up a match and watches the blaze." If that doesn't speak volumes, I don't know what does.
I was skimming Deitel and Deitel's C++ How To Program (1994) and I noticed that they picked an elevator simulator as their example to show the power of O-O methods. This is pretty telling since O-O excels at simulation. After all, it comes from a language called "Simula" that was intended for simulation. O-O is great at modeling autonomous objects that interact with one another. O-O is also strong on reuse with its emphasis on classes as modules. But that doesn't mean we should use a hammer to drive a screw. I think that for interactive programs, a pure O-O approach is inappropriate. The behavior of such a system is potentially so interesting that it usually deserves an object of its own. But an object that contains only behavior is not an object at all.
p. 214 "Systems should have a decentralized architecture. Ordering relations between the operations are inessential to the design."
p. 218 "its early successes ... in such areas as simulation and user interfaces." Although Meyer is not interested in the user (since that would introduce a "top" to a system), he does mention O-O UIs. These are UIs that are graphical and allow manipulation of objects (such as icons) through drag-and-drop and other means. Is O-O really good here? His mention of simulation is obvious. Also on this page, Direct Mapping property: objects in the software should map to objects in the real-world.
p. 219 "The classes introduced for design and implementation ... are ... the most difficult to find." This is true. I was hoping that Meyer would give examples and advice about finding these hard-to-find classes. However, by the end of the book, I found only vague advice pertaining to finding the classes in a system. External Objects (real-world) vs. Software Objects.
p. 228 "The concern for simplicity applies to the software text and not necessarily to the run-time object structure." Solution is "tools that help explore object structures for testing and debugging." To create such complexity requires complexity in the initialization code. This starts to feel like a "top" then. For some applications, the complex organization of objects might be created by the user as a document.
The more I read, the more I mellow as the book is definitely getting better. Although the controversy is still there, the good stuff is outweighing it. It might be that Meyer is overcompensating in this book. Perhaps the intent is to get the reader to see the value of O-O by hitting them over the head. Maybe Meyer assumes his audience is reluctant to change? In general, all his points have some merit. However, his evangelical tone is a turn-off to me. I already understand everything he is saying. He's preaching to the choir in my case.
One could interpret this evangelical approach as irresponsible. His points should stand on their own without having to pretend to be controversial to get people's attention. The controversy could be misleading to the uninitiated. The book's tone might be good for those stuck on Structured Design who need convincing. However, even they might be quickly turned off.
The portion of the book I'm in now (Chapter 8) is getting pretty deep into the details of the Eiffel language. No controversy on the horizon as far as I can see. Somewhere 'round page 600 I think there may be some. This brings up the interesting point that if you are looking for a general O-O book, this one is mired a bit too much in defining the Eiffel language. Although it does emphasize general O-O ideas, it still can't escape the nitty gritties of defining a programming language. It's good, but it might not suit everyone.
p. 300 hints at the concept of "once functions". These are interesting as we will see in section 18.3 (pg 646). At first, I mistook these for being similar to C++'s static functions and member objects, but instead a once function is quite different. It is useful when implementing a constant within a class. Basically, it only runs the first time it is called, hence the term "once". The second time it simply returns the result that was computed the first time. It's an efficiency thing.
p. 346 - "Input and Validation Modules" These strike me as possible "implementation objects". Are these "behavior objects", MVC controllers, models of external objects (temperature sensor), or all three? Unfortunately, he never goes into this further. It appears that these are just simply what they look like: Modules that validate data and handle anything that is out of bounds in some appropriate manner.
p. 367 (2nd paragraph from bottom) mentions that features change most frequently. As opposed to classes? Features can be data or functions, so what is it that Meyer really thinks is less likely to change, classes or data? I originally thought he meant that data doesn't change, but functions do. (See p. 103 "should we structure systems around functions or around data?") Saying that data is less likely to change is a stronger statement. Since classes are composed most importantly of data, this means that data and classes are least likely to change. p. 367 seems to imply a weaker constraint: that features (data and functions) change more frequently than classes. e.g. if you have a "Temperature Sensor" class, it is likely that you will always have that class, but its features might change dramatically over the life of the software. This is interesting as it implies that the only thing that doesn't change is the modular arrangement of a system (its classes). The actual code changes like crazy. p. 115 is fairly specific indicating it is the types of objects that are least likely to change, with no mention of data. How can there be any benefit in organizing software along these O-O boundaries? What is the benefit, if any? Then he gives in, "we can hardly guarantee that any aspect of a system will remain set for eternity."
p. 392, near bottom. Short form is called more abstract. Is this what he meant on pg. 118? Abstract representation? I don't see how an abstract representation of an object is going to help find abstract behavior like top-down encourages. We are left hanging on pg. 118, will he deliver? No, he never does deliver on this.
It just struck me that Meyer likes to say that classes are implementations of ADTs. But then what about implementation classes? They aren't always ADTs as they don't necessarily have any data. This is a contradiction.
p. 425, Meyer presents a command processing "top". Interesting.
p. 460 the shape class has a "display" feature. It's interesting to note that he doesn't ever specifically talk to MVC later, and how one might want to separate the display part of a class into its own class. (MVC is mentioned on pg 734) Again, this isn't an architecture book, and that is an architectural issue, not a construction issue.
p. 503, Behavior Class. In a case where there might be multiple implementations, the general behavior of a class is factored up into a parent class. Then the ancestors implement the behavior in their own unique way. This is basically the Gang of Four's "Template Method". Event driven windowing frameworks tend to use this approach. Each application derives from a parent "Application" class which provides support for some of the basics, but then the child class implements the portions specific to the application being developed. This is a very common approach in application framework libraries.
As an exercise, I thought briefly about using Behavior Classes to implement the "Controller" part of the Presentation/Abstraction/Controller pattern. But it became obvious that this doesn't work very well. Generally a Controller is very specific to what an application is doing. There is not likely to be multiple classes deriving from it. Deriving the abstraction or the presentation from it seems absurd.
Since Meyer's treatment of Behavior Classes is limited to ADTs, I don't think this applies at the larger architectural level. When I think of behavior classes, I think of controllers. That's not what Meyer is talking about. The issue is with terminology. What he calls "Behavior Classes", the Gang of Four call "Template Method". What I call a "behavior class" is what Coutaz 1989 Architecture Models For Interactive Software calls a "Control" class in the Presentation Abstraction Control pattern.
p. 547, Multiple Inheritance. This French/US Driver example is pretty bogus. This implies that the source code would have to have a class for every possible combination of drivers. A real implementation would use containment instead. A "General Driver" class would have a list of "Driver" references which could each point to driving records for any number of countries. It is clear that this example is being used to illustrate the points, and it does so wonderfully. But, the fact that it is so bogus detracts from its value, especially to the beginner. It is misleading. Is there a more realistic example that could be used? Coming up with one seems pretty tough as this sort of thing rarely occurs in real code. (Meyer agrees that this happens rarely on the next page and says that these cases are not for beginners, but he never points out that the driver example would be better implemented in a different way).
p. 606, in Eiffel, you can completely override information hiding by inheriting. Descendents have complete access to ancestor's private features. C++ provides more control through protected (like Eiffel) and private (not in Eiffel) access levels.
Chapter 17 was definitely a tough one. The issues discussed like type covariance and feature hiding are not issues in C++. Although both are interesting solutions to problems. It would be interesting to investigate how to solve those same problems in C++. I guess for covariance, you would just keep the base class type and do type checking at runtime. For feature hiding, you would have to override the features and detect violations at runtime as well.
pg. 643, here come the globals. I knew they had to appear at some point. So far, Meyer has made a case for reusable libraries and modeling of real-world objects. Nowhere have any sort of mechanisms been described that can tie it all together and make a system work. "Object technology is all about decentralization, all about modularity, all about autonomy. It has developed from the beginning of this presentation as a war of independence for the modules, each fighting for its freedom from the excesses of central authority. In fact, there is no central authority any more." Then there is no system. This is what makes me most uneasy about "object technology". Something always seems to be missing. A system comprised of objects does nothing unless something ties it all together and makes it work. Whether this something is centralized, decentralized, or minimalized, it must still be there.
Chapter 18 on globals brings up some interesting points. Inheritance is used to bring in classes that contain global constants and other useful global things such as "once functions". This is borderline "utility class" and certainly it is possible to create utility classes in Eiffel if you want to. Note that again inheritance comes to the rescue. Eiffel sure seems to be all about inheritance.
The process for defining constant objects is particularly convoluted, requiring a once function and a class invariant which duplicates the values that appear in the function. So much for having the values in one place. This reminds me of C++ which isn't quite that bad, but still requires that you declare the constant in the .h file, then define the value for it in the .cpp file. Why is it that object oriented languages seem to handle constants so poorly? Constants are a good thing. They should be easy to create and maintain.
Here we go, this is what I've been waiting for.
Chapter 20 presents a design for an interactive flight reservation system consisting of multiple "panels" (full screen text-based dialogs, very retro) where the user can enter data and select what they want to do next. Meyer starts with an unstructured design (complete with gotos), evolves that into a Top-Down design, then evolves that into an Object Oriented design. The focus is on the high-level interactive aspects of the system. This is good since this is where most O-O newbies will find themselves confused: After I make all these wonderful autonomous objects, how do I connect them all together so they can do something?
First, the "unstructured" solution (20.2). I admit it is ugly. I admit I would never do this. But I also admit (I'm not sure Meyer would) that this example actually satisfies some of the O-O goals and requirements described in the book. As an O-O advocate myself, I find it rather embarrassing. Let's look at this example in light of his three problems for generality and flexibility. Bear in mind that Meyer requires that the reader make a mental leap here from a list of problems (G1-G3) to how they affect generality and flexibility. This is confusing and therefore somewhat suspicious when one is trying to make a point. I will try and make the leaps here, although only Meyer can tell us exactly what he intended.
"G1: The graph might be large." It sounds like his point here is that the system might be so big that it would be rather hard to follow the transitions while reading the code. Funny thing, that's because the control of this application is too decentralized. But Meyer insists that decentralization is an O-O hallmark. In his later revisions of this example he will indeed centralize the control by putting it into a state machine. Advantage: centralization of control, a quality Meyer claims is not O-O.
"G2: The structure is subject to change." It sounds like the goal here is that change should be easy. For this, I will rely primarily on Modular Continuity. Ok, let's consider some possible changes. Two come to mind (please tell me if there are more). The first would be a change to the flow (transitions) of the system. In the "unstructured" solution, this would mean changing the text on the panels and changing the corresponding gotos. Well, fortunately, they are both easy to find and together in the same block. I would say we have a pretty good case of modular continuity at the "block=module" level. The second possible change is the addition or deletion of a panel (state). This is no big deal either as we add or delete a block, then modify the transitions as described above. There is some duplication of code for the input processing, but one could easily factor those pieces into functions (admittedly structured, but what the hey?).
"G3: Nothing in the given scheme is specific to the choice of application." In other words: The scheme must be reusable. Well, it is indeed. The "unstructured" solution's scheme certainly is no more specific to airline reservation systems than it is to banking systems or anything else. I think what Meyer meant to say is that the approach should yield modules that can be reused on other projects. This is what he calls Modular Composability. (I really wish I didn't have to write his book for him.) Admittedly, it fails miserably here. The modules (blocks) aren't really very reusable directly. What is reusable is the system's skeleton. One could copy and paste one block from this system and begin creating a whole new system by replacing the interesting parts. By the time we get to the O-O approach, we do get the reuse of a state machine class (APPLICATION), but we still will need to cut and paste the STATE classes and use their skeletons to implement each new panel.
So, I've just demonstrated how Meyer's arguments against the unstructured solution are lukewarm at best. However, I still would never write a program this way. I would indeed use an O-O approach. Meyer has picked an example that is unfortunately too limited to show the real power of O-O. Only as the system gets larger do the benefits become clear. O-O is somewhat subtle and its advantages only become clear after years of use. Even then, there are so many different ways to solve a problem using O-O techniques that it is sometimes very hard to know where to begin. It is unfortunate that he doesn't cover any of this since this is not a book on large scale architecture. This flawed example in chapter 20 is all we get.
Next, in section 20.3, the "top-down" solution. Interestingly, this isn't simply a top-down solution. It is the introduction of a state machine along with a top-down decomposition. The state machine is introduced as a device to get rid of the gotos, and to distract the reader from the possibility of a better solution. There are many ways we could travel this road, and unfortunately, Meyer chooses the worst to illustrate the benefits of O-O. The original solution could have been decomposed in a top-down fashion with the gotos remaining, and each panel having its own function. Then something other than a state machine could have been used to dictate the system flow. For example, each panel could return a value indicating which panel was next. This has many advantages along the lines of modular continuity. On the downside, this means the panels must know about each other (coupling). But then, they know about each other anyway as they have each other's names on them as selections the user can make. The final O-O solution can also benefit from this by placing the panel flow decisions within the panel (STATE) classes themselves.
I don't think someone seriously considering top-down design would have focused so much on the state machine mechanism. Only someone interested in a final O-O design would do something like this. Since that is Meyer's goal, this approach is perfect to prove his point. Although, in light of the above it doesn't prove it at all.
In 20.4 Meyer opens by saying that the solution so far "falls short of our goals of extendibility and reusability." It is interesting to note that this "top-down" design is actually worse than the unstructured design in many ways. The various second level functions all have to switch on STATE. This means adding a new state means touching a mess of functions. In the unstructured solution, the change was confined to a single block. This leads one to believe that there is most likely a better top-down decomposition for this system hiding someplace that captures the positive aspects of the original. (See above) It appears as if the haphazard introduction of the state machine has brutally sabotaged the architecture.
(I need to do the "right" top-down decomposition and see how it turns out. First, decomp into one function per panel, instead of Meyer's contrived decomp. Introduce the state machine after the goal of modular continuity is achieved in a top-down decomp. Possibly even consider introducing a state machine before top-down just to see how it would be done in the unstructured approach. This will prove my point even more powerfully if it turns out to be fairly easy.)
20.5: The transformation of the state machine into a set of classes should be pretty obvious at this point. APPLICATION is the state machine class, and STATE is the panel class. Are they reusable? Pretty much, although their names are rather poor. STATE can be copied and rewritten to make new panels for the system. It should be called "PANEL". APPLICATION is a reusable state machine and should have a name that indicates what it does, like "PANEL_MANAGER". Extendible? Here's where this design falls short. To extend this system requires that one add a new panel class and update the state machine by modifying the *creation procedure*. Yuck! So much for Modular Continuity. It would be much better if the state transition decisions were made by the individual panel classes. This would be the next logical step in the evolution of this system. I think this can be done in an O-O way and be very beneficial. Meyer stops short of this goal, however, and leaves us with a solution that isn't very satisfying.
That the creation procedure plays such an important role and must know so much about the system is an indication of the strong coupling between the various parts.
The big picture reveals a design that is clearly top-down. APPLICATION is the top class, STATE is the second level class. Within each class there is clearly a top-level function named "execute". Along the way, we did see some decentralization as a very poor design was corrected, but the overall architecture is still centralized. The decentralization that Meyer preaches hasn't happened in this example. Neither has the "real systems have no top" claim. This is a rather disappointing example.
*** Below are notes after my first read through Chapter 20. Some of what was discussed above may be re-hashed. I should combine what's below into the discussion above. Maybe if I have some time one day. As it is, there are some important points in here that will remain.
The trick here is weighing whether the O-O version is successful at supporting Meyer's points about the benefits and qualities of an O-O system, and particularly his three problems for generality and flexibility stated on page 676. Two main tranformations occur on the way to O-O.
First, the STATE class is created by noticing that STATE is passed around a lot amongst a number of functions. I personally would have called the class PANEL since it encapsulates the display, verification, and processing done by a panel (dialog) in the system. This is a focus on the data instead of the functions that yields a coherent module that is easier to update. The system also becomes easier to extend as one must only create a new class that inherits from STATE and add it to the system to get a new panel. This is powerful stuff, and Meyer does it right. It would be interesting to consider a number of likely changes to the system and see how hard it would be to make those changes in this O-O design versus the non-O-O design. (see the above section, where I do indeed do that)
The second transformation is a bit less exciting. Basically, all the rest of the functions from the Top-Down design are bunched into an APPLICATION object whose main function is called "execute". How disappointing. I thought real systems had no top. Clearly, this system has both a top class (APPLICATION), and a top function (APPLICATION's execute). Again the name APPLICATION is rather poor. In reality, this is more of a PANEL_STATE_MACHINE class that uses a state machine to sequence the panels in the system. PANEL_MANAGER would also be a good name since it is at a higher level of abstraction and hides the implementation details. Either of those names is more reusable than APPLICATION. Although this poor choice of names really clouds the power of O-O, this isn't what I take issue with.
The trick now is to go through all of Meyer's claims about O-O systems (benefits and qualities, Chapter 3) and discuss them in light of this example. It's too bad Meyer doesn't do this himself, it really is his responsibility as our guide through the world of O-O. In my opinion, "exercise for the reader" is a cop-out.
Modular Continuity. Classes as ADTs. Data first, behavior second. Centralized vs. Decentralized. Premature ordering. Reusability. Real systems have no top. etc... (need to talk to each)
Modular Continuity. One thing that really bugs me about this design is that the creator of APPLICATION is responsible for setting up the state machine. This means that if there is a change to the flow of the panels, a change will likely need to be made to the panels themselves (to display a new option) and of course to the creator of APPLICATION. After the system grows substantially, this could be a very annoying change to make. Modular Continuity doesn't hold very well in this case. The perceived increase in modular continuity in the STATE class is a mirage. If the top-down decomposition had been done to favor the blocks as modules, modular continuity would have been maintained from beginning to end. As it is, this top-down decomposition was selected to make O-O look good and top-down look bad.
Classes as ADTs. The lack of modular continuity when changing the flow of the panels is probably due to the conversion of APPLICATION from a simple class filled with functions to a configurable state machine. If we were to move the configurability from the outside to the inside of APPLICATION, we would increase modular continuity slightly by bringing the two places that need to change closer together. To me, this makes sense as the world outside APPLICATION has no need to know about the intricacies of panel sequencing in this system. It's not like they could change it in any way that was useful without considering the implementation of the panels and their content. This should probably be encapsulated. But, now APPLICATION is a class that only has an execute function exported. It no longer looks anything like an ADT. It looks merely like a single function. We can take this evolution one step further and make the STATE classes themselves responsible for selecting the next state. This would give us total Modular Continuity for many kinds of changes to the system. This is modularity taken to the extreme. It's almost plug and play.
STATE already isn't much of an ADT. It just has execute, and that's pretty much it. The passing of user responses back to APPLICATION doesn't make it much of an ADT. I think the lesson to be learned here is that not all classes are ADTs, and there is nothing wrong with that. In fact, trading off ADT-ness will in some cases allow us to increase encapsulation and Modular Continuity.
I find it interesting that in this design, we see both UI code and processing code in the PANEL class. I assume there is another set of objects representing flights and the reservation database that this PANEL class interacts with to do its job. It's so Doc/View. I wonder what sort of balance Meyer advocates when deciding what portion of the processing belongs in the UI and what portion belongs in the underlying ADTs. My bet is that most belong in the ADTs.
Chapter 21 - (pg 695) Here, Meyer presents a typical undo feature implemented using polymorphic command classes. Nothing new here, although the treatment is more detailed and easier to understand than the Gang of Four's treatment of the command pattern. See the comments above on pg 41 for my take on the "Command Pattern" and its implications for Modular Continuity.
pg 701(?), "the basic interactive step". This is clearly a top-down design.
Chapter 22, How to find the classes (pg 719). Talent, experience and luck are required to find the classes. There are no rules. This is a pretty realistic and useful chapter. No definite rules are given, only some suggestions which may or may not help.
pg 732, "all software is simulation." One could use this argument to say that if O-O is good mainly at simulation, then O-O is good at all software. The connection between simulation and O-O is discussed here.
pg 734, "...'controller' classes as in the Smalltalk MVC model are good examples of design classes." Sounds like he advocates MVC.
pg 738, Meyer criticizes Use Cases as being unsuited to O-O analysis and design. See my review of Jacobson 1992 for more on this issue.
Very few examples of the "hard-to-find" design classes are given here, and when they are given, they are glossed over. Unfortunately, he never presents a helpful catalog. Again, this isn't a software architecture book, it is a construction book.
pg. 844. It is becoming obvious that Meyer is focusing primarily on low-level design issues in this book. Very little time has been spent considering the larger architectural picture. In fact, the two case studies were the only examples, and they were nowhere near enough. In fact, they were geared more toward discussing the low-level issues. Most of the design guidance is focused on the ins and outs of using each of the typical O-O features. This usually requires considering no more than two classes at a time and the interactions between them. This isn't at all what I was looking for in an O-O book.
pg. 1122. "True to its origins, Simula includes a set of primitives for discrete-event simulation. It is no accident, of course, that the first O-O language was initially meant for simulation applications; more than in any other area, this is where the modeling power of the object- oriented method can illustrate itself."
<- Back to my software books page.Copyright ©2005, Ted Felix. Disclaimer