1 Star 0 Fork 0

朱跃林 / PharoByExample

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
05.A_first_tutorial_Developing_a_simple_counter.md 20.13 KB
一键复制 编辑 原始数据 按行查看 历史
fmcdr 提交于 2023-05-15 15:25 . some fix

第5章 第一个教程:开发一个简单的计数器

要开始使用Pharo,让我们按照下面给出的步骤编写一个简单的计数器。在本练习中,您将学习如何创建包、类、方法、实例、单元测试等。本教程涵盖了在Pharo中开发时将执行的大多数重要操作。你也可以在 http://mooc.pharo.org 上观看Pharo MOOC的配套视频,这些视频有助于讲解本教程。

注意,这个小教程提倡的开发流程是传统的,即定义一个包、一个类,然后定义它的实例变量,然后定义它的方法,最后执行它。现在在Pharo中,开发人员通常遵循不同的工作流,称之为测试驱动开发(就像我们在上一章中看到的那样):他们执行一个会引发错误的表达式。这些错误由调试器和开发人员直接在调试器中编码捕获,允许系统动态地为它们定义实例变量和方法。

我们还将向您展示如何使用Iceberg保存git托管服务(如GitHub)的代码。

一旦您完成本教程,并且对Pharo更加有信心,我们强烈建议您再次使用TDD进行练习。还有一个视频展示了这种强大的编码方法。

5.1 我们的用例

下面是我们的用例:我们希望能够创建一个计数器,让它递增两次,递减一次,再检查它的值是否与预期的一样。下面的例子展示了这一点,并将创建一个完美的单元测试——稍后您将定义一个单元测试。

| counter |
counter := Counter new.
counter increment; increment.
counter decrement.
counter count = 1

我们将编写所有必要的类和方法来支持这个示例。

5.2 创建包和类

在这个部分,您将创建第一个类。在Pharo中,类是在包中定义的,因此我们首先需要创建一个包来容纳类。每次创建类的步骤都是相同的,所以请注意。

创建一个包

使用浏览器创建一个包(在包窗格中右键单击,选择New package)。系统会询问你包的名字,输入MyCounter。然后创建这个新的包,将其添加到包列表中,并在默认情况下已选中。预期结果如图5-1所示。

figure-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说明了浏览器应该显示的结果情况。

figure-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显示了带有注释的类。

figure-5-3

图5-3 Counter类现在有注释了!做得好

5.3 定义协议和方法

在这部分,你将学习如何使用浏览器添加协议和方法。

我们定义的类有一个名为count的实例变量,我们将使用该变量来保持计数。我们将递增、递减它,并显示其当前值。但在Pharo中,我们需要记住三件事:

  1. 一切都是对象;

  2. 实例变量是完全私有的;

  3. 与对象交互的唯一方式是向其发送消息。

除了向对象发送消息之外,没有其他机制可以从计数器外部访问我们的实例变量。必须做的是定义一个返回实例变量的值的方法。这样的方法称为getter方法。因此,我们来为我们的实例变量count定义一个访问器方法。

通常将方法放入协议中。协议是对方法的分组-它们在Pharo中没有意义,但它们确实向您的类的读者传达了重要的信息。尽管协议可以起任何名字,但是Pharo程序员在命名协议时遵循某些约定。如果您定义了一个方法,但不确定它应该采用哪种协议,请首先查看现有代码,看看是否可以找到已经存在适当的协议。

5.4 创建一个方法

现在,让我们为实例变量count创建getter方法。首先在浏览器中选择Counter类,并确保通过选择'instance side`选项卡来编辑类的实例侧(即,我们在类的实例上定义方法)。然后定义你自己的方法。

图5-4显示了准备定义方法的方法编辑器。

figure-5-4

图5-4 方法编辑器选中并准备定义方法

在文本的末尾或开头双击并开始输入您的方法:这会自动替换掉模板。

写入下面的方法定义:

