这里所谓的面向对象主要是指创建一个类(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