1. Summary
  2. Files
  3. Support
  4. Report Spam
  5. Create account
  6. Log in

Starting OO design

From oorexx

Jump to: navigation, search

Contents

Main_Page contents Week 2 Week 3


Designing an OO application

This is a simple tutorial on starting an OO design. It doesn't deal with formal design methods, it just shows how I would go about designing a simple report class (object design) and then using it to print the two times multiplication table.

If it gets a good response in the rexxLa mailing list I will follow it with Articles refining and expanding the example. Feel free to join in, whether you have questions or you know better than me how to do this.

Please note that the code examples require ooRexx 3.2 (the latest as I write) or above.

Jon


What does a report consist of?

  • A title
  • A header
  • Rows
  • A footer
  • A suffix perhaps

A class is a factory for creating objects that defines what the object looks like. We could start to create a report class:

::class report

::attribute title
::attribute header
::attribute rows
::attribute footer
::attribute suffix

This gives us a start at containing our data. All of our attributes are string objects, except for the rows, which are going to be a collection of some objects, … lets use an array to contain the rows. As such we need to set that up when the object instance is created.
When you create an object instance the init method is run.

::method init
self~rows= .array~new

Because we have declared rows as an attribute of our class, we can set values to it like this. We can also get (query) it’s value from any method within our class like this:

Say self~rows

We can also set or get it’s value from code outside the class like this:

MyReport = .report~new      /* make a report instance */
Say MyReport~rows

This would make more sense if rows were a string object. As it is an array object, the string value of the array class will be returned, ie “an Array”
Now we can ask our self what our rows are going to look like. I guess that they are going to consist of a number of fields. These fields are also going to affect the header. So I am going to add a fields attribute to hold the fields definitions and make that an array as well.

 ::class report

 ::attribute Title
 ::attribute header
 ::attribute rows
 ::attribute footer
 ::attribute suffix
 ::attribute fields

 ::method init
 self~rows   = .array~new
 self~fields = .array~new

as you can see, I have separated the ‘=’ from the attribute messages in the init method so that they line up nicely. OoRexx allows this.


Each of our fields is going to be an object in itself. What characteristics will they have?

  • A title (for the header row)
  • A width
  • An alignment (left, right, center)
  • A data type (string, integer, decimal)
  • A precision (if they are decimal numbers)
  • A default value (hunch tells me this will come in handy)

So this sounds like we need another class to cope with this:

::class reportFieldDefinition
::attribute title
::attribute width
::attribute alignment
::attribute type
::attribute precision
::attribute defaultValue

As the only actions we are going to do with object instances of this class are define them and query the attributes it should be fairly straightforward. To save us code setting the attributes individually we can do that in the init method from parameters passed:

::method init
use arg title, width, alignment, type, precision, defaultvalue
self~title        = title
self~width        = width
self~alignment    = alignment
self~type         = type
self~precision    = precision
self~defaultValue = defaultValue

Let’s say we were going to create a report to show the two times table, we would have the following fields

  1. Multiplier
  2. ‘*’
  3. Multiplicand
  4. ‘=’
  5. Result

And we might set it up as follows:

MyReport        = .report~new
                                  /* set up the field definitions */
f = .reportFieldDefinition~new('Multiplier',10,'right','integer',0)
MyReport~fields~append(f)
f = .reportFieldDefinition~new("",1,'left',string,,'*')
MyReport~fields~append(f)
f = .reportFieldDefinition~new('Multiplicand',12,'right','integer',0)
MyReport~fields~append(f)
f = .reportFieldDefinition~new("",1,'left',string,,'=')
MyReport~fields~append(f)
f = .reportFieldDefinition~new('Result',7,'right','integer',0)
MyReport~fields~append(f)

As MyReport~fields is an array it provides the append method so I don’t need to write that myself – just use it.
In the above example I’ve assigned the reportFieldDefinition objects to variables and then appended them, so that the lines don’t wrap. You can of course do it all in one statement.

Now we have to attend to the actual data in the report. We have defined an array to contain the rows, but what will a row look like?

  • Fields

Now lets create a class for a row:

::class reportRow
::attribute fields
::method init
self~fields = .array~new

Fields is an array of string objects.
Now we could add the data for the row 2 * 3 = 6 like this:

R = .reportRow~new
R~fields[1] = 2
/* field 2 has the default value of ‘*’ */
R~fields[3] = 3
/* field 4 has the default value of ‘=’ */
R~fields[5] = 6
MyReport~Rows~append(r)

Lets see where we are at now. We have a way to define our report and a way to get data in. All that is missing is a way to get the data out.

Outputting the report

This is an action, so it is going to be a method of the Report Object. You might want to direct this to a file or a printer, but we are just going to use say as our output method.

::method Output

say self~title

hString = ""                              /* construct the header string */
do field over self~fields
   parse upper value field~alignment~strip with align 2 .
   select
     when align = 'L' then hstring = hstring field~title~left(field~width)
     when align = 'R' then hstring = hstring field~title~right(field~width)
     otherwise
         hstring = hstring field~title~center(field~width)
   end /* select */
end
self~header = hstring~subStr(2)       /* remove extraneous leading blank */

say self~header