count
    ^ count

这定义了一个名为count的方法,该方法不接受任何参数,返回实例变量count的值。然后在菜单中选择Accept以编译该方法。该方法被自动归类到协议accessing中。

图5-5显示了定义方法后系统的状态。

figure-5-5

图5-5 在协议accessing中定义的方法count

现在,您可以在Playground中输入并求值下面的表达式来测试新方法:

Counter new count
>>> nil

该表达式首先创建一个新的Counter实例,然后将消息count发送给它。它检索计数器的当前值。这应该返回nil(未初始化实例变量的默认值)。之后,我们将创建具有合理的默认值的实例。

5.5 添加一个setter方法

作为对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中求值上面的示例来测试您的方法。

5.6 定义一个测试类

如今,编写测试--无论您是在编写代码之前还是之后进行测试--实际上并不是可选的。一组编写良好的测试将支持您的应用程序的发展,并使您相信您的程序会完成您期望它做的事情。为您的代码编写测试是一项很好的投资;测试代码只需编写一次,然后执行一百万次。例如,如果我们将上面的示例转换为一个测试,我们可以自动检查我们的新setter方法是否按预期工作。

我们的测试用例编写为方法,需要驻留在从TestCase继承的测试类中。因此,我们定义一个名为CounterTest的类,如下所示:

TestCase subclass: #CounterTest
    instanceVariableNames: ''
    classVariableNames: ''
    package: 'MyCounter'

现在我们可以通过定义一个方法来编写我们的第一个测试。测试方法应该以由Test Runner自动执行的测试开始,或者在允许您运行测试的方法名称旁边获得一个可点击的小圆圈。

图5-6显示了CounterTest类中的方法testCountIsSetAndRead的定义。

figure-5-6

图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所示)或使用工具菜单中提供的测试运行程序来验证测试是否通过。 现在您有了第一个绿色的测试,现在是保存您的工作的好时机。

5.7 通过Iceberg将你的代码保存到git存储库中

将您的工作保存在Pharo映像中是好的,但对于共享您的工作或与他人协作并不是很理想。许多现代软件开发都是通过开源版本控制系统git进行的。像GitHub这样的服务构建在Git之上,为开发人员提供了共同构建开源项目的场所--比如Pharo!

Pharo通过工具Iceberg与Git合作。本节将向您展示如何为您的代码创建本地Git存储库,提交您对它的更改,并将这些更改推送到远程存储库,如GitHub。

打开Iceberg

通过Sources菜单或使用快捷键 Cmd-O, I打开Iceberg

您现在应该看到类似于图5-7的内容,其中显示了顶层的冰山面板。它显示了Pharo项目,以及您的图像附带的其他几个项目,并通过显示“本地存储库缺失”来指示它无法为它们找到本地存储库。如果您不想为Pharo做出贡献,则不需要担心Pharo项目缺少本地存储库。

figure-5-7

我们将创建一个属于我们自己的新项目。

添加并配置一个工程

按下Add按钮创建一个新项目。从左侧选择‘New Repository’,您应该会看到一个类似于图5-8所示的配置面板。在这里,我们命名项目,在本地磁盘上声明一个保存项目源代码的目录,并在项目本身中声明一个子目录,它将用于保存Pharo代码--通常这是src目录。

figure-5-8

将你的包添加到工程中

添加工程后,Iceberg Working Copy Browser应该会显示一个空窗格,因为我们还没有向项目中添加任何包。点击Add Package按钮,选择包MyCounter,如图5-9所示。

figure-5-9

提交你的更改

一旦添加了包,Iceberg就会向您显示在您的项目管理的包中有未提交的代码,如图5-10所示。按下提交按钮。Iceberg将向您显示即将保存的所有更改(图5-11)。输入提交消息并提交更改。

figure-5-10

figure-5-11

代码已保存

一旦提交,Iceberg就会指示您的系统和本地存储库是同步的。

