Object-oriented programming

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.

Class definition

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:

Class definition

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
}

Inheritance

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:

Inheritance

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

Object definitions

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.

Using mixins

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:

Using mixins

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.

Forwarding methods

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:

Forwarding methods

The method log actually invokes the mainlogger command from the :: namespace. userconnected and the userdisconnected methods invoke methods from other objects.

Additional information

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:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.163.197