Object-oriented programming has been a feature Tcl offered for several years with additional packages. This was possible due to the fact that Tcl syntax is easily extendable. Those packages include Incr Tcl, which is available from http://incrtcl.sourceforge.net/itcl/, XOTcl available from http://media.wu-wien.ac.at/ and Snit which can be obtained from http://www.wjduquette.com/snit/.
However, with the release of version 8.5, Tcl now comes with an object-oriented programming functionality included called TclOO. When Tcl 8.6 is out, TclOO will soon be part of core language. In Tcl 8.5 it is an extension to the language, but leverages 8.5 extensions to support it.
This is a set of additions that allows us to build a class hierarchy on its own. TclOO is designed so that it is possible to build wrappers so that it works the same as Incr Tcl, XOTcl, or Snit. So, for those of you that use any OO extension, it might be the right time to switch. For those of you that don't know any OO system yet, start with TclOO if your applications need to work on Tcl 8.5 and newer.
One of major features that TclOO offers is multiple inheritance. This means that a class can inherit from more than one class. For example, a class that implements a network server for queuing data would inherit classes for supporting queues and classes for network connectivity. TclOO also allows multiple classes to derive from same base classes.
In addition to inheritance, TclOO also supports mixins. For classes they work similar in a way to inheritance, but mixins can also be applied to individual objects. Mixins can be added and removed dynamically. This means that we can add all methods and functions of a class on the fly to an object. Mixins are explained in more detail later in the chapter.
TclOO also offers method forwarding—this means that a method of an object can be forwarded to a different command, passed to an object as any method for that object, or as a specified method for that object.
If you are familiar with object-oriented programming in languages such as C++ or Java, Tcl's object-oriented system is different from it.
As Tcl is a dynamic language, TclOO is more dynamic and offers more features that are common for scripting languages, but which compiled languages usually lack. TclOO offers features such as modifying definitions per-object, for example adding method forwarding, mixins, or changing method definition for a specific instance of an object. Examples of how this can be done can be found later in this chapter.
Tcl does not offer features such as defining interfaces or virtual classes, which are used commonly in technologies such as Java, C++, and .NET. In Tcl world, creating an interface simply means defining set of methods and parameters they accept. Implementing a class that has all the methods is sufficient. The object itself does not even need to be written in TclOO. After that, any code can use this class/object by invoking the defined interface. This approach is in sync with Tcl's dynamic design.
Let's start by creating a simple counter class that will allow incrementing, getting the current counter, and resetting it:
oo::class create counter { constructor {} { my Resetcounter } method Resetcounter {} { # map variable "c" as local variable and set to 0 my variable c set c 0 } method increment {} { # map variable "c" as local variable and increment my variable c incr c } method getvalue {} { # return current value my variable c return $c } }
We start by creating new class called counter
. Then we define a constructor, which invokes the method Resetcounter
to set the counter to 0. The command my
is used to invoke methods for the current instance of an object. Also, the command my variable
can be used to map an object specific variable as local variable, so my variable c
means that the object-specific variable c
becomes available for a specified method.
The increment
method increases the value of the counter and getvalue
returns it. We can now create an instance of this class by invoking<className> new
. It creates a new object instance and returns its unique identifier, which is also a command name using which we can run methods of this object. For example, to create two counters and increment them, we can do the following:
set c1 [counter new] set c2 [counter new] $c1 increment $c2 increment $c1 increment puts [$c1 getvalue]
This will create two counter
instances and store their identifiers/commands in variables c1
and c2
. We then increment c1
twice and c2
once. The result is 2 being printed out. In order to delete both objects, we can simply invoke the built-in destroy
method:
$c1 destroy $c2 destroy
The following diagram shows how public methods are mapped to the counter
class. Items in bold indicate public methods while those in italics indicate private methods; the arrows indicate the path in which each method is looked up:
In this case, all public methods are simply mapped to counter class. Method Resetcounter
is not public and, therefore, is not available from public API.
Besides creating a whole class along with its definition, it is also possible to define parts of a class independently using oo::define
command. It needs an existing class name as the first argument and either an additional definition as one argument or a definition inlined as multiple arguments. For example, we can define a setvalue
method in two ways, one option is:
oo::define counter { method setvalue {value} { my variable c set c $value } }
And the other possibility is:
oo::define counter method setvalue {value} { my variable c set c $value }
We can create classes that inherit from other classes—for example, if we want to make a counter that also resets its count after being read, we can do this:
oo::class create resetablecounter { # declare class we inherit from superclass counter method getvalue {} { # get result from our counter class set result [next] my Resetcounter return $result } }
This causes our class to inherit everything from the counter
class—in TclOO this means that counter
is a superclass to resetablecounter
. We also override the getvalue
method. We use the next
command, which calls commands from the superclass and is discussed in more detail later in this chapter. Next we run the Resetcounter
method to reset the counter. We are invoking the superclass to get and reset the actual value so that if the implementation of the counter
class changes, we will still be able to use the underlying implementation.
We can see how methods are inherited from the counter class in the following diagram:
The object constructor and increment method are mapped to the counter class. The method getvalue
is created in the resetablecounter
class and, therefore, its implementation in the counter is not invoked automatically. However, our code invoked the next command to explicitly invoke getvalue
from the counter class.
At this point, it is worth mentioning that the concept by which variables in TclOO work differs from the concepts of object-oriented programming from C++ or Java. Variables are set up at each object level and are not differentiated or protected by the class that created them. This way our resetablecounter
class can access data from the counter
class. On the other hand, there is no explicit need to create variables and they can be added on the fly. When destroying an object, all its variables are deleted.
TclOO also introduces a mechanism for invoking commands from within the object and from external code. By default, all methods are accessible from both inside of an object (by invoking my <methodName>)
and from outside of an object (by invoking $c1 <methodName>
for our example). This can be changed by adding unexport
or export
to class definition. For example, we can create a class that doesn't allow the increment
method to be invoked from outside of object methods by doing:
oo::class create timedcounter { superclass counter constructor {} { # here we should set up some timer event to increment # the counter periodically } unexport increment }
Often our classes will inherit from multiple base classes. In this case, we need to specify more than one superclass
. In this case all methods are looked up in all base classes along with all their direct and indirect base classes. If a method is implemented by multiple classes, then the order in which classes are specified determines which one is run. However, the command next
will look into other base classes as well, so one base class can retrieve information from other base classes. For example:
oo::class create testclass { method getvalue {} { puts "In testclass" next } } oo::class create testcounter { superclass testclass counter }
In this case, running getvalue
from testcounter
class will cause In testclass
to be printed to standard output and the method would return current value. Internally, TclOO will cause getvalue
from testclass
to be invoked, which then invokes the method from the counter
class using next
command.
The next
command can also be used for methods that accept parameters. In this case, we need to run next
with parameters that are expected by the base class, which can be different than currently accepted arguments. For example, assuming our counter
class already has a setcounter
method that accepts the new value as an argument, we can override it to write new value and run the implementation from the base class in the following way:
oo::class create othercounter { superclass counter method setvalue {value} { puts "Setting value to $value" next $value } }
TclOO works in such a way that both classes and objects can define and/or override methods by using the oo::objdefine
command. It accepts an object name and the definition that should be added.
For example, in order to add additional code to the getvalue
method of only a specified instance of a class, we can do the following:
oo::objdefine $c1 { method getvalue {} { puts "Hello world from getvalue for this object only" next } }
We are using the next
command, because from the TclOO perspective, an object-specific implementation of a method is treated in the same way as though a subclass would override a particular method, so next
invokes the same method within the class for this object.
Mixins in TclOO work in a way similar to inheritance. When our class mixes in another class, all methods available from that class are also available from within our class. The main difference is that when our class inherits a base class, methods from our class override methods in the base class. Mixins work in the opposite way—methods from a mixin override methods from our class so that mixins can interact with our class.
For example, we can build a mixin that caches results based on arguments:
oo::class create cache { method perform {args} { my variable cachedata # key for caching data, #for now we assume it's all arguments set key $args # if we don't know the results yet, run actual method if {![info exists cachedata($key)]} { set cachedata($key) [next {*}$args] } return $cachedata($key) } }
This mixin is generic enough, so it can cache any class that performs calculations when the perform
method is invoked. Now we can define our class that will use the cache
mixin to increase performance:
oo::class create complexcalculation { # use caching mechanism mixin cache method perform {a b} { # simulate long running calculations # by waiting for 0.5 second after 500 return [expr {$a + $b}] } }
The following shows how cache and complexcalculation
classes are related:
In this case, even though the complexcalculation
class refers to cache, from method lookup perspective, the implementation in cache class has higher priority. Therefore, invoking perform
actually calls the method from the cache class. Implementation in cache calls perform
from complexcalculation
using next
command.
Our class definition specifies a list of mixins that should be included for each instance of an object. In this case, it contains only one mixin—our cache
class. When invoking perform
method, calculations will take 0.5 second to perform due to delay, first time it is invoked with specific arguments. Next time the mixin will simply return a cached value without our method actually being implemented.
Another difference is that mixins can be applied to an individual object, while inheritance needs to be carried out for the entire class. For example, our complexcalculation
class might be implemented without the mixin cache
statement in the class definition and later on we can do the following to apply caching to only specific objects.
This way we can enable caching by invoking the oo::objdefine $object mixin cache
command—it sets mixins to our cache
class. Invoking the oo::objdefine $object mixin
command will disable caching by setting no mixins for this object. Note that even if a class definition specifies a list of mixins, we can explicitly disable it—for example, disable cache on specific instances for debugging purposes. This gives more flexibility than static inheritance.
Method forwarding is a feature that allows specifying that a method of a class will be forwarded to another command, another object or a specific method in an object. While forwards can easily be implemented as methods, this takes the burden of maintaining and passing arguments correctly off the developer of this class.
Let's assume we have a class called eventhandler
that allows a generic set of operations, such as adding and removing a command to be run when something occurs. If we inherit this class in our code, we could only have one type of event. So it makes more sense to create one or more objects and forward all calls to these objects. For example:
oo::class create messagingserver { # forward method log to # mainlogger command in global namespace forward log ::mainlogger constructor {} { # create forwarded method when user # connects and disconnects from our server oo::objdefine [self] forward userconnected [eventhandler new] oo::objdefine [self] forward userdisconnected [eventhandler new] } destructor { # delete event handler objects userconnected destroy userdisconnected destroy } }
This causes our class to have the method log
which forwards it to the mainlogger
command—outside of object definition and common for all instances of an object. This can be defined for the entire class because forwarding is done in the same way for all objects.
It will also cause the methods userconnected
and userdisconnected
to forward all parameters to that object—meaning that the first parameter will be the method to invoke for that object and all remaining arguments will be passed as arguments to that method.
As we need to create an object instance, we need to do that from inside the constructor—as objects are created with unique identifier for each instance of messagingserver
instance, we need to perform this for each object separately and from within our constructor. The self
command returns the current object identifier so that we can invoke oo::objdefine
to add method forwarding definition from a constructor.
The following diagram shows how each of publicly available methods maps to other objects and procedures:
The method log
actually invokes the mainlogger
command from the :: namespace. userconnected
and the userdisconnected
methods invoke methods from other objects.
While some of the approaches might seem different from known solutions (such as objects in C++, Java, or Python), mechanisms in TclOO offer much more flexibility to the developer. As one of key Tcl aspects is that it is a dynamic language, its object-oriented programming syntax also offers similar flexibility.
TclOO also offers a feature called filters, which control and filter method invocations. It can be used to achieve highly advanced features such as caching and aspect programming. While this topic is beyond the scope of this book, we recommend readers doing advanced object-oriented programming in Tcl investigate this feature, and in particular, read the http://wiki.tcl.tk/20308 page, which shows how to extend the oo::class
to add aspects.
More information about TclOO features can be found in the following Tcl manual pages:
3.137.163.197