干得好!稍后,我们将介绍如何将这些更改推送到远程存储库。但现在让我们回到我们的Counter上。

figure-5-12

5.8 添加更多的消息

我们将以测试驱动的方式为我们的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提交它们。

figure-5-13

5.9 实例的初始化方法

目前,我们的计数器的初始值尚未设置,如以下表达式所示:

Counter new count
>>> nil

我们来编写一个测试,断言新创建的计数器实例的计数为0

CounterTest >> testInitialize
    self assert: Counter new count equals: 0

这一次,测试将变为黄色,表示测试失败-测试运行良好,但断言没有通过。这与我们到目前为止看到的红色测试不同,在红色测试中,测试失败是因为发生了错误(例如,当方法尚未实现时)。

5.10 定义 initialize 方法

现在,我们必须编写一个初始化方法来设置Counter实例变量的默认值。然而,正如我们所提到的,initialize消息被发送到新创建的实例。这意味着initialize方法应该在实例端定义,就像发送到Counter实例的其它方法一样(incrementdecrement)。initialize方法负责设置实例变量的默认值。

因此,在Counter的实例端,在initialization协议中,编写以下方法(此方法的主体为空。请填写!)。

Counter >> initialize
    "set the initial value of the value to 0"
    
    "Your code here"

如果你没做错,我们的testInitialize测试现在应该通过了。

像往常一样,在进入下一步之前保存您的工作。

5.11 定义一个新的创建实例的方法

我们刚刚讨论了如何在类的实例端定义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

5.12 更好的对象描述

当您检视一个计数器实例时,无论是通过调试器,还是通过在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的内容。

figure-5-14

在本例中,我们将允许您为该方法定义一个测试用例。提示:将消息printString发送到Counter new以获取其字符串表示,如同printOn:所生成的那样。

Counter new printString
>>> a Counter with value: 0

现在,让我们再次保存代码,不过,这一次是在远程GIT服务器上

5.13 将你的代码保存到远程服务器

到目前为止,您已将代码保存在本地磁盘上。我们现在将向您展示如何将代码保存在远程Git存储库中,例如您可以在gitHub http://github.com 或 gitLab上创建的存储库。

在远程仓库中创建一个工程

首先,您需要在远程git服务器上创建一个项目。不要里面放任何东西!事情可能会变得令人困惑。仓库的名字要简单明了,比如“Counter”或“Pharo-Counter”。这就是我们的Iceberg项目将要发送到的地方。

添加一个通过HTTPS访问的远程仓库

在Iceberg中,通过双击repository转到您的Counter库的工作副本。然后单击看起来像一个框的图标,标记为Repository。这将打开Counter项目的存储库浏览器,如图5-15所示。

figure-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。

figure-5-16

figure-15-17

Push

一旦你添加了一个有效的服务器地址,Iceberg就会在按钮上显示一个红色的小指示器。这表明您的本地存储库中有尚未推送到远程存储库的更改。您所要做的就是按下Push按钮;Iceberg将向您显示将被推送到服务器的提交,如图5-18所示。

figure-5-18

现在,您真正保存的代码将能够从另一台计算机或位置重新加载。这项技能将使您能够远程工作,并与他人共享和协作。

5.14 总结

在本教程中,您学习了如何定义包、类、方法和测试。我们在第一个教程中选择的编程工作流类似于大多数编程语言。然而,在Pharo中,聪明和敏捷的开发人员使用不同的工作流程:测试驱动开发(TDD)。我们建议您通过首先定义一个测试、执行它、在调试器中定义一个方法,然后重复执行来重做整个练习。观看 http://mooc.pharo.org 上提供的Pharo MOOC的第二个“计数器”视频,以更好地了解工作流程。

1
https://gitee.com/fmcdr/pharo-by-example.git
git@gitee.com:fmcdr/pharo-by-example.git
fmcdr
pharo-by-example
PharoByExample
master

搜索帮助