10
4
2014
4

Tcl 现代方法:你知道几种写类的方法?

本文是从底向上逐步构建其 TclOO 大框架的,如果你更喜欢自顶向上的,可以参考别的教程。

另外,本文只是入门级别,读完本文后再去看文档或者其他教程应该会容易些,毕竟是汉语写的。这里我推荐 Ashok 写的《Tcl Programming for Windows》中的 TclOO 一章。

除去协程,面向对象编程也是现代编程语言不可或缺的一部分。Tcl 在 8.6 之前都没有内建支持面向对象编程,但这不意味着 Tcl 没法 OO——恰恰相反,Tcl 有一大堆 OO 库,我随口就能叫出几个出名的:[incr Tcl]、XOTcl、Snit。这些库各有千秋,有的模仿 C++,有的是原型继承,还有的是模仿 Common Lisp Object System,千姿百态。可能和大家所想不同,这样的「大好」局势并未给 Tcl 带来许多好处:依然有人批评 Tcl 没有 OO 支持;有的人虽然知道有,但批评选择太多让用户无所适从,或者为自己所爱的库口诛笔伐;有的人虽然不在乎别人怎么选择,但很苦恼其他扩展包依赖了自己根本不需要的 OO 库……在这种情况下,Tcl Core Team 站了出来,提出了 TIP 257,由 Donal K. Fellows 主要实现。这套面向对象机制起名叫做 TclOO,并随 Tcl 本身提供。TclOO 的设计十分灵活,可以以此为基础重新制作 [incr Tcl] 等库,让大家其乐融融地生活在一起。

思考一下,开源系统的桌面环境是不是也是这样呢?

TclOO 重要的目标就是使得它可以成为其他 OO 库的基础库,此外也足以满足普通编程使用而不至于繁冗。因此,就有了本文的标题。当然,这绝非「危言耸听」!打起精神,下面的内容可能会非常难以理解。

TclOO 默认有的两个「元类」是 oo::object 和 oo::class,这儿有两个公理,看看你能推导出什么定理。

  1. 一切类都是 oo::class 的实例
  2. oo::class 是 oo::object 的子类

由此,得到引理:

  1. oo::class 是 oo::class 的实例
  2. oo::object 是 oo::class 的实例
  3. 一切类都是 oo::object 的实例

糊涂了吗?这就是「元类」,classes of classes。:-) 这些都是真的,不信你看:

% info class subclasses oo::object
::oo::class ::oo::Slot
% info class instances oo::class
::oo::object ::oo::class ::oo::Slot

好了,基础写完了,总该进入正题了吧?对,那就是「写类」的方法。当然,还和上面的「公理」「定理」息息相关。首先我们由公理1「一切类都是 oo::class 的实例」得到,要创建一个新类,就得创建一个 oo::class 的实例:

cls create name ?arg ...?
cls new ?arg ...?

其中 create 以 $name 创建实例,而 new 则会返回新的唯一名称。从此我们还能得到更多的启发:

  1. 对象实际上是 namespace ensemble
  2. 要创建另外一个类的子类,不能直接在父类上调用 create,必须从 oo::class 直接继承;此时应在 definition script 里面写上 superclass
  3. create 常用于创建类
  4. new 常用于创建对象
  5. create 和 new 需要构造器参数(constructor arguments),oo::class 的构造器参数是 definition script

(有些人可能会在这里有疑问,不要着急,下面会谈到的)

假如我们要创建一个 Linux 「家系谱」,把 oo::class 当作根,那么一级子类就是比较老的大牌 Linux,我们以红帽为例:

oo::class create RedHat {}
oo::class create Fedora {superclass RedHat}
oo::class create CentOS {superclass RedHat}

检视之:

info class subclasses RedHat ;# ::Fedora ::CentOS

现在有趣的就来了!假如我们想让各个系统报出自己名字怎么办?记住,这可是 class 噢!我们给 RedHat 增加一个 family 方法,输出 "RedHat family Linux OSs"。可以使用 oo::objdefine,这里便是 TclOO 极端灵活性的一个体现。

oo::objdefine RedHat method family {} {
    puts "RedHat family Linux OSs"
}
RedHat family ;# 输出信息

