|
Abstract 上世纪70年代诞生的Smalltalk,其历史已超过30多年。在经历了软件产业 化、商业化的浮云变换之后,Smalltalk的开源实现Squeak,回归了 学者们的最初目标:儿童教育。Seaside是一个优雅、强大的Web开 发框架,由Smalltalk写成,支持Ajax等时髦技术。它如同30多年前 的故人漫步于海滨时的即兴之作。本文通过一个实际的例子,简单地 对Seaside和Smalltalk做一些介绍。希望能够抛砖引玉,对读者有所裨 益。 本文使用LATEX2ɛ排版,遵循FDL(GNU Free Documentation License)许可 协议。并受其保护。具体请参考:http://www.gnu.org/copyleft/fdl.html 由于作 者水平有限,文中难免有谬误之处,欢迎广大读者指正! |
Keywords: Smalltalk, Squeak, Seaside, Web App
Created: 2007 Septempber 2008 Feburary.
1 Introduction
十年磨一剑,霜刃未曾试。
今日把示君,谁有不平事?
–唐,贾岛《剑客》
世上最难的事情之一便是“坚持”,并且说来容易,实践起来却非常难。我曾经 试图每个月写一篇软件开发的小文,以C++为主,偶尔涉及lisp。坚持了1年半,就中 断了。反省下来,一个原因就是忙。以前工作比较简单,写好程序就成了。工作之 余,总还能写些不通的文章。现在终日忙于管理和组织上的大量事务,其中最繁琐耗 时的一项工作就是任务管理了。
每个周五下班前,就是众多工程师和经理们最头疼的工作–Weekly Report时间 了。我会收到每个人发来的一份Excel表格,其中列出了工程师这一周所接手的所有任 务,包括任务的编号,所属业务,具体类别,重要程度,状态,接手时间,花费的时 间等等信息。
我需要汇总、核对所有的周报,以形成一个项目的Weekly report。其中一些数 据,要非常精确,例如人员的工作时间,如果一个工程师在本周没有休假,他一 周应该工作40小时。到了月底,还要根据各周的数据,形成月报(Monthly report)。
月报中统计更为繁复,需要统计每个业务在所花费的时间;以及一个业 务中,每类工作所花费的时间;诸多任务中,有多少被完成了,多少不了 了之了,平均完成一个任务花费多长时间,哪类工作的平均耗时如何,等 等。
这个任务,全部使用Excel手工来完成,虽然我不断改进Excel加入了宏等自动计算 工具。可是这个工作,仍然要消耗大量时间。并且随着项目人员的增加,工作量越来 越大,不仅彻底粉碎了我每月写东西的癖好,甚至影响了我的正常开发工作和业余的 休息时间。
所以,我决心开发一个工具,把自己解放出来。
2 Why Seaside?
这样一个项目任务管理的工具,应该具备哪些功能呢?这是一个软件需求问题,客户 就是我自己本人。所以可以明确列出这些要求:
- 提供一个网络使用界面,给工程师填写周报,这样工程师就不用每周五 发电子邮件;
- 可以根据各个工程师的输入,每周自动汇总出项目周报;
- 可以统计每个月中各周的结果,自动形成月报;
- 工程师和项目负责人都可以随时检察以前的结果,修改任何错误。
- 具备用户管理功能,工程师甲不能随意察看更改工程师乙的工作报告。 管理员可以察看更改任何人的工作报告。
针对这样的需求,一个明显的解决方案就是数据库结合Web应用。实现这样 的Web应用也有多种选择:
- 使用、改进已有的类似工具。诸如Mantis等bug管理工具,就部份符合要 求;
- 使用Java或者dotNet开发;
- 使用Php/MySQL自行开发;
- 使用Ruby On Rails或者Python自行开发;
经过分析,风险最小,见效最快,成本最低的是方案1,将所有人员的工作任务当 作Bug处理,就可以实现对任务的录入,统计的追踪。但是大多数Bug tracking工具, 缺乏必需的周报和月报功能。因此只能进行改动,这样需要了解这些工具的源代码, 然后动手修改。Mantis 使用PHP/MySQL实现,作者曾经在98 99年间用Perl写过一 些CGI应用,近两年仅仅使用Perl 做过一些统计数据的脚本。综合考虑全 部工作量,包括分析代码,学习PHP,以及改动、调试,方案1和3并不理 想。
方案2是业界在解决企业Web应用时的流行方案。Java,主要是J2EE在解决此问题 时,过于重量。并且我也不打算花费重金购买数据库或dotNet开发工具,因此方 案2也不适合。
方案4比较有趣,并且是近来的流行技术。开发过程应该也是轻量敏捷的。但是这 两门语言对我来说都不熟悉,同样面临一定的学习曲线。因此我需要和最后的备选方 案Seaside/Smalltalk 进行对比。
本文并不针对Smalltalk以及Ruby与Python进行语言层面的对比。这样 会引发大量的问题和争论。我个人是带有倾向性的,因为我来自C++, 沿着C++向前追溯,有两条脉络[1]。一个是泛型(GP)层面,可以追溯到 最早的函数式语言lisp;另一个是面向对象层面,可以追溯到最早的面向 对象语言Smalltalk。我希望通过这个小工具的开发,深入了解Smalltalk, 所以,只需要确认,Smalltalk能够以非常低的代价满足全部需要就可以 了。
下面列举了一些开发层面上的需求:
- 是否容易获取:上世纪80年代到90年代,Smalltalk价格昂贵,无法为个 人所承受。现在Smalltalk已经存在开源实现Squeak;
- 学习曲线:Smalltalk最初为儿童设计,易于学习。10至14岁儿童就可以 掌握;
- 是否支持Web开发和数据库:Squeak中开发Web可以使用Seaside,Web Server内置,数据库可以使用Magma对象数据库;Seaside和Magma全部 用Smalltalk写成,不涉及任何第三方工具。
- 是否有足够的支持:技术问题可以得到Squeak社群的回答,问题平均响 应时间为半天。存在免费的书籍SBE[2][3]和Seaside tutorial[4]。Squeak全 部代码开源,内置重构浏览器,对象浏览器,方法查询器,Monticello版 本管理工具,Sunit自动化测试框架等工具。
- 兼容性和跨平台:Smalltalk在上世纪70年代就使用虚拟机,全部系统 映像跨平台,可以业余时间在家用机的Windows上开发,然后在公司 的Linux服务器上投入运行。
综合上述原因,作者选用Seaside on Squeak开发,开发非常零散分布于业余时间 内,大约每周半小时至2小时不等,持续从2007年9月到2008年1月共5个月,总共时间 约为40小时。
3 Hello World
由于这一开发过程,也是学习过程,所以作者不同于以往的文章中采用TDD式的过 程。本文采用的方法,是从一个HelloWorld式的WebApp开始,逐渐重构,最终完成 一个功能完备的项目任务管理Web工具。
在开始写第一个Hello World前,先需要准备开发环境,带有Web开发功能 的Squeak+Seaside 可以从Seaside网站下载。www.seaside.st。该zip包适用 于Windows, Mac和Linux平台。各平台都有一个对应的执行程序,无需进行任何安 装。
Squeak基本类似一个独立的操作系统,带有内核,进程调度器,垃圾回收 器,GUI界面和各种开发工具。一些基本的背景知识和操作可以参见SBE的第一章, 本文不再赘述。
Hello World程序就是一个类,打开重构浏览器,创建一个新类,HelloWorld如 下:
| 1cAp1x1-30003: | HelloWorld definition |
WAComponent subclass: #HelloWorld
instanceVariableNames: ’’
classVariableNames: ’’
poolDictionaries: ’’
category: ’MyLearning’
该类从Seaside的组件类WAComponent继承,名叫HelloWorld,属于MyLearning软件 包。Squeak提供了新类模板,实际上只要填写父类名称,新类名称和所属软件包名称 就可以了。
然后在HelloWorld类中,重载父类的renderContentOn:方法如下:
| 1cAp2x1-30003: | in HelloWorld |
renderContentOn: html
html text: ’Hello World!’
下面就要启动Komanche Web服务器,并把HelloWorld服务打开。在Squeak中打开 一个Workspace 输入并运行(do it)下面的Smalltalk语句:
| 1cAp3x1-30003: | in Workspace |
WAKom startOn: 8080.
HelloWorld registerAsApplication: ’hello’
其中第一句是对着WAKom类调用类方 法1startOn:并 传入参数8080。其含意是启动服务器,并在8080端口监听。在Smalltalk中,一般称调 用函数为“发送消息”,本文后面部份将适用发送消息这一说法。接着,第二 句话,向HelloWorld类发送registerAsApplication:消息,并传入字符串参 数’hello’。其含意是将HelloWorld作为一个名叫hello的服务注册给Web服务 器。
接下来,就可以测试这个HelloWorld了,打开Web浏览器(firefox或者ie),输入 如下的地址:http://localhost:8080/seaside/hello 浏览器就会显示:Hello World!
略微解释一下HelloWorld的原理。它什么都没有做,只是重载了renderContentOn:方 法。根据面向对象的多态原理,当浏览器向Web服务器请求hello服务 时,Komanche服务器,就找到HelloWorld类,创建一个该类对象,并调 用renderContentOn:方法并传入一个html对象作为参数。在重载的方法内部,我们 调用html的text方法,并发送一个字符串’Hello World!’ 来显示一个文本内 容。
4 Interaction
使用Seaside,不需要任何HTML知识,就可以创建Web应用。工程师可以把注意力集 中在程序,设计和算法上面,唯一需要掌握的工具就是Smalltalk语言本 身。
前面的HelloWorld只能显示,还不能和用户交互,下面我们稍做改动, 将HelloWorld变成一个网页计数器。
改动的思路如下,给HelloWorld类增加一个成员变量counter,初始化时设置为0,在 网页上显示一个链结,用户每点击一次链结,就将counter的值增加1,这样counter就纪 录了用户点击该链结的次数。
实现如下,首先是增加成员变量:
| 1cAp4x1-40004: | HelloWorld definition |
WAComponent subclass: #HelloWorld
instanceVariableNames: ’counter’
classVariableNames: ’’
poolDictionaries: ’’
category: ’MyLearning’
然后重载HelloWorld的初始化函数initialize
| 1cAp5x1-40004: | in HelloWorld |
initialize
super initialize.
counter:=0
注意super是一个关键字,它总指向当前类的父类。这里必需先调用父类 的初始化方法。否则,由于多态的原因,父类的initialize函数不会得到调 用2。Smalltalk中 的赋值号不是等号,类似于Pascal语言。另外下划线在Smalltalk中相当于:=,有时被 显示为左箭头。
为了能够增加counter纪录值,还需要给HelloWorld添加一个方法increase:
| 1cAp6x1-40004: | in HelloWorld |
increase
counter := counter + 1
最后,需要改动renderContentOn:增加一个计数器链接:
| 1cAp7x1-40004: | in HelloWorld |
renderContentOn: html
html text: ’Hello World! counter=’;
text: counter printString;
break.
html anchor
callback: [self increase];
with: ’counter++’
这个函数一共有2句话,第一句话首先调用text:方法,显示’Hello World! counter=’ 这一字符串,然后使用一个分号,继续向html对象 发送text:消息,显示内容为counter的值。由于counter在初始化时被指 向3一 个数字对象,所以向counter发送pringString 方法,将其转换为字符串。最后再利用 一个分号,继续向html对象发送break方法,实现对文本的换行。全句以句号结 尾。
第二句话调用html的anchor方法,以构造一个超链接。当用户 点击该链接时,就调用callback:方法传入的参数。该参数是一个语句 块4。 所以用户点击后,self increase语句就会被调用。其中self是关键字,总指向对象自 己。相当于C++ 中的this。这个超链接所显示的内容是字符串’counter++’。
打开Web浏览器再次访问http://localhost:8080/seaside/hello,就会显示如下内容 的网页:
Hello World! counter=0
counter++
此时如果点击counter++的链接,就会显示为:
Hello World! counter=1
counter++
为了进一步增强交互效果,还可以获取用户输入,下面再次改动HelloWorld,使得 用户可以输入counter的初始值。
counter是类内部的成员变量,为了访问它,需要增加一对setter/getter方 法5。 在重构浏览器中,右击HelloWorld类,选择‘重构成员变量’,再选择‘增 加accessor’,就可以自动为counter增加一对访问函数,增加后的代码如 下:
| 1cAp8x1-40004: | in HelloWorld |
counter
↑ counter
counter: anObject
counter := anObject
其中的上箭头,就是∧键,表示返回,相当于C++中的return关键字。然后修 改renderContentOn:方法,增加一个输入框:
| 1cAp9x1-40004: | in HelloWorld |
renderContentOn: html
html form: [
html text: ’Hello World! counter=’.
html textInput on: #counter of: self.
html submitButton
callback: [self inform: ’conter changed to:’,
counter printString ];
value: ’Set’].
html anchor
callback: [self increase];
with: ’counter++’
网页中的绝大部份输入输入控件,如文本框,下拉框,按钮等等,都需要放置 在Form中。对html对象发送textInput消息,就会产生一个文本输入框对象;再向文 本框发送on:of: 消息,就会将文本输入的动作和一个对象的某对accessor函数关联起 来。其中on:后面的参数是accessor的名字,名字前面的#号表示,counter是函数名 称6。 接着,程序又通过向html对象发送submitButton消息,产生了一个网页按钮。该按钮 显示的内容,由value:方法设置,所以会显示一个’Set’字符串。按下按钮就会调 用callback:后面传入的语句块。该语句块的内容就是,调用HelloWorld自己 的inform方法,在网页上显示counter被设置成的新值。
在Web浏览器中访问hello会显示如下内容:
在输入框中输入一个数字后按Set,网页显示内容就变为”counter变化为新值。 “按下确定后会返回上图所示页面,再次点击counter++后,counter的值就从这个新 值开始加1.
5 MVC
Seaside将Web开发变成和普通应用程序开发基本一样,所以,就可以将面向对象中 的MVC 引入Web开发。Smalltalk的MVC非常著名,以至于在GoF的Design Pattern一书中开篇就加以介绍[5]。
针对本文中项目任务管理的例子,可以先部分实现通过网络Web界面,编辑、显示 一条任务(task)的功能。其中,用于显示的类为viewer,保存task内容的类为model,控 制部分为controller。
第一步是先给task建模。可以通过增加一个新类来代表task。
| 2cAp1x1-50005: | definition of MyTaskItem |
Object subclass: #MyTaskItem
instanceVariableNames: ’id component description state owner startDate endDate’
classVariableNames: ’’
poolDictionaries: ’’
category: ’MyLearning’
其中各个成员变量的含义基本如名称所示,id表示该任务的编号,component表示 这个任务属于那个组件,如IO或者GUI等等,description是一段对该任务的简单描 述,如“编写文件输入代码”等,state表示该任务目前的状态,例如完成,处理中, 暂停等等,owner是负责这个任务的工程师姓名,startDate是接受这个任务的时 间,endData是完成时间。
定义好这个类后,可以使用重构浏览器,自动为每个成员生成一对accessor。
第二步是建立viewer类,这个类中,提供编辑所有model的界面。一个比价好 的方法是重构前面的HelloWorld类。使用重构浏览器将HelloWorld类改名 为MyTaskEditor,然后删除counter成员及其accessor。增加一model成员,然后重 写renderContentOn:方法。
| 2cAp2x1-50005: | in MyTaskEditor |
renderContentOn: html
html table attributeAt: ’border’ put: 1; with: [
self renderTableHeaderOn: html.
html form: [
html tableData:[html textInput on: #id of: model].
html tableData: [
html select list: #(#IO #GUI #Engine );
on: #component of: model].
html tableData: [
html textArea on: #description of: model].
html tableData: [
html select list: #(#ongoing #complete #pending);
on: #state of: model].
html tableData: [
html select list: #(#ZhangSan #LiSi #WangWu);
on: #owner of: model].
html tableData: [
html dateInput
callback: [:v | model startDate: v];
value: model startDate].
html tableData: [
html dateInput
callback: [:v | model endDate: v];
value: model endDate].
html tableData: [
html submitButton
callback: [];
value: ’Save’]]]
和前面HelloWord相比,这段代码虽然长度增加,但是并没有本质不同。为了页面 美观,这里使用了网页中的表格。通过atrributeAt:put:方法,将表格的边框宽度设置 为1。然后使用with:方法,设置表格中的内容。先跳过renderTableHeaderOn:方法, 一会作者将通过重构浏览器定义它。后面的代码中,作者使用了多种网页控件,包括 下拉选择框,多行文本编辑器,日期控件,按钮等等。其使用方法也很直 观,基本是通过某个对象(这里是model),和一对accessor实现读取和记 录。
有一些语法需要略微说明一下,Smalltalk中使用井号开头的括号来定义常量数 组,例如#( 1, 2, 3)代表整数数组1, 2, 3。而井号开头的英文单词串表示常量符号,所 以上面的下拉列表的选项用常量符号数组来初始化。
用户按下Save后的内容,暂时还空着没有写。我们在后面实现它。
现在需要定义renderTableHeaderOn:消息。内容非常简单,其目的仅仅是为了避免 过长的函数体。其实现如下:
| 2cAp3x1-50005: | in MyTaskEditor |
renderTableHeaderOn: html
html tableRow:
[html tableHeading: ’ID’.
html tableHeading: ’Component’.
html tableHeading: ’Description’.
html tableHeading: ’State’.
html tableHeading: ’Owner’.
html tableHeading: ’Start Date’.
html tableHeading: ’End Date’.
html tableHeading: ’Action’].
最后的步骤是建立Controller类,MyTaskController,其拥有两个成员变 量model和viewer,在Controller的初始化的时候,同时初始化model和controller,并 使它们建立关联。
MyTaskController定义如下:
| 2cAp4x1-50005: | definition of MyTaskController |
WAComponent subclass: #MyTaskController
instanceVariableNames: ’model viewer’
classVariableNames: ’’
poolDictionaries: ’’
category: ’MyLearning’
这个类也是从WACmoponent派生来,所以可以直接通过网页控制。其初始化方法 实现为:
| 2cAp5x1-50005: | in MyTaskController |
initialize
super initialize.
model := MyTaskItem new.
viewer := MyTaskEditor new.
viewer model: model.
而Controller的renderContentOn:被实现为一个超链接,用户点击后,就会显示编 辑task的界面:
| 2cAp6x1-50005: | in MyTaskController |
renderContentOn: html
(html anchor)
callback: [self call: viewer];
with: ’edit task’
现在,只要将controller注册给seaside就可以测试这个MVC架构的Web程序了。打 开workspace,输入下面语句,并“do it”。
| 2cAp7x1-50005: | in Workspace |
MyTaskController registerAsApplication: ’mytask’.
之后打开浏览器,访问http://localhost:8080/seaside/mytask 就出现一个 网页,显示一超链接“edit task”,点击该task,就可以显示下图所示界 面:
现在点击Save还没有任何效果,我们其实已经可以在viewer中,通知controller 做 进一步的操作了。
6 More MVC
我们在这一节将做进一步的重构,以达到管理多个task的功能。上一节的成果,可以 作为一个可复用的程序,多个这样的程序聚合在一起,就成了一个管理task list的程 序。
第一步将创建一个新类MyTaskList,该类拥有一个MyTaskController的数组。它 可以将该数组中的所有task一条一条按照表格显示出来。最后显示一行用于增加 新task的MyTaskEditor。当用户点击“Save”按钮后,就通知MyTaskList将新Task加 入到数组中。
MyTaskList类的定义如下:
| 3cAp1x1-60006: | definition of MyTaskList |
WAComponent subclass: #MyTaskList
instanceVariableNames: ’controllers newTaskController’
classVariableNames: ’’
poolDictionaries: ’’
category: ’MyLearning’
该类有两个成员变量,一个是controllers列表(数组),所有加入的task都记录在 该列表中,它初始化为空数组。newTaskController是一个任务controller,它专门用于 用户输入新任务。
MyTaskList的初始化函数中,需要做的,就是对这两个成员变量初始 化。
| 3cAp2x1-60006: | in MyTaskList |
initialize
super initialize.
controllers := OrderedCollection new.
newTaskController := MyTaskController new initialize; observer: self.
其中,将任务列表controllers初始化为一个有序集(一种比数组更加抽象的容器)。 然后将newTaskController初始化好,这里作者使用了Observer模式,将任务列表,设 置成新任务编辑器的观察者。这样,一旦用户按下保存新任务的按钮,任务列表就会 被通知,以便将新任务存入controller列表中。我们将在后面介绍该controller作为被观 察者的具体实现。
当任务列表MyTaskList得到通知时,它的响应如下:
| 3cAp3x1-60006: | in MyTaskList |
notify: aTaskItem
controllers add: (MyTaskController new initialize; model: aTaskItem; observer: self).
newTaskController initialize.
该段代码会根据传入的新任务,创建一个任务controller然后将该任务设置 为model,并将MyTaskList设置为观察者。最后,再将新任务编辑器重新初始化清 空。
Seaside支持符合网页组件,也就是一个WAComponent中可以在内嵌多个 子WAComponent,但是,需要重载一个额外的成员函数childern这个函数只要返回所 有内嵌子WAComponent的列表(数组)就可以了。MyTaskList中childern实现如 下:
| 3cAp4x1-60006: | in MyTaskList |
children
|array|
array := OrderedCollection new.
controllers do: [:x | array add: x viewer].
array add: newTaskController viewer.
↑array
该函数的意图就是将所有任务的controller和添加新任务的放到一 个OrderedCollection中,然后返回这个数组。
通过使用这个childern复合Web组件的显示函数就可以简单地实现为:
| 3cAp5x1-60006: | in MyTaskList |
renderContentOn: html
html table attributeAt: ’border’ put: 1;
with: [
self renderTableHeaderOn: html.
controllers do: [:x | html tableRow: [html render: x viewer]].
html tableRow: [html render: newTaskController viewer]]
其中的renderTableHeaderOn:已经被通过重构浏览器从原先的MyTaskEdtior移动 到MyTaskList类中。表头显示完毕后,程序遍历controllers列表,对每个元素,将其 放置在表格的一行中,然后通过调用html对象的render:方法,让每个viewer自己显示 到网页中。
由于MyTaskList是观察者,MyTaskController被设计成被观察者,为此需要略加 改动,在类定义中加入一个成员变量,用以通知观察者:
| 3cAp6x1-60006: | definition of MyTaskController |
WAComponent subclass: #MyTaskController
instanceVariableNames: ’model viewer observer’
...
然后可以借助重构浏览器,为观察者添加一对accessor方法。为了进一步降 低MVC三者之间的偶和,我们删除掉viewer中的model成员,而改为增加一 个controller成员以及它的一对accessor。viewer不再直接访问model,而必需通 过controller来访问。如下:
| 3cAp7x1-60006: | in MyTaskEditor |
model
↑self controller model
同样,MyTaskController类的初始化函数被重构为:
| 3cAp8x1-60006: | in MyTaskController |
initialize
super initialize.
model := MyTaskItem new.
viewer := MyTaskEditor new controller: self.
最关键的改动在MyTaskEditor的显示部份,当用户点击save按钮时,viewer通 过controller通知observer进行动作,新task被加入列表中。
| 3cAp9x1-60006: | in MyTaskEditor |
renderContentOn: html
html form: [
html tableData: [html textInput on: #id of: (self model)].
...
...
html tableData: [html submitButton
callback: [self controller observer notify: self model];
value: ’Save’]]
经过这些改动,使用MVC结构的任务列表管理程序就可以初步使用了,通过浏览 器访问后,网页初始界面如图3。
填入一些信息后,点击Save按钮,新任务就会被加入,连续加入任务后就会出现 一个列表,如下图。
7 Database
上述程序有一个很大的问题,就是各种信息不能保存下来,所有的内容都暂时存储 在MyTaskList 对象中,随着网页访问结束,这个对象被销毁。信息也就全部丢失 了。为此必需把信息保存在存储介质上。有多种方法可以达到这个目的。经过作者实 验,Magma数据库是最为简单可靠的一种。
Magma数据库也是纯用Smalltalk写成的对象数据库,使用者不用学习SQL,就可 以使用。在Squeak 中安装Magma数据库也很方便,可以参考[4]中的persistence一章 的内容。笔者在实际安装中由于版本问题遇到一些实际困难,解决方法如下。 首先去http://www.squeaksource.com/Magma/下载全部文件到seaside下 的package-cache目录。改目录是Monticello版本管理工具目录,第一次运 行Monticello后就会自动生成。
将magma tester的mcm文件拖拽进入Squeak就会显示该文件的内容。然后打 开Monticello,选中package-cache这个本底的Repository并打开,按照mcm中的 内容,一个一个将安装包装入squeak,Magma对象数据库就可以正确安 装。
无论多少用户同时访问网页,它所访问的数据库只有一个,所以数据库控制类明 显可以做成singleton。在Smalltalk中,非常容易实现,首先需要定义一个类成 员Instance如下:
| 4cAp1x1-70007: | definition of MagmaTaskDatabase |
AbstractTaskDatabase subclass: #MagmaTaskDatabase
instanceVariableNames: ’session’
classVariableNames: ’Instance’
poolDictionaries: ’’
category: ’MyLearning’
然后,在该类的类一侧,添加方法(类似于C++中的static方法),如下
| 4cAp2x1-70007: | in MagmaTaskDatabase class |
instance
Instance ifNil: [Instance := super new. Instance initialize.].
↑Instance
而在该类的对象一侧,初始化方法定义为在数据库中创建task列表
| 4cAp3x1-70007: | in MagmaTaskDatabase |
initialize
self tasks ifNil:[self createTasks].
createTasks
self session
commit: [self session root at: ’tasks’ put: OrderedCollection new]
有关Session的打开、关闭,数据库的初始化,请读者参阅[4]这里不再赘 述。
而添加一个新task就被重构为,通过数据库的唯一实例,进行添加,如下面的代码 片断。
| 4cAp4x1-70007: | in MyTaskList |
notify: aTaskItem
MagmaTaskDatabase instance add: aTaskItem.
...
其中MagmaTaskDatabase中的add:方法实现为:
| 4cAp5x1-70007: | in MagmaTaskDatabase |
add: item
self session commit: [self tasks add: item]
8 Furthur works
本文至此,基本已经实现了一个基于Web和数据库的简单应用,当然还有一些细节没 有介绍,在此,作者一一列出,并给出实现思路,具体请读者参考和本文一起的源代 码。
- 每项任务的修改。前面的例子程序没有提供修改功能,任务只能被添 加。为了加入修改功能,有2个方法,一个是给controller增加一个状态成 员变量isAdding,如果为真,则notify的时候进行增加,如果为假,则进 行更新。第二个方法是增加一个viewer,专门显示task,task的id链接到 一个专用的修改界面,然后通知controller修改。
- 用户管理,这一点是需求5中提出的,经过分析可以发现,工程师 和task具有很大的相似性,用户和用户列表也可以被增加,编辑。 所以task的代码可以大部份复用。在面向对象的解决方案中,就是 将task和user的共同点抽象成基类,然后各自派生出不同的特点。这些 在Smalltalk和重构浏览器的帮助下,可以非常容易地实现。
- 统计功能,所有的数据都可以从数据库中得到,所以统计工作也可以简 单地实现。
- 安全登录,Seaside提供session支持。具体请参考[4]。
- Ajax,Seaside提供Ajax支持,不需要额外使用Javascript和XML。
作者开发的简单任务管理工具,已经实验性地在实际项目中进行尝试。希望本文 能够为那些准备利用Web进行开发的读者提供另外一种选择,在不需要掌握额外 的HTML,CSS,XML数据库等技能的前提下,快速轻量地开发网络应用。
References
[1] Liu Xinyu. “溯 源” http://baredog.at.infoseek.co.jp/intl/chn/softdev/book1/essay7.htm
[2] Alec Sharp. “Smalltalk By Example.” McGraw-Hill, 1997. PDF version. http://www.iam.unibe.ch/ ducasse/WebPages/FreeBooks.html
[3] Andrew P.Black, St閜hane Ducasse, Oscar Nierstrasz, Damien Pollet, with Damien Cassou and Marcus Denker. “Squeak By Example.” www.squeakbyexample.org
[4] Software Architecture Group Hasso-Plattner-Institut (Michael Perscheid, Martin Beck, Stefan Berger, Peter Osburg, Michael Haupt, Robert Hirschfeld). “Seaside tutorial”. http://www.hpi.uni-potsdam.de/swa/????
[5] Gamma, Erich; Richard Helm, Ralph Johnson, and John Vlissides. “Design Patterns: Elements of Reusable Object-Oriented Software.”, Addison-Wesley. 1995.
*题目借用自庐隐女士作品的名字
†Liu Xinyu
5-2-201, ShiZiPo, Xi, DongZhiMenWai, DongCheng district, Beijing, 100027,
P.R.China
Email: liuxinyu95@gmail.com
Tel: +86-1305-196-8666
Fax: N.A.
1 Smalltalk中的类方法相当于C++或Java中的静态类方法
2 Smalltalk中的所有方法一视同仁,不同于C++中存在的构造函数会自动调用父类构造函数的情 况
3 Smalltalk中的变量,类似于Java中的非primitive变量,可以理解为指向对象的指 针
4 用方括弧括起来的若干Smalltalk语句称为block,它也是对象
5 Smalltalk中没有“公有”、”私有“,”保护“等概念,可以理解为,所有的变量都是私有 的,所有的函数都是公有的。
6 可以类比为C++中的成员函数指针


