OpenQM Magic!

TOC, TOC, TOC

The Theory of Constraints (TOC) has, as a main tenet, the idea that every system is limited by its constraints. TOC has a methodology for finding those constraints and overcoming their limitations. While I'm not sure that the developers of OpenQM have used the Theory of Constraints, I am sure that they have identified certain limits in the MultiValue programming model. The technology that has been introduced into OpenQM that has got me talking about TOC is objects.

But before I explain the potential of this marrying OO (Object Oriented) to MultiValue, there is one other issue I want to raise relevant to TOC: The classification of constraints. The most obvious constraints in any system are physical ones, but the most important and significant constraints are the non-physical limitations (policy constraints). Vendors and users of any data model, including SQL, have a major policy constraint: Resistance to change. I'm impressed that OpenQM has overcome this resistance by implementing important new features.

What's so Important About Objects Anyway?

One of the best theorists on this subject is Bertrand Meyers. To quote from his book "Object-Oriented Software Construction" he states that "the main problem (in software) is the data transmission structure of the system." He then continues "the background to object technology, as presented at the beginning of this book, is a battle between the function and data (object) aspects of software for control of the architecture. In non O-O approaches, the functions rule unopposed over the data; but then the data take their revenge."

I'm sure you have all seen applications with large commons, which usually exist because of the various and complex data transmissions between functions. The easiest way to manage this is to dump all the various variables into one large common. Then, unfortunately, developers do things like "borrow" a little-used (from their perception) common variable as a quick fix for an extra data transmission between functions. I'm sure you have all suffered from this side-effect when debugging a function and being frustrated by finding the data you didn't expect in a common variable.

So Back to the Question "Why Objects?"

Law of Inversion
If your routines exchange too many data, put your routines in your data.

Instead of building modules around operations and distributing the data structures between the resulting routines with all the unpleasant consequences that can occur, object-oriented design does the reverse. It uses the most important data-types as the basis for modularization, attaching each routine to the data-type to which it relates most closely. The law of inversion is the key to obtaining an object-oriented design from a classical functional (procedural) decomposition. Analyzing data transmissions is a good way to detect and correct design flaws.

Now For An Example

This example comes from Oracle for Objects. Yes, it's an SQL product, but Oracle has moved on from the traditional SQL implementation, and introduced changes to make their database conform more closely to E.F. Codd's original theoretical model. But before presenting the problem and the OpenQM solution, we need one more bit of background information to show why objects are important to the multi-valued model.

C.J. Date and Hugh Darwin in their book "Foundation for Object/Relational Databases, The Third Manifesto" present the argument why the SQL vendors have made a complete mess of implementing the relational model and what the model actually represents. A key tenet is that "Databases of the future will need to contain much more sophisticated kinds of data than current commercial ones typically do." The second tenet is that "a relation (the model terminology as distinct from the term table which is implementation terminology) has two parts, a heading and a body, where the heading is a set of column-name: domain-name pairs, and a body is a set of rows that conforms to the heading."

The crux of the issue is in how we define the domains. In SQL its simple, single-valued data types such as integer, varchar, date etc. In the MultiValue model it is numeric or text arrays representing the domain values. In Oracle Objects we can have the construct shown in Figure 1.

Print

Note that Date and Darwin state that a "domain is the equivalent to a class." And yes, the standard MultiValue model could handle this, something like Figure 2.

Office-ID <@ID>

Office_loc <1>

Occupant <2>

O001

Building_no

City

L1

Auckland

Idno

Name

Phone

P1

Bill

-1234

-9876

P2

Auckland

-654

-9977

A record structure such as

L1]Auckland^p1\Bill\1234|9876]P2\Jack\654|9977^

Updating the second phone number of the second person would require something like:

Read record from fvar_Office,"O001"then
Record<2,2,3,2> = {new value}
end

