OOP Challenge #3 -
Overriding Compiled Routines

Updated 1/12/2001

Originator: Patrick Schoenbach and Ralf Engels


This challenge is a small one, but it comes up fairly often in various forms, so I figure I will nip it now.

Patrick: "This raises another issue: Imagine you want to write an "external library" as you call it and you don't want to provide the customer with the source code. Since p/r [procedural] does not have subtyping, is it possible to extend the library with new or modified behavior without changing the source code? If so, how would you design it?"
Here is one approach (but not the only):
  sub processX(a,b,c)
     compiled_processX(a,b,c)
  end sub
Routine processX is made available to the customer, but not the source for compiled_processX. Thus, the customer can:

These cover everything that an OO method of a child class can do.

Note that "process_X" should be the routine called in the application and not "compiled_processX". (Naming conventions shown are for illustration only.)

I sometimes do something like this to provide my own output routines. For example, in Microsoft Web programming tools, the output method call is "response.write". Rather than use that, I define my own:

  sub hout(theText)
     response.write  theText
  end sub
  ...

  hout "Hello World!"
  hout "Tables our your friend!"

  // instead of

  response.write "Hello World!"
  response.write "Tables our your friend!"
This not only reduces the bloat caused by the OOP method syntax ("hout" is shorter than "response.write" and thus makes the code easier to read), but also allows me to do things like:
  sub hout(theText)
     response.write  theText
     LogIt  theText, timeNow()   // echo to a log
  end sub

Stubs

An OO proponent has complained that my approach requires the distribution of "stub" routines, such as the ProcessX() routine in the above example.

In most cases creating stubs would be left to the package user rather than be supplied with the package. Further, overriding in OOP also requires the creation of at least one new child class and at least one new method for every method to be overridden.

Further, in most cases the differences (overrides) are not going to shared globally, but instead applied differently in different situations. Thus, there would be no ProcessX() routine because the behavior would be added to (or ignored) differently per situation, often in-line rather than a separate routine.

The difference is that OO would probably try to find a taxonomy to the differences, which gets us right back to the taxonomy issues and/or method granularity issues.

Any Routine

The bottom line is that any subroutine, compiled or not, can be overridden or supplemented by a routine of your choosing. In worse-case scenarios you may have to re-name all of the calls to the routine being overridden, but this is highly language dependant because the scope rules of routine names varies widely among languages.

Another potential caveat is that the granularity (range) of what you want to change may be smaller than the routine you want to override. For example, you may want to override only half of the original behavior. However, OOP often suffers the same problem.


Game Example

Ralf Engels talked about a compiled role-playing game that allowed one to override specific actions and character features by sub-classing. Although I am not a game expert (and technically this is outside of the challenge scope), one possible way to allow procedural programmers to override defaults is to allow the game user to define a routine something like this:
  // Original "out-of-the-box" routine:
  sub overrides(subject, action, thing, target)
    doOrig = true   // default
    // insert your overrides here
    return doOrig
  end sub


  // Example *after* some typical custom behavior changed/added
  sub overrides(subject, action, thing, target)
    doOrig = true   // default
    // Sword grows larger if attacking a wizard
    if thing="sword" and action="attack" and target="wizard"
       scale(thing, 1.5)  
       doOrig = true
    end if
    //----------
    // replace sling rocks with magic dust
    if thing="slingRock" and action="obtain"
       thing = "magicDust"
       doOrig = true
    end if
    //----------
    // Spupify both characters if wand is
    // used, but stupify target a bit longer
    if thing="wand" and action="attack"
       disable_character(subject, 4)
       disable_character(target, 8)
       doOrig = false
    end if
    //----------
    ...etc...
    return doOrig
  end sub
This is a simplified example, but illustrates the general idea. All standard actions would call this routine. The "doOrig" flag allows one to optionally include (continue) the original behavior.
Another variation for managing "parent" behavior is to optionally call the original action, similar to the prior ProcessX example. This may allow "before" and "after" overrides. The "before" and "after" could instead be specified in one of the parameters, a "stage" parameter similar to handling a "pre" and "post" stage in data validation customization routines. One may want to have a separate override routine for each stage. However, this makes certain logical groupings harder to keep together in my experience. Anyhow, the possibilities and choices for building such protocols is large.
Note that this approach in some ways resembles rule-based Expert Systems (an artificial intelligence topic). See also Block Discrimination if you have complaints about lots of IF blocks. Why are lots of IF blocks more evil than lots of method blocks?

As a side note, role playing systems seem like they would be a great use of tables. It would be a lot easier to manage a huge cast of characters and actions via table(s) than in code. Often times the table can be used to manage "standard" features, and then a routine like the one above can be used in cases where the standard features don't provide enough specific control. I have used this approach for data dictionaries in form generation and validation systems and components.

The combination would also allow you to use sets to organize characters and actions rather than (or in addition to) hierarchies. For example, default wizards or warlocks could belong to both the "magic" group and to the "human" group. These features could be a checkbox (Boolean) in the character table, perhaps. Some overrides (or even standard operations) may do something to all characters with magical powers, and then later do something to all human characters. Later, a given warlock may be turned into a toad, but still have magical powers. OO sub-class hierarchies often have a tough time managing such orthogonal traits and action dispatching criteria. The above approach allows one to use AND's, OR's etc. to select a wider variety and combinations than hierarchies alone.

Just something extra to think about the next time you build a game.


Challenge Intro | OOP Criticism | P/R Patterns