![]() |
The NetRexx Tutorial
![]() |
In this chapter we'll look at some "details" we intentionally left uncovered in the previous discussion.
Pattern Design is used to sketch a solution to some particular Object Oriented problem. It has probably already happened to you (as it did to me) to think: "I've already solved this problem (or a similar one) in the past." Then you rush to your code and try to find the solution again. If I'm allowed to make such comparison, then, "Design Patterns" stand to "Object Oriented Programming" as "Algorithms" stand to "Procedural Programming". Even further, Gamma, Helm, Johnson and Vlissides text stands to "Design Patterns" as Knuth's stands to "Algorithms".
The key issue is to make your software reusable. Using Design Patterns, you not only make it such, but you also reuse other's people efforts to find the right solution.
Let us consider a class hierarchy for a simple problem: we consider the "universe" of 2D rectangular objects, where we'll find Rectangles and Squares. A Venn diagram representing our "universe" might be useful:
+---------------------------------------------+ U | | N | RECTANGLE | I | | V | +--------------+ | E | | | | R | | SQUARE | | S | | | | E | +--------------+ | +---------------------------------------------+ Venn diagram of the "universe" class RECTANGLE with a subclass (SQUARE)
Recalling what we saw in the previous section, we can try to implement the above diagram using NetRexx. The first thing I'd think of is to make a Rectangle class, and have Square defined as a subclass of Rectangle.
Let's make an Object Model for this diagram:
+--------------+ | | | RECTANGLE | | |<-----( RE1 ) +--------------+ | * * * ***** | +--------------+ | | | SQUARE | | |<-----( SQ1 ) +--------------+
Note that, in our diagram:
So, from our picture we can say phrases like: the class "SQUARE" "is-a-subclass-of" the class "RECTANGLE", or the object "SQ1" is an instance of the class "SQUARE".
The actual implementation is trivial: so just look at the code.
+----------------------------------------------------------------------+ | -- abex1.nrx |01 | -- Implements Rectangles and Squares |02 | -- |03 | class abex1 public |04 | properties public |05 | |06 | method main(args=String[]) public static |07 | args = args |08 | |09 | RE1 = _Rectangle(1,2) |10 | say RE1.area() |11 | |12 | SQ1 = _Square(2) |13 | say SQ1.area() |14 | |15 | exit 0 |16 | |17 | class _Rectangle |18 | properties public |19 | length |20 | width |21 | method _Rectangle(l=Rexx,w=Rexx) public |22 | length = l |23 | width = w |24 | method area public |25 | return this.length*this.width |26 | method set_width(w=Rexx) public |27 | this.width = w |28 | method set_length(l=Rexx) public |29 | this.length = l |30 | method perimeter public |31 | return 2*(this.length+this.width) |32 | |33 | class _Square extends _Rectangle |34 | method _Square(s=Rexx) public |35 | super(s,s) |36 | method area public |37 | return this.length*this.length |38 | method perimeter public |39 | return 4*this.length |40 +----------------------------------------------------------------------+ abex1.nrx | ![]() |
There is a series of problems with the above implementation; I analyse them in order of increasing importance.
SQ1 = _Square(3) SQ1.setlength(4)which is, in my opinion, REALLY a bad thing: we allow people to make squares with different sides.
To correctly represent the Venn Diagram, we MUST use three classes. The universe class will be an "abstract" class, that we can call 2DSHAPE.
Let's revise our Object Model:
+--------------+ | | | 2DSHAPE | | | +--------------+ | * * * +--------*****-----------------+ | | | | +--------------+ +--------------+ | | | | | SQUARE | | RECTANGLE | | |<--( SQ1 ) | |<--( RE1 ) +--------------+ +--------------+
In order to create an abstract class (i.e. a class that contains at least an abstract method), we use the keyword abstract (note that in C++ the keyword virtual is used).
That's how you'd implement in NetRexx:
+----------------------------------------------------------------------+ | -- abex2.nrx |01 | -- abstract class example |02 | -- |03 | class abex2 public |04 | |05 | method main(args=String[]) public static |06 | args = args |07 | R1 = _Rectangle(2,3) |08 | say R1.area() |09 | S1 = _Square(3) |10 | say S1.area() |11 | say 'You defined' _2Dshape.nobjects 'shapes.' |12 | exit 0 |13 | |14 | class _2Dshape abstract |15 | properties public static |16 | nobjects = 0 |17 | method _2dShape() public |18 | nobjects = nobjects+1 |19 | method area public returns Rexx abstract |20 | method perimeter public returns Rexx abstract |21 | |22 | class _Rectangle extends _2Dshape |23 | properties private |24 | length |25 | width |26 | method _Rectangle(l=Rexx,w=Rexx) public |27 | super() |28 | length = l |29 | width = w |30 | method area public |31 | return length*width |32 | method perimeter public |33 | return 2*length*width |34 | |35 | class _Square extends _2Dshape |36 | properties private |37 | side |38 | method _Square(s=Rexx) public |39 | super() |40 | side = s |41 | method area public |42 | return side*side |43 | method perimeter public |44 | return 4*side |45 | |46 +----------------------------------------------------------------------+ abex2.nrx | ![]() |
*** This section is:*** and will be available in next releases
The interface part will look as follows:
+----------------------------------------------------------------------+ | -- runnable.nrx |01 | -- |02 | class runnable interface |03 | method run() public |04 +----------------------------------------------------------------------+ runnable.nrx | ![]() |
+----------------------------------------------------------------------+ | -- dyna2.nrx |01 | -- |02 | class dyna2 public |03 | |04 | method main(args=String[]) public static |05 | arg = Rexx(args) |06 | do |07 | r = runnable; |08 | un = Class.forName(arg); |09 | r = runnable un.newInstance() |10 | r.run() |11 | catch e= Exception |12 | say e |13 | end |14 | exit 0 |15 | |16 | class test1 implements runnable |17 | method run public |18 | say 'Hello from class TEST1' |19 | |20 | class test2 implements runnable |21 | method run public |22 | say 'Hello from class TEST2' |23 | |24 +----------------------------------------------------------------------+ dyna2.nrx | ![]() |
+----------------------------------------------------------------------+ | -- dyna3.nrx |01 | -- |02 | class dyna3 public |03 | |04 | method main(args=String[]) public static |05 | arg = Rexx(args) |06 | loop forever |07 | say 'Enter Class name (A,B,C) or quit' |08 | parse ask.upper() name |09 | if name = 'QUIT' then leave |10 | do |11 | r = runnable; |12 | un = Class.forName(name); |13 | r = runnable un.newInstance() |14 | r.run() |15 | catch e= Exception |16 | say e |17 | end |18 | say 'There are' A.n 'instances for A.' |19 | say 'There are' B.n 'instances for B.' |20 | say 'There are' C.n 'instances for C.' |21 | end |22 | say 'End.' |23 | exit 0 |24 | |25 | -- class A |26 | -- |27 | class A implements runnable |28 | properties static |29 | n = 0 |30 | method A public |31 | n = n+1 |32 | method run public |33 | say 'Hello from class A' |34 | |35 | -- class B |36 | -- |37 | class B implements runnable |38 | properties static |39 | n = 0 |40 | method B public |41 | n = n+1 |42 | method run public |43 | say 'Hello from class B' |44 | |45 | -- class C |46 | -- |47 | class C implements runnable |48 | properties static |49 | n = 0 |50 | method C public |51 | n = n+1 |52 | method run public |53 | say 'Hello from class C' |54 +----------------------------------------------------------------------+ dyna3.nrx | ![]() |
This is what we get running dyna3:
.............................................................. Enter Class name (A,B,C) or quit A Hello from class A There are 1 instances for A. There are 0 instances for B. There are 0 instances for C. Enter Class name (A,B,C) or quit A Hello from class A There are 2 instances for A. There are 0 instances for B. There are 0 instances for C. Enter Class name (A,B,C) or quit A Hello from class A There are 3 instances for A. There are 0 instances for B. There are 0 instances for C. Enter Class name (A,B,C) or quit B Hello from class B There are 3 instances for A. There are 1 instances for B. There are 0 instances for C. Enter Class name (A,B,C) or quit C Hello from class C There are 3 instances for A. There are 1 instances for B. There are 1 instances for C. Enter Class name (A,B,C) or quit B Hello from class B There are 3 instances for A. There are 2 instances for B. There are 1 instances for C. Enter Class name (A,B,C) or quit quit End. .............................................................. |
*** This section is:*** and will be available in next releases
The idea of Singleton is simple: we want to make sure that a class has ONLY one instance, and we want to provide a global point of access to it.
The structure is (GAMMA, 96, p. 127)
+----------------------------+ | Singleton | +----------------------------+ +----------------------\ | static Instance() *-------------> | return uniqueInstance| | (...) | +----------------------+ | SingletonOperation() | | GetSingletonData() | | | | | | | +----------------------------+ | static UniqueInstance | | (...) | | singletonData | | | | | +----------------------------+ |
The NetRexx implementation of the Singleton Pattern might look like:
+----------------------------------------------------------------------+ | -- Singleton.nrx |01 | -- NetRexx Implementation of Singleton |02 | -- see GAMMA, 1996, p.127 |03 | -- |04 | class Singleton public |05 | |06 | properties private static |07 | _instance = Singleton NULL |08 | |09 | method Singleton() private |10 | |11 | method Instance() returns Singleton public static |12 | if _instance = NULL then |13 | do |14 | _instance = Singleton() |15 | return _instance |16 | end |17 | return _instance |18 +----------------------------------------------------------------------+ Singleton.nrx | ![]() |
Let's look at it closely. The first "uncommon" feature we find is:
method Singleton() private
i.e. the constructor is declared as private. Clients will not be capable to access it with a normal:
s = Singleton()
Instead, they're forced to use the Instance() member function, declared as static.
This means that the clients will need to write:
s = Singleton.Instance()
in order to get the unique Singleton's instance.
*** This section is:*** and will be available in next releases
It is sometimes interesting to record the actions that an user enters when dealing with an interactive program. This is, for example, the case of the history command in an UNIX shell.
When I dealt for the first time with an implementation of an history command, my solution was to define a history buffer (with his length):
properties public static cmdbuf = Rexx(") cmdbufl = 20
and 2 methods to save/dump the history:
+----------------------------------------------------------------------+ | -- method......: historyd |44 | -- purpose.....: display the history |45 | -- |46 | method historyd(cur=Rexx) public static |47 | if cur < cmdbufl |48 | then st = 1 |49 | else st = cur-cmdbufl |50 | loop i = st to cur-1 |51 | say i.right(5) cmdbuf[i] |52 | end |53 | |54 +----------------------------------------------------------------------+ xshell1.nrx(Method:historyd) | ![]() |
+----------------------------------------------------------------------+ | -- method......: history |55 | -- purpose.....: history |56 | -- |57 | method history(a=Rexx,n=Rexx) public static |58 | if a <> '' then |59 | do |60 | cmdbufl = a |61 | end |62 | else |63 | do |64 | historyd(n) |65 | end |66 | |67 +----------------------------------------------------------------------+ xshell1.nrx(Method:history) | ![]() |
In the main loop, I was calling saving the entered command in the buffer
cmdbuf[cmdno] = todo cmdno = cmdno+1
The commands are saved in the history buffer inside a circular buffer
+----------------------------------------------------------------------+ | -- method......: save |66 | -- purpose.....: |67 | -- |68 | method save(entry=Rexx) public |69 | k = lastrec // maxrec |70 | if record[k] <> NULL then |71 | do |72 | if entry = record[k] |73 | then return |74 | end |75 | lastrec = lastrec+1 |76 | k = lastrec // maxrec |77 | record[k] = entry |78 | |79 +----------------------------------------------------------------------+ history.nrx(Method:save) | ![]() |
+----------------------------------------------------------------------+ | -- method......: dump |45 | -- purpose.....: |46 | -- |47 | method dump(n=Rexx) public |48 | first = lastrec - n + 1 |49 | loop i=first to lastrec |50 | k = i// maxrec |51 | if record[k] = NULL then iterate |52 | if record[k] = '' then iterate |53 | say i.right(5) record[k] |54 | end |55 | |56 +----------------------------------------------------------------------+ history.nrx(Method:dump) | ![]() |
+----------------------------------------------------------------------+ | -- method......: retrieve |57 | -- purpose.....: |58 | -- |59 | method retrieve(n=Rexx) public returns Rexx |60 | if n < lastrec - maxrec then return " |61 | if n > lastrec then return " |62 | k = n// maxrec |63 | return record[k] |64 | |65 +----------------------------------------------------------------------+ history.nrx(Method:retrieve) | ![]() |
his = history(100) loop -- get user input his.save(USER_INPUT) end
You can find additional information about patterns at:
http://st-www.cs.uiuc.edu/users/patterns/
with some tutorial information at:
http://www.enteract.com/~bradapp/docs/patterns-intro.html http://www.csc.calpoly.edu/~dbutler/tutorials/winter96/patterns/
*** This section is:*** and will be available in next releases