A practical solution, but certainly not intuitive. And what happens if person two is an occupant of more than one office, can we be sure that they both get updated - to name just one issue.

Before converting this into OpenQM, an explanation of the Oracle Object steps above is necessary:

  1. Create type is the definition of a Domain. This type definition is an array definition, the equivalent of stating DIM PHONE_LIST_ARR(10)
  2. This type definition is of a person Domain (or class), the equivalent would be a dictionary, something like :
    @ID = idno
    <1> = name
    <2> = phone(10)
  3. This type definition is for a location Domain (or class), the equivalent would be a dictionary, something like:
    @ID = building_no
    <1> = city
  4. This type definition is for a nested table (think of the intersection of a row and column, not just holding a single value, but a table of values). The nearest equivalent in the multi-value model would be equivalent to:
    <2> = nt_person_type(x,y) - a two dimensional array in a file attribute. Another equivalent would be rows = VM's, and columns in the row = SVM's.
  5. This type defines a domain (a class), the equivalent would be a dictionary, something like:
    @ID = office_id
    <1> = office_loc (of type location_typ)
    <2> = occupant(x,y) (of type person)
  6. This table is the domain type of office_typ

The OpenQM Solution

The Person class is defined as:<

The Person class is definition can be found at:

CLASS CLASS.PERSON.QM  
   *----------
   * class definition for PERSON data-type
   *----------   
   EQUATE E.NAME TO 1           ;* sv
   EQUATE E.PHONE TO 2          ;* sv
   EQUATE E.MEMBEROFF TO 3      ;* mv'd array of office ID's
   PRIVATE IDNO
   PRIVATE PERSON.RECORD(3)
   PRIVATE FVAR

   **********INITIALISE OBJECT ***********
   PUBLIC SUBROUTINE INIT(ID.NO) 
      * using specific constructor INIT, rather than the default routine "create.object"
      OPEN "PERSON.TYPE" TO FVAR ELSE ; ERR = "UNABLE TO OPEN PERSON.TYPE FILE" ; STOP ERR
      IDNO = ID.NO
      MATREAD PERSON.RECORD FROM FVAR,IDNO ELSE MAT PERSON.RECORD = ""
   END
   *************** NAME ******************
   PUBLIC SUBROUTINE SET.NAME(NAME)
      PERSON.RECORD(E.NAME) = NAME
      X = ME->SAVE
   END
   PUBLIC FUNCTION GET.NAME
      RTN = PERSON.RECORD(E.NAME)
      RETURN RTN
   END
   *************** PHONE *****************
   PUBLIC SUBROUTINE SET.PHONE(PHONE)
      PERSON.RECORD(E.PHONE) = PHONE
      X = ME->SAVE
   END
   PUBLIC FUNCTION GET.PHONE
      RTN = PERSON.RECORD(E.PHONE)
      RETURN RTN
   END
   *************** MEMBEROF **************
   PUBLIC SUBROUTINE SET.MEMBEROF(PARENT)
      MEMBER = PERSON.RECORD(E.MEMBEROFF)
      LOCATE(PARENT, MEMBER,1 ; I ; "AR") ELSE
         INS PARENT BEFORE MEMBER<1>
      END
      IF MEMBER <> PERSON.RECORD(E.MEMBEROFF) THEN PERSON.RECORD(E.MEMBEROF) = MEMBER
      X = ME->SAVE
   END
   PUBLIC FUNCTION GET.MEMBEROF
      RTN = PERSON.RECORD(E.MEMBEROFF) ; RTN = RAISE(RTN)
      RETURN RTN
   END   

   ********************* SAVE ***********
   PUBLIC FUNCTION SAVE
      IF UNASSIGNED(FVAR) THEN
         OPEN "PERSON.TYPE" TO FVAR ELSE ; ERR = "UNABLE TO OPEN PERSON.TYPE FILE" ; STOP ERR
      END
      ERROR.FLG = 0 ; ERROR.STR = ""
      MATWRITE PERSON.RECORD TO FVAR,IDNO ON ERROR 
         ERROR.FLG = 1 ; ERROR.STR := @FM:"Error " : STATUS() : " writing ":IDNO
      END
      RETURN ERROR.FLG:ERROR.STR
   END