do row over self~rows
   rowStr = ''                                /* contruct the row string */
   do fieldNo = 1 to row~fields~last          /* for all the fields      */
                     /* get the field value or the defaul if not defined */
      if row~fields[fieldNo] = .nil
      then fieldText = self~fields[fieldNo]~defaultValue
      else fieldText = row~fields[fieldNo]

      if self~fields[fieldNo]~type~left(1)~translate = 'D'    /* decimal */
      then fieldText = fieldText~format(,self~fields[fieldNo]~precision)

      parse upper value self~fields[fieldNo]~alignment~strip with align 2 .
      fieldWidth = self~fields[fieldNo]~width
      select
         when align = 'L' then rowStr = rowStr fieldText~left(fieldWidth)
         when align = 'R' then rowStr = rowStr fieldText~right(fieldWidth)
         otherwise
            rowStr = rowStr fieldText~center(fieldWidth)
      end /* select */
   end /* DO */
   say rowStr~substr(2)               /* remove extraneous leading blank */
end /* DO */

say self~footer
say self~suffix

Purists would say quite correctly that there is too much code in this method and it should be broken down into smaller methods or routines, which call each other, and I agree, but I leave it as it is for demonstrations sake.

Using our classes

We have now written our classes & it only remains to use them:
Here, tidied up a bit is all the code for our two times table report:

MyReport        = .report~new
MyReport~title  = 'Two times Table'
MyReport~footer = 'Report produced on' date()
MyReport~suffix = ''
                                  /* set up the field definitions */
MyReport~fields~append(.reportFieldDefinition~new('Multiplier',10,'right','integer',0))
MyReport~fields~append(.reportFieldDefinition~new("",1,'left',string,,'*'))
MyReport~fields~append(.reportFieldDefinition~new('Multiplicand',12,'right','integer',0))
MyReport~fields~append(.reportFieldDefinition~new("",1,'left',string,,'='))
MyReport~fields~append(.reportFieldDefinition~new('Result',7,'right','integer',0))

do i = 1 to 10
   r = .reportRow~new
   r~fields[1] = 2
   r~fields[3] = i
   r~fields[5] = 2*i
   MyReport~Rows~append(r)
end

MyReport~outPut

/* ===================================================================== */
::class report
/* ===================================================================== */
::attribute title
::attribute header
::attribute rows
::attribute footer
::attribute suffix
::attribute fields
/* --------------------------------------------------------------------- */
::method init
/* --------------------------------------------------------------------- */
self~rows   = .array~new
self~fields = .array~new
/* --------------------------------------------------------------------- */
::method Output
/* --------------------------------------------------------------------- */
say self~title

hString = ""                              /* construct the header string */
do field over self~fields
   parse upper value field~alignment~strip with align 2 .
   select
     when align = 'L' then hstring = hstring field~title~left(field~width)
     when align = 'R' then hstring = hstring field~title~right(field~width)
     otherwise
         hstring = hstring field~title~center(field~width)
   end /* select */
end
self~header = hstring~subStr(2)       /* remove extraneous leading blank */

say self~header

do row over self~rows
   rowStr = ''                                /* contruct the row string */
   do fieldNo = 1 to row~fields~last          /* for all the fields      */
                     /* get the field value or the defaul if not defined */
      if row~fields[fieldNo] = .nil
      then fieldText = self~fields[fieldNo]~defaultValue
      else fieldText = row~fields[fieldNo]

      if self~fields[fieldNo]~type~left(1)~translate = 'D'    /* decimal */
      then fieldText = fieldText~format(,self~fields[fieldNo]~precision)

      parse upper value self~fields[fieldNo]~alignment~strip with align 2 .
      fieldWidth = self~fields[fieldNo]~width
      select
         when align = 'L' then rowStr = rowStr fieldText~left(fieldWidth)
         when align = 'L' then rowStr = rowStr fieldText~right(fieldWidth)
         otherwise
            rowStr = rowStr fieldText~center(fieldWidth)
      end /* select */
   end /* DO */
   say rowStr~substr(2)               /* remove extraneous leading blank */
end /* DO */

say self~footer
say self~suffix

/* ===================================================================== */
::class reportFieldDefinition
/* ===================================================================== */
::attribute title
::attribute width
::attribute alignment
::attribute type
::attribute precision
::attribute defaultValue
/* --------------------------------------------------------------------- */
::method init
/* --------------------------------------------------------------------- */
use arg title, width, alignment, type, precision, defaultvalue
self~title        = title
self~width        = width
self~alignment    = alignment
self~type         = type
self~precision    = precision
self~defaultValue = defaultValue

/* ===================================================================== */
::class reportRow
/* ===================================================================== */
::attribute fields
/* --------------------------------------------------------------------- */
::method init
/* --------------------------------------------------------------------- */
self~fields = .array~new


And here is what the output looks like

Two times Table
Multiplier   Multiplicand    Result
    2      *      1       =    2
    2      *      2       =    4
    2      *      3       =    6
    2      *      4       =    8
    2      *      5       =   10
    2      *      6       =   12
    2      *      7       =   14
    2      *      8       =   16
    2      *      9       =   18
    2      *      10      =   20
Report produced on 6 Jan 2008

What next?

Next time we'll look at strong interfaces, documentation and storing your classes in a library
Week 2

Personal tools