要开始使用Pharo,让我们按照下面给出的步骤编写一个简单的计数器。在本练习中,您将学习如何创建包、类、方法、实例、单元测试等。本教程涵盖了在Pharo中开发时将执行的大多数重要操作。你也可以在 http://mooc.pharo.org 上观看Pharo MOOC的配套视频,这些视频有助于讲解本教程。
注意,这个小教程提倡的开发流程是传统的,即定义一个包、一个类,然后定义它的实例变量,然后定义它的方法,最后执行它。现在在Pharo中,开发人员通常遵循不同的工作流,称之为测试驱动开发(就像我们在上一章中看到的那样):他们执行一个会引发错误的表达式。这些错误由调试器和开发人员直接在调试器中编码捕获,允许系统动态地为它们定义实例变量和方法。
我们还将向您展示如何使用Iceberg保存git托管服务(如GitHub)的代码。
一旦您完成本教程,并且对Pharo更加有信心,我们强烈建议您再次使用TDD进行练习。还有一个视频展示了这种强大的编码方法。
下面是我们的用例:我们希望能够创建一个计数器,让它递增两次,递减一次,再检查它的值是否与预期的一样。下面的例子展示了这一点,并将创建一个完美的单元测试——稍后您将定义一个单元测试。
| counter |
counter := Counter new.
counter increment; increment.
counter decrement.
counter count = 1
我们将编写所有必要的类和方法来支持这个示例。
在这个部分,您将创建第一个类。在Pharo中,类是在包中定义的,因此我们首先需要创建一个包来容纳类。每次创建类的步骤都是相同的,所以请注意。
创建一个包
使用浏览器创建一个包(在包窗格中右键单击,选择New package)。系统会询问你包的名字,输入MyCounter
。然后创建这个新的包,将其添加到包列表中,并在默认情况下已选中。预期结果如图5-1所示。
图5-1 包已创建,创建类的模板
创建类
浏览器的下部窗格现在应该打开,并显示一个类定义模板的选项卡。要创建一个新类,您只需要编辑这个模板并编译这个类。这里有五个部分你可能需要改变:
超类. 这描述了您正在创建的类的超类。它默认为Object
,这是Pharo中所有类中最不专门化的类,这是我们想要的Counter
类的超类。但情况并非总是如此:通常情况下,您可能希望将一个类建立在更具体的类上面。
类名. 接下来,您应该填写类的名称,将#MyClass
替换为#Counter
。注意类的名称以大写字母开头,并且不要删除#Counter
前面的#
符号。这是因为我们使用Symbol
来命名类,在Pharo中,以#
开头的字符串表示该字符串是独一无二的。
实例变量. 然后,您应该在instanceVariableNames
旁边填写该类的实例变量名。我们只需要一个名为count
的实例变量。注意要写在单引号里面!
类变量. 它们被声明在classVariableNames:
;确保它是一个空字符串,因为我们不需要任何类变量。
你应该得到以下类定义:
Object subclass: #Counter
instanceVariableNames: 'count'
classVariableNames: ''
package: 'MyCounter'
现在我们有了Counter
类的定义。要在我们的系统中定义它,我们仍然需要编译它——要么通过下面板的上下文菜单,要么通过快捷键Cmd-S。Counter
类现在被编译并立即添加到系统中。
图5-2说明了浏览器应该显示的结果情况。
图5-2 类已创建:它继承了Object
类,并有一个名为count
的实例变量
Pharo代码评估工具将自动运行并显示一些错误;现在不要担心它们,它们主要是关于我们的类还没有被使用。
作为有纪律的开发人员,我们将通过单击comment窗格和Toggle Edit / View注释开关向Counter
类添加注释。你可以写下面的注释:
`Counter` is a simple concrete class which supports incrementing and
decrementing.
Its API is
- `decrement` and `increment`
- `count`
Its creation message is `startAt:`
注释是用Microdown语法编写的,这是Markdown的一种方言,应该非常直观。它们能够很好地呈现在浏览器中。同样,通过菜单或点击Cmd-S来接受这些更改。
图5-3显示了带有注释的类。
图5-3 Counter
类现在有注释了!做得好
在这部分,你将学习如何使用浏览器添加协议和方法。
我们定义的类有一个名为count
的实例变量,我们将使用该变量来保持计数。我们将递增、递减它,并显示其当前值。但在Pharo中,我们需要记住三件事:
一切都是对象;
实例变量是完全私有的;
与对象交互的唯一方式是向其发送消息。
除了向对象发送消息之外,没有其他机制可以从计数器外部访问我们的实例变量。必须做的是定义一个返回实例变量的值的方法。这样的方法称为getter方法。因此,我们来为我们的实例变量count
定义一个访问器方法。
通常将方法放入协议中。协议是对方法的分组-它们在Pharo中没有意义,但它们确实向您的类的读者传达了重要的信息。尽管协议可以起任何名字,但是Pharo程序员在命名协议时遵循某些约定。如果您定义了一个方法,但不确定它应该采用哪种协议,请首先查看现有代码,看看是否可以找到已经存在适当的协议。
现在,让我们为实例变量count
创建getter方法。首先在浏览器中选择Counter
类,并确保通过选择'instance side`选项卡来编辑类的实例侧(即,我们在类的实例上定义方法)。然后定义你自己的方法。
图5-4显示了准备定义方法的方法编辑器。
图5-4 方法编辑器选中并准备定义方法
在文本的末尾或开头双击并开始输入您的方法:这会自动替换掉模板。
写入下面的方法定义:
count
^ count
这定义了一个名为count
的方法,该方法不接受任何参数,返回实例变量count
的值。然后在菜单中选择Accept以编译该方法。该方法被自动归类到协议accessing中。
图5-5显示了定义方法后系统的状态。
图5-5 在协议accessing
中定义的方法count
现在,您可以在Playground中输入并求值下面的表达式来测试新方法:
Counter new count
>>> nil
该表达式首先创建一个新的Counter
实例,然后将消息count
发送给它。它检索计数器的当前值。这应该返回nil
(未初始化实例变量的默认值)。之后,我们将创建具有合理的默认值的实例。
作为对getter方法的补充,我们发现了setter方法。它们用于从对象外部更改实例变量的值。例如,表达式Counter new count: 7
首先创建一个新的计数器实例,然后通过向其发送消息count: 7
将其值设置为7
。getter方法和setter方法统称为访问器方法。
此示例显示了实际使用的setter方法:
| c |
c := Counter new count: 7.
c count
>>> 7
setter方法当前不存在,因此作为练习创建方法count:
这样,当在Counter
的实例上调用时,实例变量被设置为消息的参数。在Playground中求值上面的示例来测试您的方法。
如今,编写测试--无论您是在编写代码之前还是之后进行测试--实际上并不是可选的。一组编写良好的测试将支持您的应用程序的发展,并使您相信您的程序会完成您期望它做的事情。为您的代码编写测试是一项很好的投资;测试代码只需编写一次,然后执行一百万次。例如,如果我们将上面的示例转换为一个测试,我们可以自动检查我们的新setter方法是否按预期工作。
我们的测试用例编写为方法,需要驻留在从TestCase
继承的测试类中。因此,我们定义一个名为CounterTest
的类,如下所示:
TestCase subclass: #CounterTest
instanceVariableNames: ''
classVariableNames: ''
package: 'MyCounter'
现在我们可以通过定义一个方法来编写我们的第一个测试。测试方法应该以由Test Runner自动执行的测试开始,或者在允许您运行测试的方法名称旁边获得一个可点击的小圆圈。
图5-6显示了CounterTest
类中的方法testCountIsSetAndRead
的定义。
图5-6 第一个测试被定义并通过
为我们的测试用例定义以下方法。它首先创建一个计数器实例,设置其值,然后验证值是否已设置。消息assert:equals:
是在我们的测试类中实现的消息。它验证一个事实(在本例中,两个对象相等),如果事实不是真的,则测试将失败。
CounterTest >> testCountIsSetAndRead
| c |
c := Counter new.
c count: 7.
self assert: c count equals: 7
排版惯例
Pharoers经常使用符号ClassName >> methodName
来标识方法所属的类。例如,我们上面在类计数器中编写的count
方法将被称为Counter >> count
。请记住,这并不完全是Pharo语法,而更像是我们用来表示“属于类Counter
的实例方法count
”的一种方便的表示法。
从现在起,当我们在本书中展示方法时,我们将以这种形式写下方法的名称。当然,当您在浏览器中实际输入代码时,您不必键入类名或>>;相反,您只需确保在类窗格中选中了适当的类。
通过点击方法前面的圆圈图标(如图5-6所示)或使用工具菜单中提供的测试运行程序来验证测试是否通过。 现在您有了第一个绿色的测试,现在是保存您的工作的好时机。
将您的工作保存在Pharo映像中是好的,但对于共享您的工作或与他人协作并不是很理想。许多现代软件开发都是通过开源版本控制系统git进行的。像GitHub这样的服务构建在Git之上,为开发人员提供了共同构建开源项目的场所--比如Pharo!
Pharo通过工具Iceberg与Git合作。本节将向您展示如何为您的代码创建本地Git存储库,提交您对它的更改,并将这些更改推送到远程存储库,如GitHub。
打开Iceberg
通过Sources菜单或使用快捷键 Cmd-O, I打开Iceberg
您现在应该看到类似于图5-7的内容,其中显示了顶层的冰山面板。它显示了Pharo项目,以及您的图像附带的其他几个项目,并通过显示“本地存储库缺失”来指示它无法为它们找到本地存储库。如果您不想为Pharo做出贡献,则不需要担心Pharo项目缺少本地存储库。
我们将创建一个属于我们自己的新项目。
添加并配置一个工程
按下Add按钮创建一个新项目。从左侧选择‘New Repository’,您应该会看到一个类似于图5-8所示的配置面板。在这里,我们命名项目,在本地磁盘上声明一个保存项目源代码的目录,并在项目本身中声明一个子目录,它将用于保存Pharo代码--通常这是src
目录。
将你的包添加到工程中
添加工程后,Iceberg Working Copy Browser应该会显示一个空窗格,因为我们还没有向项目中添加任何包。点击Add Package按钮,选择包MyCounter
,如图5-9所示。
提交你的更改
一旦添加了包,Iceberg就会向您显示在您的项目管理的包中有未提交的代码,如图5-10所示。按下提交按钮。Iceberg将向您显示即将保存的所有更改(图5-11)。输入提交消息并提交更改。
代码已保存
一旦提交,Iceberg就会指示您的系统和本地存储库是同步的。
干得好!稍后,我们将介绍如何将这些更改推送到远程存储库。但现在让我们回到我们的Counter
上。
我们将以测试驱动的方式为我们的Counter
类开发下面的消息。首先,这是一个测试increment
的消息:
CounterTest >> testIncrement
| c |
c := Counter new.
c count: 0 ; increment; increment.
self assert: c count equals: 2
现在,你尝试一下,写一个方法定义increment
,来使得测试通过。
完成后,再尝试为decrement
写一个测试,然后通过在Counter
类上实现该方法来使其通过测试。
解答
Counter >> increment
count := count + 1
Counter >> decrement
count := count - 1
您的测试应该全部通过(如图5-13所示)。再说一次,这是保存您的工作的好时机。在测试是绿色的地方保存总是很好的做法。要保存更改,您只需使用Iceberg提交它们。
目前,我们的计数器的初始值尚未设置,如以下表达式所示:
Counter new count
>>> nil
我们来编写一个测试,断言新创建的计数器实例的计数为0
:
CounterTest >> testInitialize
self assert: Counter new count equals: 0
这一次,测试将变为黄色,表示测试失败-测试运行良好,但断言没有通过。这与我们到目前为止看到的红色测试不同,在红色测试中,测试失败是因为发生了错误(例如,当方法尚未实现时)。
现在,我们必须编写一个初始化方法来设置Counter
实例变量的默认值。然而,正如我们所提到的,initialize
消息被发送到新创建的实例。这意味着initialize
方法应该在实例端定义,就像发送到Counter
实例的其它方法一样(increment
和decrement
)。initialize
方法负责设置实例变量的默认值。
因此,在Counter
的实例端,在initialization
协议中,编写以下方法(此方法的主体为空。请填写!)。
Counter >> initialize
"set the initial value of the value to 0"
"Your code here"
如果你没做错,我们的testInitialize
测试现在应该通过了。
像往常一样,在进入下一步之前保存您的工作。
我们刚刚讨论了如何在类的实例端定义initialize
方法,因为它负责更改Counter
的实例。现在,让我们来看看如何在类端定义方法。类方法将作为向类本身而不是向类的实例发送消息的结果来执行。要定义类上的方法,我们需要通过选择Class Side将代码浏览器切换到类端。
定义一个名为startingAt:
的新实例创建方法。此方法接收一个整数作为参数,并返回一个Counter
的新实例,该实例的count
设置为指定的值。
我们首先要做什么?当然是定义一个测试:
TestCounter >> testCounterStartingAt5
self assert: (Counter startingAt: 5) count equals: 5
这里,消息startingAt:
被发送给了Counter
自身。
你的实现看起来应该像下面这样:
Counter class >> startingAt: anInteger
^ self new count: anInteger.
这里,我们可以在文本中看到标识类端方法的符号:ClassName class >> methodName
只表示“Counter
类上的类端方法startingAt:
”。
self
在这里指的是什么?像往常一样,self
指的是定义方法的对象本身,因此,这里的self
指的就是Counter
类本身。
我们来再编写一个测试,以确保一切正常:
CounterTest >> testAlternateCreationMethod
self assert: ((Counter startingAt: 19) increment; count) equals: 20
当您检视一个计数器实例时,无论是通过调试器,还是通过在Counter new
表达式上使用Cmd-I打开Inspector,或者甚至当您只是在Counter new
上运行"print it"时,您将看到计数器的一个非常简单的表示:它只会显示"a Counter"
Counter new
>>> a Counter
我们想要一个更丰富的表示,例如,一个显示计数值的表示。在printing
协议下实现如下方法:
Counter >> printOn: aStream
super printOn: aStream.
aStream nextPutAll: ' with value: ', count printString.
请注意,当使用Print it打印(参见图5-14)或在Inspector中检视任意对象时,消息printOn:
将被发送给该对象。通过在Counter
的实例上实现并覆盖掉Object
类中定义的printOn:
方法,我们可以控制它们显示的方式。在本书的后面,我们将更详细地研究这些想法,并了解更多关于streams和super
的内容。
在本例中,我们将允许您为该方法定义一个测试用例。提示:将消息printString
发送到Counter new
以获取其字符串表示,如同printOn:
所生成的那样。
Counter new printString
>>> a Counter with value: 0
现在,让我们再次保存代码,不过,这一次是在远程GIT服务器上
到目前为止,您已将代码保存在本地磁盘上。我们现在将向您展示如何将代码保存在远程Git存储库中,例如您可以在gitHub http://github.com 或 gitLab上创建的存储库。
在远程仓库中创建一个工程
首先,您需要在远程git服务器上创建一个项目。不要里面放任何东西!事情可能会变得令人困惑。仓库的名字要简单明了,比如“Counter”或“Pharo-Counter”。这就是我们的Iceberg项目将要发送到的地方。
添加一个通过HTTPS访问的远程仓库
在Iceberg中,通过双击repository转到您的Counter库的工作副本。然后单击看起来像一个框的图标,标记为Repository。这将打开Counter项目的存储库浏览器,如图5-15所示。
然后,您只需为项目添加一个远程存储库,只需单击标记为Add Remote的大加号图标即可。系统将要求您提供Remote的名称(这只是Git在本地用来标识它的标签)和Remote的URL。您可以使用HTTPS访问GitHub(以 https://github.com 开头的URL)或SSH访问(以git@github.com开头的URL)。SSH将要求您使用正确的凭据在您的计算机上设置SSH代理(有关如何实现这一点的详细信息,请咨询您的GIT远程提供商),HTTPS将要求您输入用户名和密码;如果您愿意,Pharo可以为您存储这些凭据。有关使用HTTPS的信息,请参见图5-16和5-17。
Push
一旦你添加了一个有效的服务器地址,Iceberg就会在按钮上显示一个红色的小指示器。这表明您的本地存储库中有尚未推送到远程存储库的更改。您所要做的就是按下Push按钮;Iceberg将向您显示将被推送到服务器的提交,如图5-18所示。
现在,您真正保存的代码将能够从另一台计算机或位置重新加载。这项技能将使您能够远程工作,并与他人共享和协作。
在本教程中,您学习了如何定义包、类、方法和测试。我们在第一个教程中选择的编程工作流类似于大多数编程语言。然而,在Pharo中,聪明和敏捷的开发人员使用不同的工作流程:测试驱动开发(TDD)。我们建议您通过首先定义一个测试、执行它、在调试器中定义一个方法,然后重复执行来重做整个练习。观看 http://mooc.pharo.org 上提供的Pharo MOOC的第二个“计数器”视频,以更好地了解工作流程。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。