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
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