END

The location class is defined as:

CLASS CLASS.LOCATION.QM  
   *----------
   * class definition for LOCATION data-type
   *----------   
   EQUATE E.CITY TO 1           ;* sv
   EQUATE E.MEMBEROF TO 2       ;* mv'd array
   PRIVATE IDNO
   PRIVATE LOCATION.RECORD(2)
   PRIVATE FVAR

   **********INITIALISE OBJECT ***********
   PUBLIC SUBROUTINE INIT(ID.NO)
      * using specific constructor INIT, rather than the default routine "create.object"
      OPEN "LOCATION.TYPE" TO FVAR ELSE ; ERR = "UNABLE TO OPEN LOCATION.TYPE FILE" ; STOP ERR
      IDNO = ID.NO
      MATREAD LOCATION.RECORD FROM FVAR,IDNO ELSE MAT LOCATION.RECORD = ""
   END
   *************** CITY ******************
   PUBLIC SUBROUTINE SET.CITY(CITYNAME)
      LOCATION.RECORD(E.CITY) = CITYNAME
      X = ME->SAVE
   END
   PUBLIC FUNCTION GET.CITY
      RTN = LOCATION.RECORD(E.CITY)
      RETURN RTN
   END
   *************** MEMBEROF **************
   PUBLIC SUBROUTINE SET.MEMBEROF(PARENT)
      MEMBER = LOCATION.RECORD(E.MEMBEROF)
      LOCATE(PARENT, MEMBER,1 ; I ; "AR") ELSE
         INS PARENT BEFORE MEMBER<1,I>
      END
      IF MEMBER <> LOCATION.RECORD(E.MEMBEROF) THEN LOCATION.RECORD(E.MEMBEROF) = MEMBER
      X = ME->SAVE
   END
   PUBLIC FUNCTION GET.MEMBEROF
      RTN = LOCATION.RECORD(E.MEMBEROF) ; RTN = RAISE(RTN)
      RETURN RTN
   END   

   ********************* SAVE ***********
   PUBLIC FUNCTION SAVE
      IF UNASSIGNED(FVAR) THEN
         OPEN "LOCATION.TYPE" TO FVAR ELSE ; ERR = "UNABLE TO OPEN LOCATION.TYPE FILE" ; STOP ERR
      END
      ERROR.FLG = 0 ; ERROR.STR = ""
      MATWRITE LOCATION.RECORD TO FVAR,IDNO ON ERROR 
         ERROR.FLG = 1 ; ERROR.STR := @FM:"Error " : STATUS() : " writing ":IDNO
      END
      RETURN ERROR.FLG:ERROR.STR
   END
END

You'll notice that both these classes have data persistence by having their own files to store the object contents.

These classes satisfy the Law of Inversion in that these file I/O routines are attached to the data-type to which it relates most closely. This is a major change from having one monolithic program carrying out a multitude of file I/O for a range of files.

Both classes have a INIT routine to read and initialise the object contents. Both classes also have a X = ME->SAVE call to the SAVE routine whenever any internal value changes.

The office class is defined as:

Office Class

Class Office defines the domain Office_Typ.

In the property definition of the class are two private object pointers, the first for a single location object and the second a dimensioned array for the multiple people objects. The structure of the People Array is

