Introducing Tcl 8.7 Part 9: More TclOO
This is the ninth in a series of posts about new features in the upcoming version 8.7 of Tcl. It describes more enhancements to Tcl's object-oriented programming facilities.
To take Tcl 8.7 for a spin, you can download a pre-alpha binary for your platform. Alternatively, you can build it yourself from the
core-8-branch
branch in the Tcl fossil repository.
NOTE: If you are not familiar with the object-oriented programming features in Tcl 8.6, see this tutorial.
Some of these enhancements were covered in a previous post:
- Private variables and methods
- Inline
export
/unexport
modifiers - Additional slot operations
This post describes the following additional features:
- Class variables and methods
- Singleton and abstract classes
A future post will cover a final set of enhancements:
- Convenience commands for common operations
- Hooks for extending the class and object definition language
- Utilities for creating custom definition dialects
Class variables
A class variable is a variable that is shared across all instances of a class. Its initial value can be set through a call to initialize
in the class definition. Then within a method definition, the classvariable
command is used to bring it into scope.
Here is an example to demonstrate usage. Suppose we only want a file to be opened at most once in our application. We can keep track of open files through a class variable.
oo::class create File {
initialize {
variable OpenFiles
set OpenFiles [dict create]
}
constructor {path} {
classvariable OpenFiles
set path [file normalize $path]
if {[dict exists $OpenFiles $path]} {
error "File already open"
}
my variable myPath
set myPath $path
dict set OpenFiles $path 1
}
destructor {
my variable myPath
if {[info exists myPath]} {
classvariable OpenFiles
dict unset OpenFiles $myPath
}
return
}
}
And a demonstration,
% File create fd1 foo.txt
::fd1
% File create fd2 bar.txt
::fd2
% File create fd3 foo.txt
File already open
% fd1 destroy
% File create fd3 foo.txt
::fd3
Internally, class variables are created within the class object itself (remember classes are also objects in the TclOO world). The classvariable
command is then similar to commands like upvar
in that it links that variable to a local variable of the same name in a method.
Similar functionality could be implemented in 8.6 as demonstrated by the ooutil package in Tcllib. Tcl 8.7 brings the capability into the core.
Class methods
Analogous to class variables are class methods which run within the context of the defining class as opposed to the defining class instance. Continuing the above example and extending the definition of our File
class, we can define a class method.
% oo::define File {
classmethod numfiles {} {
my variable OpenFiles
return [dict size $OpenFiles]
}
}
% fd3 numfiles
2
Note above that numfiles
uses my variable
, not classvariable
, to reference OpenFiles
. This is because, as a class method, it executes within the context of File
itself and not the fd3
instance.
Moreover, you do not even need an instance of the class to call a class method. You can directly invoke it on the class.
% File numfiles
2
Again, like class variables, similar functionality is available for Tcl 8.6 through the ooutil package in Tcllib.
Singletons
The singleton pattern is employed when you want at most one instance of a class to exist. Why? Ask google for use cases. In Tcl 8.7, you can implement this pattern simply by defining the class with oo::singleton
instead of oo::class
.
oo::singleton create TheOne {
method id {} {return [self]}
}
All attempts to obtain a new TheOne
object return the same object.
% set o [TheOne new]
::oo::Obj87
% set o2 [TheOne new]
::oo::Obj87
Moreover, you cannot create a named object of that class using create
.
% TheOne create one
unknown method "create": must be destroy or new
Note that classes inheriting from a singleton class are not restricted.
% oo::class create ChildOfOne { superclass TheOne }
::ChildOfOne
% ChildOfOne new
::oo::Obj90
% ChildOfOne new
::oo::Obj91
Abstract classes
Another common pattern is the definition of abstract classes that are intended to be used as a base class and not be directly instantiated. In Tcl 8.7, the oo::abstract
command can be used in lieu of oo::class
to create abstract classes.
oo::abstract create RegularPolygon {
constructor {sides len} {
my variable Sides
my variable Len
set Sides $sides
set Len $len
}
method perimeter {} {
my variable Sides
my variable Len
return [expr {$Sides * $Len}]
}
}
An attempt to create an instance of RegularPolygon
fails.
% RegularPolygon create poly
unknown method "create": must be destroy
However, we can derive a class from it.
oo::class create Square {
superclass RegularPolygon
constructor {len} {
next 4 $len
}
method area {} {
my variable Len
return [expr {$Len * $Len}]
}
}
We can then create objects from the derived class and call inherited methods on it as usual.
% Square create square 5
::square
% square area
25
% square perimeter
20
Unlike some languages like C++, abstract classes cannot be used to enforce interface requirements on child classes. So there is no way to say for example that every derived class must have an area
method.
Coming up
We are almost done with TclOO, with one more post to go where I will describe some convenience features added to TclOO in 8.7 along with the ability define custom class definition dialects.