home *** CD-ROM | disk | FTP | other *** search
- This file contains a description of the TBrowse inheritance example
- provided, in the files DBROWSE.PRG and DBROWDEM.PRG. This was adapted
- from an article in Reference(Clipper), August 1991.
-
- Executing the batch file MAKEALL.BAT will create the demonstration program,
- DBROWDEM.EXE. It will have debug information included, so is interesting to
- trace thorugh in the Clipper debugger.
-
- ---------------------------------------
-
-
- dBrowse: Inheritance from TBrowse
- =================================
-
-
- The pulldown menu example in the manual is highly object-oriented,
- consisting of five interacting object classes. This provides an
- interesting view of how such a system can be structured. But few of us are
- in a position to begin writing completely object-oriented Clipper systems
- right now - although some people are successfully doing so.
-
- This document discusses a more immediately practicable example of using
- user-defined classes in a Clipper program. We are going to 'inherit' a new
- class from the built-in Clipper class, TBrowse, resulting in a class which
- is more complete, easier to use, and without sacrificing flexibility.
-
- I have not attempted to create an "ultimate" browsing class here. Instead,
- I have taken a piece of code which will be familiar to many readers -
- Nantucket's sample program TBDEMO.PRG - and reworked it as a new class,
- called dBrowse, inherited from TBrowse.
-
- TBDEMO.PRG can be found in Clipper 5's SOURCE\SAMPLE subdirectory. The
- version I am discussing is the Clipper 5.0 version, _not_ the 5.01 version.
-
- It uses the TBrowse class to do a fairly simple database browse, with
- editing. The majority of the code in TBDEMO is devoted to setting up and
- using a TBrowse object. Essentially, TBDEMO 'specializes' the behaviour of
- TBrowse, by providing a specifically database oriented browsing capability.
-
- It is not a coincidence that this exact kind of specialization is a major
- characteristic of any inherited class in a well designed object-oriented
- system.
-
- What most of the code in TBDEMO is saying, in no uncertain terms to those
- willing to listen, is that it should be implemented as a subclass of
- TBrowse. It makes little sense to revert to procedural programming, 'just
- when we could most use object orientation to manage the complexity of a
- general class such as TBrowse'.
-
- The Code
- --------
-
- To start with, we have declared the dBrowse class as follows:
-
- create class dBrowse from TBrowse
- instvar appendMode
-
- export:
- method autoFields
- method exec
-
- method goBottom
- method goTop
- method skipper
- endclass
-
- So the dBrowse class 'inherits' all of the instance variables and methods
- of the TBrowse class. In addition, a new instance variable and five new or
- changed methods are added.
-
- The new instance variable, 'appendMode', makes quite a subtle, but
- important difference to the code. In the original TBDEMO, a variable
- called 'lAppend' is declared local to the MyBrowse() function, and is
- continually passed to other functions such as DoGet() and Skipper(). If
- MyBrowse() were broken up into smaller procedures, as has been done in the
- dBrowse class, 'lAppend' would have to be passed as parameters to them too.
- By making it an instance variable, it becomes accessible to all the methods
- in the class, and it no longer has to be passed as a parameter.
-
- Cargo is a kludge!
- ------------------
-
- It is interesting to note that Nantucket recognized this little problem
- with the 'lAppend' variable, and in the Clipper 5.01 version of TBDEMO,
- used the TBrowse 'cargo' slot to hold the append mode flag instead.
- Unfortunately, to retain readability and maintainability, they have had to
- use the preprocessor to hide the underlying instance variable name, as
- follows (taken with comments from the latest version of TBDEMO):
-
- // These #defines use the browse's "cargo" slot to hold the
- // "append mode" flag for the browse. The #defines make it
- // easy to change this later (e.g. if you need to keep
- // several items in the cargo slot).
- #define TURN_ON_APPEND_MODE(b) (b:cargo := .T.)
- #define TURN_OFF_APPEND_MODE(b) (b:cargo := .F.)
- #define IS_APPEND_MODE(b) (b:cargo)
-
- This approach can only lead to trouble and complication, and only serves to
- highlight the fact that the entire concept of the 'cargo' slot, in all four
- of Clipper's predefined classes, is a workaround for the fact that Clipper
- itself does not support user-defined classes and inheritance.
-
- Overall program structure
- -------------------------
-
- The original TBDEMO code consists of four functions, TBDemo(), MyBrowse(),
- Skipper(), and DoGet(). I will briefly discuss each function and how it
- was modified to fit into the dBrowse class.
-
- Before we do that, though, there is one change which was made repeatedly
- throughout the file which we should examine. In the original TBDEMO, there
- are about 50 occurrences of a message send to 'b', which is the variable
- containing the TBrowse object. But with the new structure, TBrowse is no
- longer a foreign entity - it is dBrowse's parent class. As such, all
- TBrowse methods needed in dBrowse can be invoked by sending a message to
- 'self'. So all occurrences of 'b:' in the dBrowse methods were replaced
- with the double colon, '::', which is Class(y) shorthand for sending a
- message to 'self'.
-
- The very fact that so many messages were being sent to the TBrowse object
- in TBDEMO was another strong hint that those functions should have been
- methods in the class or a subclass, such as dBrowse.
-
- TBDemo()
- --------
-
- This function demonstrates the use of the MyBrowse() function and does not
- require much explanation. A separate module has been created for this
- purpose, called DBROWDEM.PRG. Extracting the relevant portions from this
- module gives us something like this:
-
- local dBrow := dBrowse():new(5, 5, 15, 70)
- dBrow:autoFields()
- dBrow:exec()
-
- We have implemented the browse in three lines here. What each of these
- lines do is explained more fully below.
-
- MyBrowse()
- ----------
-
- This is the main browsing function. It can be broken into three parts:
-
- i. A TBrowse object is created and some of its instance variables are
- initialized. In a class, this is usually the job of the constructor,
- so we have extracted this code to the 'new' method in our dBrowse
- class.
-
- ii. A loop is executed in which a TBColumn object is created for each
- field in the currently selected database table. This object is then
- added to the TBrowse object using the 'addColumn' method. To avoid
- making the dBrowse class so specific that it is only capable of
- browsing all fields in the current DBF, this section has been made
- into a separate method, called 'autoFields'. It is then under the
- caller's control whether the columns to be browsed are taken from the
- current DBF or from some other source.
-
- iii. Finally, an event loop is entered which 'stabilizes' the browse while
- waiting for a keystroke. When a key is pressed, a CASE statement is
- used to execute the appropriate action. For dBrowse, this section has
- been made into a method called 'exec', since it is when this method is
- called that the browse is actually activated.
-
- Skipper()
- ---------
-
- This function controls moving the record pointer through the data. This
- has been made into a dBrowse method, also called 'skipper'. As mentioned
- earlier, the original second parameter, 'lAppend', is not required. It is
- replaced by the instance variable 'appendMode' which is automatically
- available to 'skipper'.
-
- DoGet()
- -------
-
- This function handles the editing of individual cells, or fields, in the
- browse. This has been made into a method, also called 'doGet'. Unlike the
- original function, this method does not require any parameters. The
- original first parameter, 'b', is replaced by the implicit 'self'. The
- second parameter, 'lAppend', is replaced by the instance variable
- 'appendMode'.
-
- Where did the code blocks go?
- -----------------------------
-
- Two methods in the dBrowse class have not yet been mentioned, since they do
- not have direct equivalents in TBDEMO. These are 'goTop' and 'goBottom'.
- They are 'replacements' for methods of the same name in the TBrowse class.
- This has been done more as an interesting exercise than because it is
- necessary. In the TBrowse class, these methods operate by evaluating the
- corresponding code blocks contained in the instance variables 'goTopBlock'
- and 'goBottomBlock'. This technique was needed to enable TBrowse to browse
- data from different sources, where the code to move to the top or bottom of
- the data would vary with the data source. Here again, as with the 'cargo'
- instance variable, a workaround has been used to compensate for Clipper's
- lack of an inheritance capability - in this case by using instance
- variables containing user-supplied code blocks. To prove that these are
- unnecessary, these methods were reimplemented without using code blocks.
-
- Unfortunately, the same approach could not be taken with TBrowse's
- 'skipBlock' instance variable, since there is no single corresponding
- method which evaluates it. Rather, 'skipBlock' is evaluated directly in a
- number of places inside TBrowse. This forces us to have a level of
- indirection, using the 'skipBlock' code block merely to invoke our
- 'skipper' method.
-
- How to use the class
- --------------------
-
- The demonstration program, DBROWDEM.PRG, shows how a browse can be created
- and activated in only three lines. This could easily be done in one line,
- but the advantage of the former approach is that we can manipulate the
- dBrowse object in other ways, before starting the event loop using the
- 'exec' message. After all, dBrowse is a subclass of TBrowse, and as such
- has all of TBrowse's methods available to it. For example, instead of
- invoking the 'autoFields' method, we could easily write a loop using the
- 'addColumn' message to create calculated fields, or to obtain data from
- multiple related DBFs.
-
- What next?
- ----------
-
- What has been done so far provides little more than a start towards a
- powerful yet easy to use browsing class. The changes made to TBDEMO were
- deliberately kept to a minimum to illustrate how easy it can be to convert
- to a class-based design. As a result, our dBrowse class is not really much
- more useful than the original TBDEMO. The crucial difference is that it
- should be easier to expand and enhance - as we will now examine.
-
- First of all, dBRowse actually goes too far, too quickly. In one step from
- TBrowse, we have a class that implements an event loop, does field editing,
- and is tied to a database source. However, the event loop is quite a
- general piece of code, which we could use in, say, an array browsing class.
- The obvious solution here is to have an intermediate class, which we will
- call GenBrowse, as illustrated in the following class hierarchy diagram:
-
- ┌─────────────┐
- │ TBrowse │
- └──────┬──────┘
- │
- ┌────────────┴────────────┐
- │ GenBrowse │
- │ │
- │ General purpose browsing│
- │ class which implements │
- │ method exec - a default │
- │ event handling loop. │
- └──────┬──────────────┬───┘
- ┌─────┘ └─────────┐
- ┌───────────┴──────────────┐ ┌──────────┴────────────┐
- │ dBrowse │ │ aBrowse │
- │ │ │ │
- │ Database-oriented browse │ │ Array-oriented browse │
- │ which implements methods │ │ │
- │ such as autoFields. │ │ │
- └──────────────────────────┘ └───────────────────────┘
-
-
- With such a class structure, we can achieve maximum expandability with
- minimum rewriting of code. An important point to remember when dealing
- with inheritance is that you should only have to program the exceptions and
- additions in an inherited class - if you end up duplicating code in
- different classes, their is probably something wrong with the design. By
- moving the 'exec' method up into the GenBrowse class, we avoid such
- duplication.
-
- To take this one step further, it can easily be seen that at the bottom of
- the above tree we will need to inherit a new class from GenBrowse for each
- different data source we wish to browse. This is not totally sensible;
- after all, we are trying to develop a browsing class, not a data providing
- class - but the latter would in fact provide an excellent solution. If we
- had a general purpose data providing class, with standard methods such as
- 'goTop', 'goBottom', and 'skip', we could have a subclass of this class for
- any type of data we might wish to handle - even, for example, SQL or
- Paradox data. OOPS! (sic) - I think we've just invented RDDs - replacable
- database drivers! Seriously, what this would buy us is the capability to
- implement a single general purpose browsing class which accepted a
- data-providing object as a parameter. We could then browse any type of
- data using the same browse class. In addition, we would not have to
- repeatedly implement data-providing methods in in every class which
- requires a data source - we would just pass data-providing objects around.
-
- Event Handling
- --------------
-
- Another area which could stand improvement is the key handling in the
- 'exec' method's event loop. At present, the key mappings - which keys are
- handled and what they do - cannot be changed without overriding the method
- in a subclass. To allow more flexibility, we could add an instance
- variable to the class to contain an array of paired values - a key value
- and a code block. By setting this instance variable to a suitable array,
- the keyboard mapping can be changed at will by the call. This subject will
- be discussed in more detail in a future article.
-
- Field Editing
- -------------
-
- There is one last slightly more mundane, but useful, enhancement which
- could easily be made to our browsing class system, relating to editing of
- fields in the browse. The code for editing a field in TBDEMO and dBrowse
- is fairly complex - but what if we want a read-only browse? We could
- override or otherwise disable the editing behaviour, but the unused code
- would remain in the class. Thinking about it, a browsing class is not the
- right place to put field editing code, anyway.
-
- A more logical place to implement an 'edit' method is in the TBColumn
- class (or a subclass), since that is where the contents of individual cells
- is decided.
-
- So let's inherit two new classes from TBColumn - call them ROColumn and
- RWColumn - and implement an 'edit' method in each. ROColumn (read-only
- column) would have a dummy, or null, 'edit' method which would do nothing,
- or perhaps just generate a beep. RWColumn would have a fully functional
- 'edit' method, similar to the 'doGet' method currently in the dBrowse class
- (which would now become redundant). In the generic event loop in the
- GenBrowse class, we could specify that hitting the ENTER key, say, would
- cause an 'edit' message to be sent to the currently selected column object,
- which could be either an ROColumn or an RWColumn object. This would look
- something like the following:
-
- case nKey == K_ENTER
- columnObj:edit
-
- Editing would then take place, or not, depending on what type of column
- object had been inserted when the browse object was set up. No IF
- statement is necessary here. During setup, read-only and editable columns
- could easily be mixed in the same browse.
-
- Summary
- -------
-
- The system proposed here would provide a set of classes which would meet
- the requirement for a powerful, easy to use, and flexible browsing system.
- It would be easy to use because it would have default behaviours, requiring
- very little code to implement in a calling program. It would be flexible
- because other behaviour could be implemented by inheriting new classes from
- the existing ones. No functionality has been lost, because methods all the
- way up the tree to TBrowse can still be used as required. Having an
- object-based solution also makes it easy to implemement multiple
- simultaneously active browses - something much harder to do with a
- procedural approach (without simulating objects).
-
- The combination of all of the above capabilities is undeniably very
- powerful, and is a good example of the kind of benefits which can be
- achieved with object oriented programming.
-
-
-