oo::objdefine 只对单个对象起作用,因此 Fedora family 或 CentOS family 都会报错。除了在 class 上有用,很显然也在 object 上有用,此处不赘述。

现在,我们在用这个程序自动化在机房内部署 CentOS 安装,给每个服务器一个对象。

foreach addr $addresses {
    set obj [CentOS new $addr]
    $obj addInstallOsTask
    $obj addInstallSoftwareTask
    lappend machines $obj
}

# 
# 这期间做些别的事情
# 

foreach obj $machines {
    if {![$obj succeeded]} {
        $obj saveLogs
        $obj destroy ;# 自动将系统日志发送给管理员,并把自己从 machines 里面删除
    } else {
        $obj startServices
    }
}

这里我们发现,CentOS 这个类太忙了,里面涉及到了方法、构造函数、析构函数、成员变量。用 oo::objdefine 可不可以呢?倒回去看看,oo::objdefine 只对单个对象有用!但是我们查阅文档发现有这种东西:oo::define。

oo::define 有两种变体,一种是 definition script,一种是直接摊开写的。我们先看看这样:

oo::define CentOS {
    superclass RedHat
    variable addr
}

oo::define CentOS constructor {address} {
    set addr $address
}

oo::define CentOS method addInstallOsTask {} {
    magicInstall $addr centos
}

oo::define CentOS method addInstallSoftwareTask {} {
    magicInstall $addr software
}

# ......

啊!烦不烦哪你!为啥要把 oo::define CentOS 重复这么多次?!现在就是揭晓刚才谜底的时候了!我知道刚才一定有人想问 definition script 是什么,能怎么用,其实吧,就是个 script,你可以把 method、constructor 之类的东西统统写进去,然后就变得像 C++ 一样了:

oo::define CentOS {
    superclass RedHat
    variable addr

    constructor {address} {
        set addr $address
    }

    # destructor takes no arguments
    destructor {
        email $admin [getLogs]
    }

    method addInstallOsTask {} {
        magicInstall $addr centos
    }

    # ......
}

现在,我们总算入了 TclOO 的门,你已经可以在依然不太明白工作原理的情况下使用一点点 TclOO 技术了。我为什么这么说呢,是因为里面有些地方和 Tcl 语言息息相关的,如果你不太明白 Tcl 本身,那肯定用不到一块去,而有的地方(如 variable)和 Tcl 的其他地方是不一样的,因为我太懒了,所以盛情邀请你阅读 Tcl 的文档。

俗话说师父领进门,修行靠个人,我才疏学浅,文中必然会有不恰当甚至错误的地方,不敢自称师傅,不过如能助你更快进入现代 Tcl 的大门,吾愿遂矣。

最后再添一笔,虽然 TclOO 已经内建于 Tcl 8.6 了,但是业界良心 Tcl Core Team 还没忘记依然维护中的 8.5,发布了用于 8.5 的 TclOO 包

Category: 编程 | Tags: tcl tcl 现代方法 tcloo | Read Count: 4515
Avatar_small
依云 说:
Oct 06, 2014 09:41:14 PM

那两个公理感觉和 Python 的很像呢。Python 里,

1. 绝大部分类都是 type 的实例(Python 支持指定其它的元类,但是我从来没见过有人用过)
2. type 是 object 的子类

nic 说:
Oct 23, 2014 12:11:23 PM

s/从顶向上/自顶向下

XE88 APK FREE Downlo 说:
Aug 30, 2021 12:11:13 AM

Below you will understand what is important, the idea provides one of the links with an exciting site:

Alyssa 说:
Dec 30, 2022 08:07:18 PM

"There are many ways to write classes in Tcl. The most common way is to use the ""class"" command, which defines a class with a given name and superclass. Other ways to write classes include using the ""oo::class"" command, or writing a class in Lab grown diamonds pure Tcl code. The ""class"" command is the most straightforward way to write a class. It takes a class name and a superclass name, and creates a new class with the given name and superclass. For example: class MyClass { superclass MySuperclass } This creates a new class called ""MyClass"" which is a subclass of ""MySuperclass"". "


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

Powered by Chito | Hosted at is-Programmer | Theme: Aeros 2.0 by TheBuckmaker.com