This example is probably what prompted me to buy the book. I was thirsty for real-world examples after all the shape and animal examples, and this was about the only one I could find on the shelf.
Note that this topic relates a bit to the top-down discussion of chapter 5. I recommend reviewing the comments and examples about that chapter first.
My solution to this "pattern" would actually resemble the "simple-minded" version shown on page 677, but with a few differences:
sub Flight_Enquiry if not pre_condition_X() Return "Pre-condition X is not met." endIf Until answer_ok r = Display_Page("Enquiry") if bad answer Display_Error endIf endUntil select r case A: call page_A case B: call page_B case C: call page_C case [etc...] case EXIT: return endSelect endSubLike the example under the chapter 5 review, the beginning of the routine checks for the proper prerequisites. Perhaps pre-checking is not needed if the page (screen) calls are assumed to always be right. However, we will keep them in our pattern.
Also, the Until loop and error display could be perhaps built into the page-display framework (which good Data Dictionary systems are often ideal for in my opintion). This allows the stuff in the Until loop to be factored into one place. Thus, we end up with something more like:
sub Flight_Enquiry if not pre_condition_X() Return "Pre-condition X is not met." endIf r = Display_Page("Enquiry") select r case A: call page_A case B: call page_B case C: call page_C case [etc...] case EXIT: return endSelect endSubIn fact, if we don't need the pre-conditions (or they are stored a different way), and the "next" pages a built into the screens (via IDE), then there is no longer any routine! The page display driver controls it all. The framework can also perhaps automatically supply a "previous" and "to main" option.
By chance if we still need the prerequisite sections, then we can still have a prerequisite routine:
sub Pre_FlightEnquiry result = "" // blank = good, init if problemA result = "Boo boo in A, please fix the foobar." elseif problemB result = "Boo boo in B, Discount does not apply if blah." elseif problemC result = "Your C is bad. Cut down on the hair-spray." endIf return result endSubOur page framework calls this routine (or "snippet" in some circles.) Sometimes a bunch of these are in a case-like structure rather than one routine per page/screen:
sub pre_validate(pageID) select pageID case "Flight_Enquiry" blah // code similar to prior example case "Reservation" blah case else handle_error endSelect endSubSometimes there is also a post-validation operation for each screen that has a similar pattern. (This is for stuff that is more complex than simply picking the wrong menu choice.) In some variations I mix them:
sub validate(stage, screen) result = "" // initialize if stage="pre" and screen="reservation" blah blah endIf if stage="post" and screen="reservation" blah blah endIf if stage="pre" and screen="seat_enquiry" blah blah endIf return result endSubI found the "stage" concept to actually be quite useful for web page forms. There can be other stages besides just "pre" and "post".
An OO fan would perhaps look at this example and complain that the validation should be grouped (encapsulated?) by screen, not by operation. However, such a grouping decision is very subjective and application-specific. This all goes back to the issue of which grouping to favor at the expense of which other. Because code is one-dimensional, there will usually such tradeoffs.
However, the above setup let's the programmer chose the physical grouping they want, since the order of the IF statements does not matter. (Grouping tradeoff was discussed before in several places.) One could even organize it like this:
sub validation result = "" if screen="reservation" if stage="pre" blah elseif stage="post" blah endIf elseif screen="seat_enquiry" blah blah ... endIf endSubEven the "execute" step (if there is one) can be made into a stage, and thus grouped per screen just like the OO solution. Note that most inter-stage data is stored in the data tables. (Sometimes a few global variables are used. I know OO frowns on globals, but a handful have never been problematic to me.)
NOTE: Meyer did not really deal with the issue of validation, so my description and examples about how to handle it should not be used by itself to conclude that my solution is more complex than Meyer's. However, in my opinion one cannot ignore validation in these patterns because it is usually a key issue.
There is often what one can call field-level validation and screen-level validation. Screen level validation is for issues that involve multiple fields where the sequence of field entry cannot be assumed. For example, there may be a home-phone and a work-home field. The requirement is that at least one must be filled in. Field-level validation cannot really catch this because one field does not know the other's future. Think about it.
On page 678, he complains about the "goto" nature; but it is not really a goto structure. The gotos would probably wind up being subroutine calls or something else in a real system, not actual goto statements. His goto complaint seems purely superficial.
Using routine calls would collapse the call stack whenever the user selects "to main" or "finish" for a customer, because every actively "stacked" routine would reach the end. Thus, there would be no endless recursion-like memory consumption.
Also, exiting one screen will not necessarily bring one back to the prior screen because there is no "paint" or "repaint" command after the call statements. If we decided to do such (optional), then we could have an outer loop. As a user-interface issue, perhaps each screen should have a "Back" button in addition to an "Exit" button. This can be implemented by having each screen routine return a code that tells it to either redraw (continue looping) or exit the redraw loop, and thus the routine. The looping structure can perhaps be put into a single utility routine or an "include file" to avoid having to repeat the looping structure code for each screen. Each "section" of the screen would then have a standard subroutine name. These sections could be names like "draw()", "validate()", and "transfer()". Each screen then becomes a module instead of a routine. Note that not all languages may support such a module and/or scoping structure.
I am also assuming much of information is stored in tables with names like Customer, Seat, Flight, etc. Tables are discussed more in chapter 31.
Next (same page), he complains that it hardwires the current structure into the algorithm. I am not quite sure what he means by that. The screen links are going to be somewhere in the code (or the data dictionary or GUI binaries) regardless of paradigm. They are only going to be in one place, so I don't see a factoring problem. (Whether the page links are in the screen IDE files or in program code is a project or personal choice.)
Perhaps he means that changing one screen will create a chain reaction, and violate the Continuity Principle. However, I do not see this to be the case. It closely reflects the change scope: change one screen, then only the code for one screen need be changed.
No screen has to worry about what came before it, other than making sure any needed prerequisites are met. Any given screen's links to other screens are through either the menu links (shown as buttons above), or indirectly through the prerequisites. Each screen cannot get more independent than that.
Some might say that "reaching inside" a big validation routine to get at the section of code for a screen is a "bump the neighbor" risk. However, calling the block a "method" instead does not solve any such thing. There is always going to be neighbor code blocks of some type. Giving them a different name, such as "method" instead of "case" or "if" does not really make it safer. Bumping is bumping. Some OO fans seem to play name games with code blocks to make them seem safer from proximate code changes. NOT! (See the Shape Example for more discussion on accidental neighbor bumping risk issues and proximity grouping issues.)
Page 680, "[Modeling] Real-worldliness is not [for the most part] a significant difference between O-O and other approaches; what counts is how we model the world." [emphasis changed]
Amen! However, many OO fans (and PHB's) still think this is something that sets OO apart.
Page 693, "It shows in particular the benefits of getting rid of the notion of [a] main program."
This "main" thing and "sequential lock-in" was discussed under chapter 5 as a false dichotomy between OO and others. Note that I did not choose Meyer's top-down version (page 678) to model my version of the panel example after.