本章我们会开发一个简单的游戏:点图。随着开发流程我们会演示Squeak开发过程中的大部分工具,演示开发者之间如何交互。还会使用系统查看器,对象查看器,调试器,包管理器等工具。使用Smalltalk进行开发非常高效:其中大部分时间花在编写代码上,开发流程的管理交给Squeak自动完成。Smalltalk的语言简洁性和Squeak编程环境的丰富性。
2.1 点图游戏
通过开发一个简单的点图游戏说明如何使用Squeak的开发工具。这个游戏的面板如图。包含一系列的黄色块组成,点击其中的一个,周围的四个变成蓝色,再次点击,恢复到黄色。这个游戏的目的是尽可能多的转换为蓝色。
这个游戏包含两类对象:一个是盒子对象,一个是100个独立的块对象。因此在Squeak需要通过代码实现两个类,一个是游戏盒子,一个是块。接下来我们说明如使用Squeak的开发工具定义这两个类。
2.2 创建新的类分类
我们将会在第一章介绍的系统查看器的基础上,介绍如何定义新的类分类以及其中的类
Doit打开系统查看器,右击分类面板,选择add item
在打开的对话框中输入新的分类名字SBE-Quinto。然后点击accept
或者输入回车键。新的类分类创建。通常新的分类在最后,如何选择了一个已存在的分类,那么将会创建在选择的分类前面。
2.3 创建SEBCell类
这个新的分类中并没有包含任何Class。在下面的主编辑区域包含了创建类的模板可以用作类快速创建模板
快速浏览其中的代码,可以发现这个模板组织为发送消息给Object类,创建一个名为NameOfSubClass的子类。这个新的子类没有任何变量,并且属于SBE-Quinto类分类
我们就在这个模板的基础上进行修改创建我们需要的类
Doit 修改类创建模板
将Object名字替换为SimpleSwitchMorph
将NameOfSubClass替换为SBECell
添加mouseAction到类的实例变量中
修改结果如下
修改的代码包含一个Smalltalk表达式。发送消息给SimpleSwitchMorph类,要求创建一个子类SBECell。因为SBECell并不存在,需要传递一个参数#SBECell作为类的名字。
然后在新创建的类中包含一个mouseAction实例变量,将用来定义块应该采取的动作在受到点击的时候
当前只是输入代码,并没有编译生成任何类。需要使用accept
进行编译保存
Doit编译保存创建的类,
空白地方右击选择accept
。
创建成功后,新创建的类将会显示在系统查看器的第二栏。编辑区域显示类的定义,下面提示输入一些注释。
这个区域叫做类注释(class comment)。在大型项目开发中代码注释非常重要,Smalltalk高度重视代码的可读性和注释的详细。也就是说代码应该是自说明的。类的注释并不需要太详细的描述,仅仅需要一些用来说明这个类的用途。
Doit 对类进行注释
2.4 为类添加方法
接下来我们为新创建的类添加一些方法
Doit选择协议面板中的all。
在编辑主区域显示一个方法创建模板,选择编辑器区域,修改代码为下
Doit选择accept
保存创建的方法
让我们逐行解释这些代码的意义
这个方法名称是initialize.这个名称的意义非常重要。通常一个类定义个initizlize方法,在创建一个对象后用来初始化对象的数学。所以当执行SBECell new
后,将会发生initialize消息给类用来创建新的对象。初始化方法initialize将会用来初始化对象的状态,通常用来设置对象的实例变量,接下来我们将要说明。
这个方法中第一句就是执行父类SimpleSwitchMorph类的初始化方法,因此SimpleSwitchMorph中继承的属性将会被初始化。通常使用super initialize
来初始化继承的属性。我们在这里不知道SimpleSwitchMorph的初始化方法我们也不用关心,可以确定的是会初始化一些实例变量来保存默认值,因此最好首先调用父类初始化避免未知的变量
方法的其余语句用来设置对象的数学,self label:''
首先设置对象的label为空字符串
表达式0@0 corner:16@16
需要一些说明。0@0
表示一个xy坐标系的原点未知,事实上这个语句发送@0
消息到数字对象0,接下来数字0创建一个点Point类的实例(0,0)坐标。接着发送消息corner:16@16
用来创建以0@0和16@16的矩形。然后将这个矩形赋值给bounds
变量。这个变量是继承自父类。
需要注意的是Squeak屏幕原点在左上,y轴向下递减。
其余的语句也是用来设置属性。
2.5 探测对象
接下来创建一个对象来进行观察
Doit打开workspce。输入SBECell new
右击选择inspect it
。
打开一个查看器,显示了SBECell的实例变量列表,选择其中的一个如bounds。它的值将会显示在右侧面板。可以使用这个查看器修改实例变量的值
DOit 修改bounds的值为0@0 corner:16@16
然后保存accept
。
查看器下面是一个迷你workspace。经常用来对当前对象进行测试使用
Doit在其中输入se;f openInWorld
然后do it
接下来会在窗口创建一个块,中击显示器控制,缩放大小,观察查看器,可以看见bounds的值也会发生变化
2.6 定义SBEGame类
接下来我们创建另一个游戏类SBEGame
Doit使用系统查看器创建类SBEGame。
选择SBE-Quinto类分类,编辑类创建代码如下
代码意思创建BorderedMorph的子类。Morph是所有图形类的超级父类,而BorderedMorph类是带有边框的子类。
然后在SBEGame中定义一个初始化方法initialize
Doit选择all协议,输入下面的代码
选择accept it
的时候,Squeak会说明其中一些短语缺少定义,其中一个是cellsPerSide。提供了一些建议选项来修正拼写错误。
然而cellsPerSide并不是一个错误,仅仅是一个未定义的方法,接下来选择创建它。
Doit选择第一个选择,确认事宜cellSPerSide.
接着提示cells也是未知的,提供了修正选项
Doit选择declare instance
声明一个实例变量
接下来会说明NeCellAt:at:也是未知的,同样确认。
然后再次查看整个对象 会发现修改后的保护cells变量。
那么分析下initialize方法
第一行声明四个临时变量 sampleCell width height n
作用域与生命周期仅仅在这个方法,临时变量有助于代码的可读性。
smalltalk并不会区分常量与变量,事实上这四个变量是常量。接下来的几行定义了这些常量
那么board是多大。需要容纳足够的cells和边界框。那么其中包含多少个块呢,我们并不知道,因此需要使用cellsPerSide来。
这个方法会在后面进行定义,这种编写代码的方式是一种非常良好的方式,因为当我们进行初始化的时候才知道我们需要什么,然后我们给出有意义的方法名称,不需要打断我们的思路
接下来会获取cell的数量赋值给n.
接下来创建SBECell对象,赋值高度与宽度,
接下来设置buonds属性。
最后实则cells变量属性,使用Matrix。
需要使用两个参数i j。
创建一个nxn矩阵初始化元素。初始化值依赖于坐标信息。
接受到初始化方法的时候,需要抓住机会格式化输出。选择more...>prettyprint
会进行自动排版,需要再次保存accept
2.7 保存方法到协议中
让我们快速浏览下第三个面板。正如第一个面板用来保存类分类,第三个面板保存方法分类的协议。
如果只有少量的方法,不需要使用特定协议,这也是为什么仅仅提供all协议,
Doit有偶记选择categorize all uncategorized
来修正,移动初始化方法到初始化协议中
Squeak如何识别合适的协议接口?通常Squeak并不会进行识别,但是这种情况initialize是一个父类的方法,假设我们的初始化方法应该属于父类的同样协议接口类
可以发现Squeak会自动将initialize方法归类到initialization协议,
smalltalk使用>>来定义一个方法所属的类,可以使用SBEGame>>cellsPerSide。
从现在开始,我们将会使用这种方法说明类的方法,
使用SBEGANME>>initialize方法定义方法
依次定义cellsPerSide ,newCellAt ,toggleNeighboursOfCellAt等方法
Doit将最后一个方法拖放到game logic协议中
2.8 试试运行
到此完成所有代码编写
包含两个类和7个方法
Doit 在workspace中输入SBEGame new openInWorld
,然后选择do it
也许可能出现一个错误提示窗口,
这是需要调试器进行修正
Doit点击debug
按钮
出现调试器界面,顶层窗口输出运行栈,显示所有调用的方法。
选择其中的一个,将会显示在中间。
其中错误的代码会高亮显示
Doit选择其中的第二条语句
调试器会显示运行的上下文如下。
调试器的下方是两个探测窗口,左侧显示接受消息的对象,可以查看对象的各个实例变量的值
右侧查看对象执行当前方法的状态,可以查找方法的参数值与临时变量
使用调试,可以逐行运行代码,查看对象的形参与临时变量,最为惊奇的是可以在调试过程中修改代码。因此编程过程在调试中完成。这种调试方式可以查看编写方法特定运行上下文与形参的运行状态,
分析代码中的bug。