null
分类: 其他平台
2006-01-24 13:56:48
Squeak 和 Smalltalk-80 编程语言的基本特征
作者:Wolfgang Kreutzer
翻译:寒蝉退士 |
注意这个教程在例子中使用了 Squeak 的外观和功能。Squeak 与现代商业实现 (例如 和 )的主要区别是,它使用标准 Smalltalk 语法和编程工具,并且它的类库非常类似于在最初的系列书籍中报告的 Smalltalk 系统。
一些 基本 建造
Smalltalk 的根源可回溯到七十年代早期,那时在 Xerox Palo Alto 研究中心 (Parc) 的 Dynabook 计划的环境下探索出的许多想法被结合到 Smalltalk/ObjectWorks 编程系统中。这个 "Dynabook" 基于一个梦想:成人和儿童拥有便宜的笔记本大小的个人计算机,有能力处理他们所有的与信息有关的需求。在加入 Xerox Parc 之前, 这个想法的主要倡导者 Alan Kay 工作在 Utah 大学,他在那里是开发 Flex 编程系统小组的成员,这个系统是针对灵活的面向模拟和图形的个人计算机的一个新颖的设计,有从 Simula 和 Sketchpad 演化来得许多想法。这时 Kay 向 Parc 的主管层建议扩充这个研究,他的设计完全超越了当时的计算机硬件和软件技术的接受能力。 Dynabook 基于的假定是有充足的内存和处理能力,还有可以感知手指触摸的一个平坦的屏幕显示器。为了声音再现而插入了一个立体声系统,为了访问大的共享数据库而设有到电话线的一个连接。Kay 坚信 Dynabook 会象 Gutenberg 发明印刷机之后个人可负担起的图书替代中世纪的手工文稿一样有革命性。他想象了在各种领域中使用这种设备的一个复合体,如绘制图片(比如给学前儿童),信息存储(比如给博士),音乐演奏 (比如给作曲家),模型的动态模拟和图形动画(比如给教师),在三维空间中物体的表示法(比如给建筑师),或在一个公司内的库存和资金流通计算 (比如给商人)。为了完全开拓新媒介提供的潜在的交互,需要用户有一定程度的编程技巧。但是现存的编程语言是为专家设计的并在很大程度上只适合数值任务;在 Kay 看来严格的这种应用对希望使用 Dynabook 的工作方式是相对的无关紧要的。尽管 Lisp 家族中的语言提供了必要的符号处理能力,但仍然缺乏数据封装构造,并且不迎合由非专家用户来做容易的扩展。
所以开发一门新语言获得了优先权。与此同时 S. Papert 和一组其他研究人员也在制作叫做 Logo 的一个交互性的和高度图形化的基于计算机的学习环境,他们想用它来教儿童编程。Logo 基于 J. Piaget's 在发展心理学上的工作,而 Kay 吸收了许多它的中心观念。在过程式语言中编程经常要求过程的精密组合,并且使这种构造工作的复杂性随程序的长度而呈指数增长。因为“动态”个人计算媒介的整体想法极度依赖的特征是让非专家做轻易的概念扩展,Kay 选择分层设计的积木方法,围绕封装和继承来建造。 Smalltalk 是最终形成的编程语言。它是完全基于对象和消息概念的第一个计算机语言。
Dynabook 计划从未成功完成,而许多今天的笔记本计算机已经达到和超过了它的最初目标。在这项计划存在那些年 Xerox Parc 做了许多研究,对计算机的很多领域有巨大影响。第一个实验实现运行在所谓的 Alto 工作站上,它是改装的 Data General Nova 小型机。后来被 Dorado 所替代,它是订制的和非常快的个人计算机。在开发的时候它们都是最新技术的计算机。在 Xerox Parc 的研究首创了在白色图形上的内存映射黑色输出 (替代了在黑色 CRT 显示器上的传统的绿色输出) 和局域网的以太网体系。在软件方面,用多于一个多年的周期率先实验了在基于窗口的、鼠标和菜单驱动的用户界面上的许多革新想法。
Smalltalk 作为 Dynabook 的编程工具,最初被构想为适合被儿童使用而无需预先的计算机知识的一门简单语言。名字的选择反映了这个意图。最初的 Smalltalk 系统是一千行的 Basic 程序,它在1972年10月成功的计算了 3+4 。两个月之后随后的一个汇编实现被称为 Smalltalk-72 系统。D. Ingalls 是这些系统的主要实现者。在 1974 年 Smalltalk 被移植到 Alto 计算机并进行许多建造图形用户界面的实验。应用程序包括海龟图形、一个鼠标驱动的程序编辑器,一个结构化图形编辑器,一个动画系统和一个音乐系统。Smalltalk-72 还作为向高中生教面向对象编程的实验程序的基础。Smalltalk-74 为位图映射图形 (类 BitBlt)和虚拟内存增加了更好的设施。这个改进的系统被用来实现一个信息检索系统和一个基于窗口的用户界面。Smalltalk-74 发展成了 Smalltalk-76,它基于一个更加清晰的设计。Smalltalk-76 还增加了继承的想法,它缺少两个早期的系统。计划了作为可移植实现的基础的“字节码”,而这个设计的一个宏代码模拟器导致了改进的性能。 Smalltalk-76 被多于 20 个人作为日常基本工具使用了 4 年。一个更干净的实现(Smalltalk-78) 最终导致了 Smalltalk-80,它是在 Xerox Parc 外部可获得的第一个系统。
在 Smalltalk 早期开发的 8 年间,“学习研究组”的成员偶尔的出版物和访问学者的报告引起了相当可观的兴趣。获取更详细的信息的尝试通常受挫。在 1979 和 1980 年 Xerox 最终允许 Smalltalk-80 公开发行。这个过程被计划为三个阶段: 一系列介绍文章,带有详细系统定义的一本书,和带有可移植实现的磁带。介绍文章以 Byte 的一个专刊出版。详细系统规定的计划被修订为四本的丛书。第一本 (叫作“蓝皮书”)描述语言和它的实现。第二本(叫作“橙皮书” )提供对 Smalltalk 的用户界面的详细讨论。第三本打算描述如何使用 Smalltalk 作为图形和交互应用的工具,第四本(叫作“绿皮书” ) 意图辅助实现者。不幸的是第三本从未出版 ,这在如何使用系统的文档中留下了长时间的空白。
可移植实现的软件被分为两个主要部分,虚拟机和虚拟映射。虚拟映射(VI) 是编码 Smalltalk 功能的一系列类。这包括数据结构的定义、图形和文本处理、编译器,反编译器、调试器、视图和用户界面支持。所有定义用 Smalltalk 自身给出,在这个意义上它是机器无关的,而编译器生成的代码是叫作“字节码”的中间代码 。虚拟机(VM) 形成每个实现的机器相关的部分,包括一个字节码解释器、一个存储管理器、和一些“原语”方法。只要能写出正确的虚拟机,Smalltalk 就可以移植到提供位映射图形和一个定点设备的任何平台上。Xerox 保留系统的版权并且禁止任何未经授权的复制。在尝试广泛的发行系统之前为了测试系统的可移植性,向一些计算机公司发出了适当的设备上开发领航实现的邀请是。四个接受邀请的公司最终生成了生产系统:Apple Computer、Digital Equipment、Hewlett Packard 和 Tektronix。这个过程从系统中铲除了一些错误并在 1983 年在更广泛的社区中发行了新版本 (VI2)。自从开始了系统的公开发行,Xerox Parc 开始与 Fairchild 实验室协作 AI 研究,为 Motorola 68000 微处理器的 Smalltalk 实现改进设计。这导致叫做“PS Smalltalk”的一个系统,这形成了 Parc Place 系统当前的 Smalltalk 产品的基础。Smalltalk 计划也在日本和欧洲发起。在一段时间内 Tektronix 保持在语言方面活跃的参与(involvement)并提供完全范围的基于 Smalltalk 的工作站。他们还支援了对系统作出了许多有趣的创新的一组研究者。Apple computers 对 Smalltalk 有强烈的兴趣。在80年代早期一些最初的学习研究组成员(包括 L. Tesler 和 D. Ingalls) 加入了 Apple ,努力设计和实现了强烈的基于来自 Smalltalk 环境的想法的一个新计算机体系。这个计划直接导致了 Lisa 和 Macintosh 微机。不要奇怪 Macintosh 的用户界面中的许多想法看起来非常类似于 Smalltalk。
在 IBM 设备上的那些早期的 Smalltalk 实现中,Digitalk Smalltalk V 是唯一幸存的。Smalltalk V 和后来的 Visual Smalltalk 把自己限制为非常简单的用户界面,很大程度上因为早期 IBM PC 硬件和 Microsoft DOS 操作系统的限制,但售价非常合理。Xerox 自己在 Smalltalk 计划上只获得了稀少的收获。一些早期 Smalltalk 工作站(Xerox 1100 系列)售价过高而只售出很少。Star 机器是基于来自 Smalltalk 界面的想法的一个高级办公计算机,也由于同样的原因而从未流行。在 1987 年 Xerox 决定公司化它的 Smalltalk 利益。建立了叫作 Parc Place systems 的一个独立的公司,负责进一步开发和 Smalltalk 的许可与系统的发行。这个新公司雇用了最初的 Smalltalk 小组的一些成员并在各种计算机系统上发行了一些改进的实现 (VI 2.2 , 2.3, 2.4, 2.5),如 Apple Macintosh、IBM PC 和 Sun 工作站。在 1990 年出品了一个新版本的 Smalltalk,叫做 ObjectWorks 和以后的 VisualWorks,提供了使用了主机的“本地”窗口管理器的有颜色的和可移植的界面。这要求在 Smalltalk 虚拟机和主机的操作系统之间作功能上的重大的重新安排,这导致了一个完全重新设计的语言,用它写成的应用程序在广泛的计算机设备上是高度可移植的。后来 Parc Place system 合并了 Digitalk,并且合并后的公司忙着做一个新的联合实现,代号叫“van Gogh”。 三年之后 IBM 也加入主要的 Smalltalk 厂商的行列。它的 VisualAge Smalltalk 与 VisualWorks 只有很少的区别。
但是上述的商业系统价格昂贵,从 1996 年起已经出现了一些公众域 Smalltalk 派生者如 Smalltalk X、Dorado Smalltalk 和 Squeak 。其中 Squeak 特别有趣,它在一小组研究者活跃的开发下,包括 Smalltalk 的最初建立者 Alan Kay 和 Dan Ingalls。这个计划由 Dan Ingalls 领导,而 Kay 以一个 Disney 成员的角色来支持它。在许多方面上 Squeak 代表了对最初的 Smalltalk 计划的价值的一个回归。它的设计基于 Smalltalk 2.5,扩张了更现代的彩色图形模型和实现技术。由于在继续进一步开发它,它可以迁移到其他的领域,例如它的最新实现包含一个实验的用户界面体系叫做 Morphic (吸纳自 Sun 的已停止了的 Self 计划)。 Squeak 可免费获得当前的实现,存在于 Macintosh 、Windows PC 和 Unix 系统上。还有一个 Squeak 邮件列表让它的用户社区及时了解最新的发展。
Squeak 被选择为 COSC205 的编程工具主要因为 Smalltalk 是有很少语法负担的纯面向对象语言,而 Squeak 提供了这个想法的一个可自由访问和移植的实现。还有它的简洁、效率和趣味 :)
Smalltalk 是一个纯面向对象语言,它清晰的支持类、方法、消息和继承的概念。
所有 Smalltalk 代码由发送到对象的消息链组成。甚至编程环境自身也是在这种表现方法(metaphor)下设计的。大量的预先定义的类共同的导致了这个系统惊人的功能。与多数其他编程工具不同的是所有这些功能对浏览和变更总是可以访问的,一个因素导致 Smalltalk 成为一个非常灵活的系统,就是易于依据一个人自己的偏好来定制。还有一些东西叫做“原语”,这只是因为一些操作出于效率的原因而直接用机器代码实现。通常适用于基本算数、图形和其他 I/O 操作,它们构成典型的 Smalltalk 系统代码的 5%。但这些原语不是不可变的,如果你可以承受 性能损失的话总是可以屏弃掉它们的定义。
这个系统的基本功能来源于叫做 模型、视图、控制器的表现方法,这里的短语模型指的是一段代码,它描述一个指定应用程序的所有合理的相关结构和行为特征;是常规意义上的 “程序”。视图是模型的一个屏幕表示,它典型的存在于一个窗口中并以文字或图形形式反映这个模型的某些特征。在任何给定时刻可以有一个模型的多于一个视图活跃,在这种意义上这两个概念是正交的。控制器实体最终提供用户交互的方式。对初学 Smalltalk 的编程者最好把模型、视图和控制器之间的区分隐藏一段时间。尽管写特定的视图和控制器代码不是很难,但已经有足够多的标准构件来满足许多目的。实际上,好的 Smalltalk 编程风格强烈的鼓励对现存的代码做明智的修改而不是建立新程序。这要求代码阅读技巧和一个环境,在这里类描述和它们的文档可以容易的访问和修改。
桌面表现方式塑造了 Smalltalk 用以提供访问程序的方式。在下列章节中我们将讨论和诠释某些 Smalltalk 语法和它的接口的主要构件。但是更彻底的处置或在完全考察这个系统的内置功能(类库) 超出了这个总结的范围。[Goldberg, 1983] 和 [Goldberg, 1984] 是标准的技术参考,还有 LaLonde 和 Pugh (1990)写的两卷。
上图显示了 Smalltalk 的一个典型项目的桌面抓图。除了一些折叠的(collapse)的窗口,三个打开的视图在屏幕上的重叠的窗口中是可见的。它们是一个“记录簿” (transcript) 窗口,系统使用它来显示和记录各种状态信息并也可以被用户写入,一个“工作空间”用来计算 Smalltalk 表达式,和一个“浏览器”用来察看代码。这里的工作空间持有一个表达式,它要求系统在记录簿中显示一个字符串,而浏览器定位于展示“isEmpty”方 法,它包含在隶属于类种类“Collections-abstract”中的“Collection”类的实例方法的“testing”种类中。
Smalltalk 要求一个定点设备用于系统/用户交互;通常是鼠标。连接到 Alto 和 Dorado 机器的最初的设备有 3 个按钮,贴有“黄色”、“红色”和“蓝色”标签,仍用这个惯例来提及它们。如果在特定主机上不能获得三键鼠标 ,则必须用鼠标按钮和控制键的组合或其他方式来模拟。例如 Macintosh 实现使用单键鼠标,它的功能由上下文来决定;就是说依据光标的位置或同时按下的其他按键来激发菜单。
译注:两键鼠标用 Alt+左键 模拟中间键。Windows 系统的三键映射不是红黄蓝而是红蓝黄。
使用所谓的“红色”按钮选择信息,而“黄色”按钮激发操纵一个视图的内容的菜单。最后,“蓝色”按钮激发的菜单提供在窗口自身上的一些转换 (比如 移动、改变大小、改变标签 ...)。尽管“蓝色”钮的选择是固定的,与“黄色”按钮相关联的菜单的特定内容依赖于在一个视图中光标的位置,并由察看的应用来定义 。
Smalltalk 环境使用“弹出”菜单替代因 Apple Macintosh 用户界面而流行的“下拉”。优点是可以在屏幕的任何地方激发它们,所以更适合大显示器平面。当在屏幕的空白区域上按下鼠标按钮,则激发选择这个系统的“顶层”功能的加了外框的菜单 。所有以“...”为后缀的菜单条目都有子选项,比如,选择“open”提供一些选择:
工作空间提供某篇文本的一个视图。它们都关联着类 Macintosh 的一个剪切和粘贴编辑器并且经常被作为一个临时“便签簿”使用,在测试新方法定义的过程中录入和计算表达式。在这种情况下窗口的窗体只是录入文本的一个区域。与任何 Squeak 窗口一样,当鼠标悬在左边窗口边框的时候,它将放弃它的“选择”功能并转而激发一个“红色按钮”菜单,提供操纵这个视图的选择的一个选集(参见上面的“黄色”按钮菜单)。许多 Smalltalk 视图打开进入一个信息空间,在一个窗口中只能展示它的一部分,所以它们有滚动条来做进一步探索。Squeak 的滚动条出现在视图的左侧并且只在鼠标在其中的时候出现。附加在下面的工作空间上的滚动条指示目前对视图没有隐藏信息。如果黑条变成鼠标可以“夺取”的一个矩形,则可以移上和移下来展示信息的隐藏部分。
下面的图片展示了另一种类型(叫作“记录簿”)窗口并有与之关联的“蓝色按钮”。
记录簿窗口是文本收集器,要显示的信息可以发送给它。“系统记录簿”是用于显示各种系统信息的预先定义的实体,并且全局名字“ Transcript ”被永久保留给这个对象。
工作空间允许以下面展示的方式计算表达式。使用“选择”按钮选择相应的表达式,在这个工作空间内可以激发“执行”菜单(如图所示)。这个菜单与提供了其他选项在一起的触发计算的两个操作: doIt 将发送消息到涉及的对象,而 printIt 还展示返回的值。同于 Lisp 所有 Smalltalk 代码必须有返回某个值。如果没有其他指定则它将是最后的消息的接收者。这里是发送带有参数‘2’的消息“+”到数“2”。以反白显示来突出结果值。
作为 Smalltalk 语法的简短摘要的上下文,工作空间和记录簿就足够了,我们以后再回来讨论其他用户界面构件。
象其他面向对象语言一样 Smalltalk 是围绕类、方法和消息的概念建造的,并且它自身是由一大堆预先定义的类组成的,通过消息传递来激发它们的功能。甚至用户界面也是用这种方式设计的,所以我们可以通过计算适当的消息表达式来操纵视图而不用选择菜单的某个选项。例如:
answer := FillInTheBlank request:'Give me cookie !!! (please)'.
是一个消息表达式,其中 request: 要被发送到类 FillInTheBlank。它是 Smalltalk 库中的一个类,作为响应它将在 一个“查询框”形状中建立自己的一个实例并在屏幕上显示它。用户必须接着键入一个答案(作为一个字符串) 。它将被绑定到一个临时局部变量 answer (它必须已经被声明了)上。下图显示了结果。
answer 现在有了‘love’作为它的值。请注意为了响应一个消息已经建立了在一个适当的窗口框架中的一个视图而不是通过菜单选择。
Smalltalk 提供数、字符、字符串、符号、数组、类和对象作为基本原语。注释可以出现在代码中的任何地方,只要它们被包围在双引号中。数被写成常见式样,它们是整数、 浮点数和作为子类的小数。字符必须前导一个美元符号,字符串必须包围在单引号之中,(双引号被用于注释)而数组的元素被包围圆括号中并前导‘#’。
7 "a number"
$z "a character"
'colourless ideas sleep furiously' "a string"
#(#tom #dick #harry) "an array of 3 components"
#(#one 'should shower at least' 3 'times a week')
最后的表达式展示了数组中的元素不是必须有相同的类型,我们使用的这个数组包含一个符号常量、两个字符串和一个数。# 字符的作用类似于 Lisp 的引用操作符,它抑制对它所前导的符号的计算。下图中我们提及的 Snarfle 导致一个“未定义的标识符”响应,带有解决这种状况的可能选项的一个菜单,而此前的使用的 'Snarfle' 和 #Snarfle 简单的参照字符串和符号自身。在观看下图的时候,很明显的发送到收集器对象(这里是记录簿)的 show: 和 cr 消息导致显示它们的参数和并回车。
Smalltalk 标识符可以包含字母和数字的任何序列,但必须以字母开始。不允许其他符号。 Smalltalk 对局部变量和全局变量加以语法上的区别,在其中所有的全局变量都必须开始于一个大写字母,而局部变量总是开始于小写字母。假定类名字是全局的所以必须开始 于大写字母。Smalltalk 是“动态类型”语言,在这门语言中类型信息不与标识符相关联,而是与对象自身相关联,与在它们所实例化的类中所反映的和它们将要与之作用的消息的导致的类 型一样。但是,这个信息经常只能在运行时间获得,并且不能在编译一段代码的时候侦测出来。任何对象都可以邦定到任何标识符并且在声明变量的时候不需要类型 指定。但是,所有变量在引用之前都必须被定义。局部变量必须被邦定为联结到一个对象上的实例变量,或者被明确的声明为临时的。这种临时变量的声明被包围在 竖线之间并以空格分隔。
|kermit skooter piggy henrietta|
声明了四个临时变量。这里还有叫做 Smalltalk 的一个变量,持有到所有声明的全局变量的引用并且我们使用 at:put: 消息来输入新项目。
Smalltalk at: #Kermit put: Frog new.
Smalltalk at: #Piggy put: Pig new.
Smalltalk at: #Henrietta put: Chicken new.
Smalltalk at: #Skooter put: Mongrel new.
如果没有别的要求所有变量都被初始化为 nil。使用 := 符号来把名字绑定到对象。
|fozzie| fozzie := Bear new.
建立类 Bear 的一个新实例并把它邦定到“fozzie”,此后我们可以接着向它发送消息直到它过期。
消息表达式是发送到对象的消息的一个序列,由句点分隔。消息名字应该指示想要得动作的类型。它们由选择子和随后的各自的消息组成,如果有的话。一元消息不要求参数,而二元参数以常见的中缀表示法写成。所有其他种类的消息使用到关键字来命名参数。
"到一个类的一元消息: 'new' 返回类 CookieMonster 的新建立的实例" CookieMonster new. "到一个实例的二元消息: '+' 返回类 integer 的一个实例(总和)" 7 + 7. "到一个实例的关键字消息: 'give:to:' 提示 Skooter 去 进行一个殷勤的举动" Skooter give: #flowers to: Henrietta
消息可以是嵌套的,在这种情况下适用一些简单规则: 分析次序是从左至右,优先级是: 一元 -> 二元 -> 关键字消息; 在需要消除序列的歧义的地方必须使用圆括号。作为简写,发送到相同接收者的消息可以使用分号来“级联”,这样
fozzie center; show; dance
将初始化到 fozzie 的消息的一个序列;就是说,首先导致它使它自身进入屏幕,向观众显示自己,并尝试一些娱乐表演。
所有的 Smalltalk 的数据结构都表示为对象,并且在它的类库中可大量获得。
对象(Object)是 Smalltalk 的继承层次的根,提供对所有对象的一些基本功能。类搜集(Collection)是对象的众多子类之一,并且它自己可以被进一步指定为一套完整系列的数据结构。集合(Set)只是其中之一,有字典(Dictionary)作为它的子类。使用字典来存储成对的对象之间的关联。下面的代码在一个简单的例子中展示了这些数据结构的使用。首先定义了一个字典,把一些老朋友与一组美德关联在一起。接着发送消息到这些对象,列出和查找键。
Smalltalk 贡献了面向对象的完全解释,即使是控制结构都被实现为消息模式。通过 ifTrue:、ifFalse: 和 ifFalse:ifTrue: 消息支持选择执行的待选路径。用 whileTrue:、whileFalse:、 timesRepeat:、do:、to:do:、collect: 或递归来指定循环。Smalltalk 使用块的概念来推迟计算。块可以接受参数,可以有临时变量,并被用来封装以后可能执行的代码。此外它们还常用作给控制结构的参数。因为块自身是对象,它们可以绑定到标识符上。value 消息强制一个块的执行。
上面的工作空间展示了平常如何使用块的两个例子。第一种情况是,在一个循环内部流动于一定范围的数上作一个简单的测试。如果一个数是偶数,在记录簿 中显示‘EVEN’,如果是奇数,则打印‘ODD'。这里我们限制我们的计数为三,所以将测试 0、1 和 2。可以使用 whileTrue: 和 if..: 消息的组合来完成这个任务。要注意的事情是,在 whileTrue: 结构的情况下接收者和参数都必须是块,而在 ifTrue: 和 ifFalse: 的情况下只有参数必须是块。第二个例子处理符号的一个列表。首先构造描述适当动作的块并命名为“canon”。这里要注意的是使用分号来前缀两个块参数的 名字,用竖线来把他们与块体分隔开。任何临时变量,如果需要的话,将跟随在此后。这个块现在可以按需要执行多次,给出不同的值作为参数。在我们的例子中 value:value: 消息强制执行可疑品质(dubious quality)的三声合唱。
可以通过对象发送消息到自己来定义递归结构。在下一章中我们将见到一些它们的例子。Smalltalk 的强大的面向对象还允许以优雅的方式定义额外的控制结构,向守卫这个控制结构执行的类附加适当的方法。例如,如果我们想用一个数值值来控制一个选择或循环,则应向类 Number 附加一个相应的方法。
Smalltalk 的类描述的语法在浏览器窗口下面的窗格中展示。使用浏览器来观看现存的类和定义新类。与面向对象哲学相和谐,每个类充任它自己的命令语言的解释器,通过消息协议定义它的功能; 就是说,对象可以响应的所有消息的集合。通过在对象的类自身中定义的方法与通过引用继承自超类的方法来理解消息。尽管在这门语言的早期版本中能获得用“挂钩”来实现多继承,Smalltalk-80 支持继承的一个层次方案。Class 描述需要一个超类、实例&类变量(如果有的话)、和实例&类方法。这些部分都不是强制的并且类可以没有其中任何的部分,例如
Object subClass: #Dummy
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Sesame Street'
是完全正确的。“poolDictionaries”的概念与“pool 变量”相关联,这个概念我们不进一步追究。注意我们已经讨论过了实例变量和实例方法之间与类变量和类方法之间的区别。
不同种类的浏览器在 Smalltalk 中扮演了重要角色。使用系统浏览器来访问所有类和方法的定义 - 系统定义的和用户定义的 - Smalltalk 知道的。还需要用它们定义新类和方法。在类的有关选集上可以打开任意数目的系统浏览器,而在开发期间编程者会经常在它们之间切换来查看、变更、剪切和复制代码。
系统浏览器占据了由五个窗格的一个窗口。上面四个窗格被用作导航通过一个定义空间的一个“路径”,而下面的窗格查看选择的定义自身。为了向在典型的 Smalltalk 系统中可以找到的大量的类提供某种结构,所有的类都被组织入种类(category)中 。首先我们必须输入一个类种类,这导致在右侧窗格中出现可归类到这个标签之下的类的一个可滚动的列表。从这个列表中选择一个类,缩小浏览器的焦点到它的所 有方法上,它们现在对于检查是可访问的。在这个阶段我们还需要决定我们想要察看类还是实例方法。用与类相同的方式给方法分类,我们现在必须选择一个种类。 这最终返回方法的一个列表,它现在可以被选择了 - 此后在下面的窗格中展示有关的代码。每个窗格都有自己的“黄色”按钮菜单,如下图展示的那样,提供各种选项。现在包含在底部窗格菜单中的选项可以应用到这 些代码的所有或任何选择的部分。
从上面的讨论看来浏览器提供了一个非常强有力的工具来查看和访问代码,因为它们使 Smalltalk 的所有东西对于检查和修改一律是可访问的。
让我们步入定义一个新应用程序的过程中。基于我们的领域,我们选择了 Sesame Street 的一个小片断, 它是一个众所周知的居住着许多非常奇异的人物的城市邻居关系。为了保持事物简单我们只集中于两类人物: Monster 和 CookieMonster。Monster 被用作一个抽象类,用来示范做子类。它描述对各种 monster 都共有的一些特征。CookieMonster 只是一种特殊情况,带有它们自己的特定怪癖。
基本上我们把 cookie monster 作为一种实体,它们不停的为了 cookie 而唠叨,cookie 是它们唯一的食物。一旦醒来立即饥饿只有吃饱才能使它们平静,此后它们将睡觉直到某个愚蠢的用户再次唤醒他们。为此 monster 被特征化为一个颜色(colour)和一个胃(tummy)。 因为 monster 有各种颜色,这个信息被存储为一个符号,并且在创建 monster 的时候装备了一个空胃,用叫做包(Bag)的一个预先定义的数据结构来表示。除了访问这两个实例变量, monster 拥有协议来吃和回答是否它们的胃是空的。有关的实例方法被分类为初始化、访问、查询和动作。 CookieMonster 进一步特殊化这个协议。不定义额外的类变量,但提供两个新实例变量,状态(state)和饥饿(hunger)。CookieMonster 的状态是 #awake 或 #asleep,而它的饥饿在它们醒来时随机决定。我们提供一个类方法来建立这样的畜生,和实例方法来初始化、查询、访问和动作。CookieMonster 与普通的 monster 在吃上有所不同,就是说,它们更加挑剔食物。普通 monster 的吃方法被 eat: 方法的一个新实现所摒弃。因为它们不是特别强壮它们还必须知道通过乞讨来祈求食物,而“唠叨”方法提供了这个功能。最后,还有方法用做在消化之前测试食物,和描述进行乞讨的特定方式。CookieMonster 察看这些信息为属性,并标签上私有。
要在 Squeak 中实现这个设想,我们首先做一个新的类种类 SesameStreet,增加 Monster 和 CookieMonster 类,并定义和测试所有方法。所有这些动作可以在一个浏览器中进行,而使用工作空间和一个新类型的视图叫做检查器来进行测试。图 3.15 展示了如何增加一个新类种类(就是通过在最左的窗格的黄色按钮菜单中选择“add item”并在回应的打开的一个查询框中键入一个字符串。
一旦向系统增加了新种类就可以定义类了。为此在浏览器底下的窗格中展示类定义的一个模板,用户可以用详细内容填充它。从编辑定义的菜单中选择 “accept”并把它增加到系统知道的类的字典中。注意有时需要通过在最左侧的浏览器窗格的黄色按钮的菜单中选择“update”来强制浏览器显示最新 的变更。
让我们假定已经成功的对两个 monster 进行了定义所有类和实例方法,并且这模型的状态反映在下图中,它还在浏览器的底部窗格中展示了 CookieMonster 的 isFull 方法的代码。
译注:出于方便余录入了的类定义,请使用文件列表的导入。
下图演示同时使用多于一个浏览器的可能性。实际上,在桌面上的视图数目只受到可利用的内存的限制。经常使用多个浏览器来同时察看代码的不同特征,排除经常来回滚动的需要。通过裁剪和粘贴,还可以在视图之间传送信息。在图中的第一个浏览器察看 Monster 当前定义的建立方法。我们需要重写 Object 的标准实例建立方法来确保我们的 monster 的胃和颜色被正确的初始化。注意向上箭头符号(“^”)指示新建造的 monster 将作为这个方法生成的值来返回。super 是两个所谓的“伪变量”之一(另一个是 self),它们提供到当前执行对象(通过 self)的句柄和它的超类的消息协议(通过 super)。“^ super new initialize”将强制发送 new 到类 Monster,随后是发送消息 initialize 到新建造的 monster,并接着返回这个实例,它有一般的 monster 颜色(就是绿色 :)和一个空包作为它的胃。第二个浏览器叫做类浏览器,它可以通过在系统浏览器的类窗格中访问的菜单选项中选择“browse class”来获得。类浏览器限制自己的观察范围为选择的类的协议(这里是类 Monster)而它们的黄色按钮菜单同于系统菜单的相应的窗格。在这个浏览器中展示的 eat: 方法获得到 monster 的胃的一个引用并向其中填充一些食物。因为 Smalltalk 是一个"动态类型"语言,你必须细心察看文档获知一种方法希望那种类型的对象作为它的参数。良好的 Smalltalk 风格为此使用前缀为“a”或“some”的(比如 “anInteger”、“someMonster”) 类型名字,而不是更少描述性的标识符如“x”、“y”或“fred”。这个命名参数约定使用这个方法接受的这些对象的最一般类的名字。如果我们不想做任何用途的假定可以使用 :“someObject”或“someItem”。尽管它只是一个约定而 Smalltalk 不强制使用它,这个方案很好的充任模拟“层级”类型系统的文档特征。
尽管 Monster 意图充当一个抽象类,我们可能希望在测试它的协议的过程中建立实例。一般的增加到类的所有方法在被定义之后都必须立即测试。工作空间和检查器支持这种活动。上图展示了如何计算一个表达式(键入到一个工作空间)来制作一个 monster 并喂它一个 #nail。要信服我们的表达式被实现了我们要检查最后的结果。这将使用一个所谓的检查器的视图,叫做“Monster”,在其中我们可以查看我们要研究的对象的当前绑定到所有实例变量上的值。可以安心的注意到 monster 的胃真的含有一个 #nail。可以以各种方式激发检查器。浏览器允许我们遍历过程的空间,而检查器允许我们遍历 Smalltalk 的数据空间。
我们现在要完成 cookie monster 的定义。走个捷径我们不详细追踪整个过程,但是要在测试 CookieMonster 的“顶层”行为的时候察看系统的状态。在一个真实的应用程序中最好在写每个方法的时候 测试它们,但是空间的现实使我们不能演示这个方式。下图展示了一个正确的脚本,弹出一个通知器来响应它,查询我们“Clara”的使用。因为这不是系统知道的一个全局标识符,它要求指导如何解释它。因为它应当充任类 CookieMonster 的一个新实例,我们确定了有关的选项。
下图在 CookieMonster 的 nag 方法上打开了一个浏览器并总结来 Clara 醒来之后展现的事件序列。象希望的那样,它立即开始向我们唠叨要 cookie。我们给了它一些,还故意的包含了一些其他的东西,它拒绝了不能消化的食物。
我们再次在检查器中察看这次盛宴的结果。Clara 的 hunger 必须被设置为 2 个 cookie。它满足了并快速睡着了。注意因为 Clara 是全局定义的,它的状态保持打开以便进一步检查而我们可以继续进一步研究 Clara 的胃,从相关的黄色按钮菜单上重复的选择"inspect"。这展现了充当 Clara 的胃的包被实现为一个字典,而食物‘cookie’出现了 2 次的事实被存储为与这个键关联的一个数。检查接着“摸底”到数的级别,因为数是文字并且不能进一步检查了。
在我们最后的图中浏览器底部展示的 nag 方法包含一个 whileTrue: 循环,通过在 cookie monster 的状态上的测试来守护它。在这个循环的内部它重复的要求一个‘cookie’。如果提供了一个,则吃掉它。拒绝任何其它的食物。一旦 monster 是“吃饱了”则终止这个循环,于是它就睡觉了。第四个浏览器窗格的菜单现在允许我们浏览所有发送者,所有实现者,还有到类和实例变量的所有引用。这个功能经常很有用; 例如,如果对方法头部的改变导致必需做消息发送到系统中其他什么地方的修改,则必须定位所有这些位置。
我们现在察看一下类 Monster 和 CookieMonster 的全部代码的总结。
类: Monster 超类: Object 类别: Sesame Street 实例变量: colour tummy
“这个 ABSTRACT 类实现一些普通结构和对不同类型的 monster 通用的行为。”
动作
eat: someItem self tummy add: someItem
查询
isEmpty ^ self tummy isNil
访问
colour ^ colour colour: aSymbol colour := aSymbol tummy ^ tummy tummy: aCollection tummy := aCollection
初始化
initialize self colour: #green. self tummy: Bag new
“这里只有一个单一的类方法并且没有类变量。”
建立
new ^ super new initialize
Cookie monster 继承自 Monster,但增加了一些它们自己的特殊行为。
类: CookieMonster 超类: Monster 类别: Sesame Street 实例变量: state hunger 私有
askForCookie ^ FillInTheBlank request: 'Give me cookie !!! (please)' complainAbout: anItem Transcript show: 'No want ', anItem printString. Transcript cr. self colour: #red isCookie: anItem " | serves as the OR operator" ^ ((anItem = 'cookie') | (anItem = #cookie))
动作
eat: aCookie super eat: aCookie. self colour: #green nag | item | [self isAwake] whileTrue: [item := self askForCookie. (self isCookie: item) ifTrue: [self eat: item] ifFalse: [self complainAbout: item]. (self isFull) ifTrue: [self sleep]] sleep self state: #asleep. self hunger: 0 wakeUp self tummy: Bag new. self state: #awake. self hunger: (Random new next * 13). "Cookie Monsters are superstitious and never eat more than 13 cookies in one go !" self nag
查询
isAsleep ^ state = #asleep isAwake ^ self isAsleep not isFull self isEmpty ifFalse: [^ self tummy size >= self hunger] ifTrue: [^false]
访问
hunger ^ hunger hunger: anIntegerNumberOfCookies hunger := anIntegerNumberOfCookies state ^ state state: aSymbol state := aSymbol
初始化
initialize self state: #asleep. self hunger: nil. super initialize
要确保正确的初始化,Monster creation 类方法也被重写。
建立
new ^ super new initialize
你将会注意到多数消息非常短。实际上,它们中的很大一部分由一行代码组成,返回 (^) 或赋值 (:=) 一些变量。这对于面向对象程序特别是 Smalltalk 代码是典型的,因为所有访问到变量的有效模式必须被明确定义。出于可靠性的利益,许多状态变量不应该从一个对象的外部是可访问的。Smalltalk 的编程环境便利了它的对到变量的任何访问都要求明确的方法定义的方式。因为浏览器允许快速的只用几下鼠标点击就定义这样的选择子,修改一个现存的方法,这么做不是特别烦人 - 并且对程序的可靠性有利。以传统的方式列出这样的方法将使代码非常混乱,但是浏览器简化了这种冗长的文档。
注意从浏览器的类分类窗格上附着的黄色按钮菜单上选择“fileOut”来保存在选择的类中的所有这些类定义到一个文本文件(在当前目录中),它们可以从一个文件列表“读回”到 Squeak (重编译每个定义)。