Person ID 1 Person 1 Object Pointer
Person ID 2 Person 2 Object Pointer

  1. The private location object pointer in the Office class holds the reference to the created Location Object.
  2. OpenQM allows a dimension array to be re-dimensioned on the fly, so it can grow to accommodate the correct number of child person object pointers.
  3. If the office record does not hold a location, then create a location object using the Location object pointer, record its details in the Office object and ask the office object to save itself because its content has changes. Note: The routine does NOT specifically force the Location object to be saved. That is implicit within the Location object, it is its responsibility to save itself if its content changes.
  4. To change a property within the Location object, the Office object just sends a message to it {X = LOCATION.OBJ->SET.MEMBEROF(IDNO)}. It is up to the Location object to manage its own internal state, which it does.

Finally, Let's Put This All Together

In OpenQM, the syntax to build a file using the Office type is not yet possible, but if this was controlled by a program, then the structure would be something like Figure 3.

   OPEN "OFFICE.TYPE" TO FVAR ELSE
      ERR = "UNABLE TO OPEN OFFICE.TYPE FILE"
      STOP ERR
   END      
   *
   DIM OFFICES(1,2) 
   MAT OFFICES = ""
   * - select the OFFICE.TYPE file and load up column 1 of OFFICES(row,col)
   HUSH ON
   EXECUTE "SELECT OFFICE.TYPE BY @ID" RTNLIST LISTVAR
   HUSH OFF
   MAX.KT = 0
   LOOP
      READNEXT ID FROM LISTVAR ELSE ID = ""
   WHILE ID DO
      MAX.KT += 1
      * dynamically re-dimension dimension array to hold object pointers
      DIM OFFICES(MAX.KT,2) 
      OFFICES(MAX.KT,1) = ID
   REPEAT

We now have a collection container to hold all the office type objects (this is the equivalent of the Oracle Objects statement.

6. CREATE TABLE office_tab OF office_typ (
office_id PRIMARY KEY );

Now perhaps the user wishes to update a specific Office record, something like the following as the example in Figure 4.

   DISPLAY "enter office number ": ; INPUT OFFICE.ID
   IF OFFICE.ID = "" THEN STOP
   VA = "" ; FOR I = 1 TO MAX.KT ; VA<I> = OFFICES(I,1) ; NEXT I
   LOCATE(OFFICE.ID, VA; KT ; "AR") THEN
      * check whether object created
      IF OFFICES(KT,2) = "" THEN
         OFFICES(KT,2) = OBJECT("CLASS.OFFICE.QM")
         OFFICES(KT,2)->INIT(OFFICE.ID)
      END
      {… add logic here …}
   END

How do we add logic once the office object is created?

To read the Office's Location's City:

D = OFFICES(KT,2)->GET.LOCATION.CITY

To update the Office's Location's City:

OFFICES(KT,2)->SET.LOCATION.CITY("PAPATOETOE")

Obviously there is much more that can be done than what I have written about here. Let me summarize the key points covered.

  • Overcome your own policy constraints and investigate changes which can benefit you.
  • When a design or an existing application is becoming too complicated, use the Law of Inversion and analyze the data transmissions between functions.
  • Where possible, isolate the most important data types and by using the object features of OpenQM, attach the appropriate routines to the data type.
  • A column's domain does not have to be a simple, single value as per the SQL model, or an un-described array as in the traditional MultiValue model.
  • Complex domains can de-defined as classes, where the class has the responsibility to manage its own affairs.
  • Using classes to define domains overcomes the problem of poor clarity. What does record<2,2,3,2> mean?
  • When using classes, make them responsible for their own actions. Goggle "Design by Contract" (a Bertrand Meyer concept) which formalizes the contract between a class and its clients.
  • You can simplify the use of class domains by using a construct such as:
    Class Simple
    Public WorkArray1(100,100)
    Public WorkArray2(50,50)
    Public Var1, Var2, Var3
    End
    And the use of such a simple class would be:
    Obj = create.object("Simple")
    Obj->WorkArray(3,3) = value1
    X = Obj->Var3
    Obj = "" ;* destroy object pointer when finished
  • And of course, download OpenQM and take the object extension for a ride and enjoy.
Graeme McGillivray

View more articles
menu
menu