这里所谓的面向对象主要是指创建一个类(class),并创建这个类的实例这样一种操作。
Tcl中没有直接的用于面向对象的命令。Tcl的内置命令中最接近面向对象概念的应该是namespace(命名空间)。
借助namespace的帮助,可以实现对数据的封装。如果这个类只能有一个实例(instance)的话,我们直接用namespace来模拟就可以了。问题的关键就是如何处理多个实例(instance)的问题。
stooop是Tcllib中一个面向对象编程解决方案。一个简单的例子如下:
::stooop::class circle {
proc circle {this r} { set ($this,r) $r } ; # 构造函数
proc ~circle {this} {} ; # 析构函数
proc circle {this copy} {} ; # 拷贝构造函数
# 对象方法
proc area {this args} {
set area [expr {$this,r)*($this,r)*3.14]
set ($this,area) $area
}
# 静态函数
proc name {args} {
set (data) 0
return "circle"
}
}
set c [::stooop::new circle 20] ; 创建对象实例
puts [circle::area $c] ; 调用对象方法
::stooop::delete $c ; 删除对象实例
可以看出,stooop的使用很像是一个约定了命名规则的namespace(命名空间)。
实际上stooop的内部实现也确实就是一个namespace。各个实例的数据存储通过this指针进行区分,存储在一个empty array里面。
在进行方法调用时,我们传递了对象实例的标识作为方法的第一个参数。这也是stooop提供的实现与面向对象的习惯用法不一样的地方。比起circle::area $c这样的用法,大家可能更希望看到$c area这样的习惯用法。
虽然如此,stooop毕竟是简单的,它对namespace所做的hack较少。
snit是Tcllib中另一个面向对象的实现。它的用法可以实例如下:
snit::type circle {
constructor {_r} { set r $_r } ; # 构造函数
destructor {} ; # 析构函数
# 成员变量
variable r
# 对象方法
method print {args} { puts $args }
method area {args} {
set area [expr $r*$r*3.14] ; # 直接使用成员变量
$self print ; # 通过 $self 调用其他方法
}
# 静态函数
typemethod name {args} {
return "circle"
}
}
set c [circle create %AUTO% 13] ; 创建对象实例
puts [$c area] ; 调用对象方法
$c destroy ; 删除对象实例
circle destroy ; 删除对象定义
snit也是基于namespace的实现,但要比stooop复杂的多,当然功能也要强一些。
incr Tcl 也提供了面向对象的实现方法 class。它的语法和snit有点相像。
class circle {
constructor {_r} { set r $_r } ; # 构造函数
destructor {} ; # 析构函数
variable r 10 ; # 成员变量,同时设置默认值
# 对象方法
method print {args} { puts $args }
method area {args} {
set area [expr $r*$r*3.14] ; # 直接使用成员变量
}
# 静态函数
proc name {args} { return "circle" }
}
circle c 13 ; 创建对象实例
puts [$c area] ; 调用对象方法
$c destroy ; 删除对象实例
TODO