Incr Tcl Design Patterns

Design patterns describe problems and solutions that occur over and over again in software design and development. Several people have developed Pattern Catalogs which describe the essential characteristics of both the problems and solutions. One famous catalog is the book Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, and Vlissides. Online resources include the Patterns Home Page. [L1 ]

In general, the essential elements of a pattern are

  • Pattern name
  • Problem description
  • Solution elements
  • Consequences or trade-offs

But most pattern catalogs also include code snippets to illustrate how you would implement a particular solution. The code can't really be used in a library, but it can serve as an example for other developers. Most of the code snippets are for C++, Java, or Smalltalk. (Most of the patterns are for Object Oriented analysis and design.)

Here is a place to list [incr Tcl] code snippets implementing typical OO concepts. The code for a Factory, for example, might not be obvious because Incr Tcl doesn't naturally support abstract classes.

RWT -- January 31, 2001


Member Objects

A common question is how to implement member objects, or the has-a relationship in object modeling terms. The answer is to use a member variable to hold a reference to the object. Create it in the constructor and delete it in the destructor. For example, objects of type B contain an object of type A.

  itcl::class A {
      method print {} {puts "Hello from A.  My name is $this."}
  }

  itcl::class B {
      variable a
      constructor {} {set a [A #auto]}
      destructor {itcl::delete object $a}
      method b {} {$a print}
  }

Note that the member object is actually created in the B namespace, so it won't conflict with global object declarations.


Composition Objects

Although creating a member object is straight forward, referencing a Member objects members (varibles, methods, etc) may prove more difficult in Incr Tcl. There are many disscusions as to if inheritance is obsolete or otherwise an appropriate model for most development. Alternatives have exhibited a composite class only model, avoiding inheritance.

This example was wiki edited by Lee Atkinson.

For mixed design using Incr Tcl, one technique to allow composition of objects is illustrated here:

  class user {
      public variable username
  }
  class session {
      variable thisuser 
      variable thatuser
      constructor {} {
          set thisuser [user thisuser]
          set thatuser [user thatuser]
      }
      method osend {item cmd} {
          set obs [itcl::find objects]
        set item [string trimleft $item "-"]
          set pos [lsearch -exact $obs $item]
          if {$pos>=0} {
              return [eval [lindex $obs $pos] $cmd]
          }
      }                
  }

  # ---test it---

  % set fd [open "test.out" w+]
  % session asession
  % asession osend -thisuser {configure -username Fred}
  % asession osend -thatuser {configure -username Barney}
  % puts $fd [asession osend -thisuser {cget -username}]
  % puts $fd [asession osend -thatuser {cget -username}]
  % close $fd

In the example, some other class or module may need the user class held by a session instance. Notice that "thisuser" or "thatuser" need not be made public. If security of session users are an issue, insertion of a filter using any conditional might allow public access to "thisuser" but not "thatuser". The "osend" is for "object send", using the idea of sending the contained object a command message.


Abstract Factory

The Abstract Factory is described by Gamma et. al.. The Incr Tcl implementation illustrates both abstract classes and pure virtual methods. An abstract class cannot be instantiated - only subclass objects may actually be created. A pure virtual method is undefined in the parent class, but must be defined in the subclass.

This example was posted to the Incr Tcl mailing list by Chad Smith. (Thanks Chad!)

    itcl::class Factory {
        constructor {} {
            # Keep this class from being instantiated.
            if {[namespace tail [info class]] == "Factory"} {
                error "Error: can't create Factory objects - abstract class."
            }

            # Verify that the derived class has implemented the
            # "status" method.  This simulates pure virtual methods
            # in itcl (though at run-time only).
            if {[$this info function status -body] == ""} {
                error "Error: method 'status' undefined."
            }
        }
        protected method status {} {}
    }

    % Factory #auto
    Error: can't create Factory objects - abstract class.
    %
    % itcl::class WidgetFactory {inherit Factory}
    % WidgetFactory #auto
    Error: method 'status' undefined.
    % itcl::class MyFactory {
        inherit Factory
        method status {} {puts MyFactory::status}
    }
    % MyFactory #auto
    myFactory0

Factory Method

The Factory Method is described by Gamma et. al.. The Incr Tcl implementation illustrates creation of an object in the parent namespace. Gamma describes other interesting applications of Factory Method, such as delegating object creation to subclasses. But this example just focuses on creating and returning the object.

In some primitive languages like C++ and Java (smile) the pointer or handle namespace is flat. All pointers are considered equal. But in Incr Tcl, each class and object exist in a namespace. Normally, an object will create, manipulate, and destroy "working" objects in the course of executing its methods. These working objects exist within the namespace of the creating object, so they don't interfere with anybody else.

In the case of a Factory Method, however, you really want to create the new object in the namespace in which the factory is called. That might usually be the global namespace, but it could be elsewhere. Note that in this Factory Method, we use the uplevel command to create the object in the calling namespace. We also want to return a fully namespace qualified name, so we use namespace which to get an absolute object name which can be passed to any namespace.

    itcl::class Product {
        public variable someInterestingStuff
    }
    itcl::class Creator {
        method createProduct { } {
            return [uplevel {namespace which [Product #auto]}]
        }
     }

Singleton

The Singleton is described by Gamma et. al.. This code snippet was posted on the Incr Tcl mailing list by Mark Wilson. (Thanks Mark!)

    class Singleton {
        private common _instance {}  ;# static instance variable

        #
        # Define a "static method" Instance.
        #
        proc Instance {} {
            if { $_instance == {} } {
                set _instance [Singleton ::#auto]
            }
            return $_instance
        } 
        # end of Instance
        constructor {} {
            set caller [info level [expr [info level] - 1]]
            if ![string match "Singleton::Instance" $caller] {
                error "Class Singleton can not be directly instantiated - use Instance"
            }

        }

    }

To instantiate the singleton, do:

    set s [Singleton::Instance]

Simple Singleton

Modification of above by Lee Atkinson

    class Singleton {
        private common _instance {}  ;# static instance variable
        constructor {} {
            if { $_instance == {} } {
                set _instance onlyone
            } else {
                error "Class Singleton already instantiated"
            }
        }
    }

To instantiate the singleton, use regular constructor To test the Simple Singleton class, do:

    Singleton foo
    Singleton bar

The tcl shell will warn the developer.


Abstract method

It's safe to have some common class that has most of methods abstract, so the code takes care by itself to implement that methods in derived classes. It's pretty useful when you want to make many classes that has the same methods interface. Idea comes from both of C++ and Java (in C++ there are undefined virtual methods, in Java there are abstract methods and interfaces).

 proc abstract {method name arguments} {
        if {$method != "method"} {error "Only methods can be abstract!"}
         uplevel "
                 method [list $name] [list $arguments] {
                         error \"$name method has to be implemented for class \[string trimleft \[\$this info class] :]\"
                 }
         "
 }

To see it in action, try this:

 itcl::class Base {
         public {
                 abstract method testIt {arg}
         }
 }

 itcl::class TestClass1 {
         inherit Base

         public {
                 method someMethod {}
         }
 }

 itcl::class TestClass2 {
         inherit Base

         public {
                 method testIt {arg} {puts "test"}
         }
 }

 TestClass2 cls2
 cls2 testIt dummy
 TestClass1 cls1
 cls1 testIt dummy

The only problem that is quiet significant is the fact, that error will occur only while trying to call abstract, undefined method. There is no chance to see it while starting script.


LV: Anyone remember Tcl/Tk Programming Idioms , in which Nat Pryce started collecting Tcl patterns? Also see http://st-www.cs.uiuc.edu/users/patterns/patterns.html ... I've not heard from Nat recently.


escargo 28 Mar 2006 - Another take on this is antipatterns [L2 ], which shows what often goes wrong with software designs.


See also Design patterns in Tcl, Snit design patterns