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
endSub
Like 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
endSub
In 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
endSub
Our 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
endSub
Sometimes 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
endSub
I 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
endSub
Even 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.