Skip to content

Commit

Permalink
full pass
Browse files Browse the repository at this point in the history
  • Loading branch information
Ducasse committed Jan 22, 2025
1 parent 4a0e0fa commit 760ecad
Show file tree
Hide file tree
Showing 10 changed files with 2,791 additions and 296 deletions.
13 changes: 5 additions & 8 deletions Chapters/Counter/Counter.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,8 @@ Creating a class requires four steps. They consist basically in editing the clas
You should get the following class definition.

```
Object subclass: #Counter
instanceVariableNames: 'count'
classVariableNames: ''
Object << #Counter
slots: { #count };
package: 'MyCounter'
```

Expand Down Expand Up @@ -157,17 +156,15 @@ Writing tests is an important activity that will support the evolution of your a
To define a test case we will define a class that inherits from `TestCase`. Therefore define a class named `CounterTest` as follows:

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


Now we can write a first test by defining one method. Test methods should start with _test_ to be automatically executed by the TestRunner or when you press on the icon of the method. Now to make sure that you understand in which class we define the method we prefix the method body with the class name and `>>`.
`CounterTest>>` means that the method is defined in the class `CounterTest`.

Define the following method. It first creates an instance, sets its value and verifies that the value is correct. The message `assert:equals:` is a special message verifying if the test passed or not.
Define the following method. It first creates an instance, sets its value, and verifies that the value is correct. The message `assert:equals:` is a special message verifying if the test passed or not.

```
CounterTest >> testCountIsSetAndRead
Expand Down Expand Up @@ -310,7 +307,7 @@ If you run it, it will turn yellow indicating a failure \(a situation that you a
#### Define an initialize method


Now we have to write an initialization method that sets a default value of the `count` instance variable. However, as we mentioned the `initialize` message is sent to the newly created instance. This means that the `initialize` method should be defined at the instance side as any method that is sent to an instance of `Counter` \(like `increment`\) and `decrement`. The `initialize` method is responsible to set up the default value of instance variables.
Now we have to write an initialization method that sets a default value of the `count` instance variable. However, as we mentioned the `initialize` message is sent to the newly created instance. This means that the `initialize` method should be defined at the instance side as any method that is sent to an instance of `Counter` (like `increment`) and `decrement`. The `initialize` method is responsible to set up the default value of instance variables.

Therefore at the instance side, you should create a protocol `initialization`, and create the following method \(the body of this method is left blank. Fill it in!\).

Expand Down
515 changes: 473 additions & 42 deletions Chapters/DSL/DSL.md

Large diffs are not rendered by default.

1,224 changes: 1,093 additions & 131 deletions Chapters/Expressions/Expressions.md

Large diffs are not rendered by default.

277 changes: 258 additions & 19 deletions Chapters/Inheritance/Extending.md

Large diffs are not rendered by default.

437 changes: 405 additions & 32 deletions Chapters/Inheritance/Inheritance.md

Large diffs are not rendered by default.

566 changes: 522 additions & 44 deletions Chapters/Katas/GramKatas.md

Large diffs are not rendered by default.

32 changes: 23 additions & 9 deletions Chapters/NewCounterMaterial/Exo-Counter.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ The lower pane of the Browser should now be open with a tab showing the template
You should get the following class definition:

```
Object subclass: #Counter
instanceVariableNames: 'count'
classVariableNames: ''
Object << #Counter
slots: { #count };
package: 'MyCounter'
```

Expand Down Expand Up @@ -73,6 +72,8 @@ The class we have defined has one instance variable named `count`, and we're goi

And so there is no other mechanism to access our instance variable from outside of our counter than by sending a message to the object. What must do is define a method that returns the value of the instance variable. Such methods are called _getter_ methods. So, let's define an accessor method for our instance variable `count`.
A method is usually placed into a _protocol_. These protocols are just a group of methods - they have no meaning in Pharo, but they do convey important information to the readers of your class. Although protocols can have any name, Pharo programmers follow certain conventions when naming protocols. If you define a method and are not sure what protocol it should be in, first take a look through existing code and see if you can find an appropriate protocol that already exists.


### Create a method

Now let us create the getter method for the instance variable `count`. Start by selecting the class `Counter` in a Browser, and make sure you are editing the instance side of the class (i.e., we define methods on _instances_ of our class) by selecting the instance side tab. Then define your method.
Expand Down Expand Up @@ -110,21 +111,23 @@ c count
```

The setter method does not currently exist, so as an exercise create the method `count:` such that, when invoked on an instance of `Counter`, the instance variable is set to the argument of the message. Test your method by evaluating the example above in a Playground.

### Define a Test Class

Writing tests - whether you do it before or after you write your code - isn't really optional these days. A collection of well-written tests will support the evolution of your application, and give you confidence that your program does the things you expect it to do. Writing tests for your code is a good investment; test code is written once and executed a million times. For example, if we turned the example above into a test we could have checked automatically that our new setter method is working as expected.

Our test cases, written as methods, need to live inside a test class that inherits from `TestCase`. So we define a class named `CounterTest` as follows:

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

Now we can write our first test by defining a method. Test methods should start with _test_ to be automatically executed by the Test Runner or to get the little clickable circle next to the method name that lets you run the test.
Figure *@FirstGreenTest@* shows the definition of the method `testCountIsSetAndRead` in the class `CounterTest`.
![A first test is defined and it passes.](figures/FirstGreenTest.png label=FirstGreenTest)

![A first test is defined and it passes. % anchor=FirstGreenTest](figures/FirstGreenTest.png)

Define the following method for our test case. It first creates an instance of a `Counter`, sets its value and then verifies that the value has been set. The message `assert:equals:` is a message implemented in our test class. It verifies a fact (in this case that two objects are equal), and will fail the test if the fact isn't true.

```
Expand All @@ -138,19 +141,26 @@ CounterTest >> testCountIsSetAndRead
#### A typographic convention

Pharoers frequently use the notation `ClassName >> methodName` to identify the class to which a method belongs. For example, the `count` method we wrote above in our class `Counter` would be referred to as `Counter >> count`. Just keep in mind that this is not _exactly_ Pharo syntax, but more like a convenient notation we use to indicate "the instance method `count` which belongs to the class `Counter`".

From now on, when we show a method in this book, we will write the name of the method in this form. Of course, when you actually type the code into the browser, you don't have to type the class name or the `>>`; instead, you just make sure that the appropriate class is selected in the class pane.
Verify that the test passes by executing either pressing the circle icon in front of the method (as shown by Figure *@FirstGreenTest@*) or using the Test Runner.
As you now have your first green test, it's a good time to save your work.


### Saving your code as a git repository with Iceberg

Saving your work in the Pharo image is good, but it's not ideal for sharing your work or collaborating with others. Much of modern software development is mediated through git, an open-source version control system. Services such as GitHub are built on top of git, providing places where developers can work together building open source projects - like Pharo!
Pharo works with git through the tool **Iceberg**. This section will show you how to create a local git repository for your code, commit your changes to it, and also push those changes to a remote repository such as GitHub.

#### Open Iceberg

Open Iceberg through the **Sources** menu, or by hitting `Cmd-O,I`.
![Iceberg _Repositories_ browser on a fresh image indicates that if you want to version modifications to Pharo itself you will have to tell Iceberg where the Pharo clone is located. But you do not care.](figures/Save1-EmptyIceberg.png width=75&label=EmptyIceberg)

![Iceberg _Repositories_ browser on a fresh image indicates that if you want to version modifications to Pharo itself you will have to tell Iceberg where the Pharo clone is located. But you do not care. %width=75&anchor=EmptyIceberg](figures/Save1-EmptyIceberg.png )

You should now see something similar to Figure *@EmptyIceberg@* which shows the top-level Iceberg pane. It shows the Pharo project, and a few other projects that also come with your image, and indicates that it could not find a local repository for them by showing 'Local repository missing'. You do not have to worry about the Pharo project or having a local repository if you do not want to contribute to Pharo.
We're going to create a new project of our own.

#### Add and configure a project

Press the button `Add` to create a new project. Select 'New Repository' from the left and you should see a configuration pane similar to the one in Figure *@CreateProject@*. Here we name our project, declare a directory on our local disk where the project's source should be saved, and also a subdirectory in the project itself which will be used to keep the Pharo code in - conventionally this is the `src` directory.
Expand Down Expand Up @@ -227,11 +237,13 @@ CounterTest >> testInitialize
```

This time the test will turn _yellow_, indicating a test failure - the test ran fine, but the assertion did not pass. This is different to the _red_ tests we've seen so far, where the tests have failed because an error occurred (when a method has not been implemented, for instance).

### Define an initialize method

Now we have to write an initialization method that sets a default value of the `count` instance variable.
In Pharo, when creating a new object sending the message `new` to a class, the newly created instance is sent a message `initialize`. This gives the opportunity to the instance to initialize itself.
Therefore we will define a `initialize` method that will correctly initialize the default value of a counter.

Therefore we will define an `initialize` method that will correctly initialize the default value of a counter.

Since the `initialize` message is sent to a new instance, it means that the `initialize` method should be defined on the _instance side_, just like any method that is sent to an instance of `Counter` (`increment` and `decrement`). The `initialize` method is responsible for setting up the default values of instance variables.
And so, on the instance side of `Counter`, and in the `initialization` protocol, write the following method (the body of this method is left blank. Fill it in!).
Expand All @@ -250,6 +262,7 @@ As always, save your work before moving on to the next step.

We just discussed how the `initialize` method is defined on the _instance side_ of our class, as it is responsible for altering an instance of `Counter`. Now let's take a look at defining a method on the _class side_ of a class. Class methods will be executed as a result of sending messages to the class itself, rather than to instances of the class. To define the method on the class, we need to toggle the Code Browser over to the class side by selecting **Class side**.
Define a new instance creation method called `startingAt:`. This method receives an integer as an argument and returns a new instance of `Counter` with the count set to the specified value.

What do we do first? Why, we define a test of course:

```
Expand All @@ -266,6 +279,7 @@ Counter class >> startingAt: anInteger
```

Here we see the notation for identifying a _class side_ method in our text: `ClassName class >> methodName` just means "the class side method `startingAt:` on the class `Counter`".

What does `self` refer to here? As always, `self` refers to the object that the method is defined in, and so here it refers to the `Counter` class itself.
Let's write another test just to make sure that everything is working:

Expand Down
9 changes: 5 additions & 4 deletions Chapters/OOPNutshell/OOPNutshell.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ What the diagram shows is that we have:
- directories that have a name and can contain other files or directories. Here we get the `manga`, `comics`, `oldcomics`, and `belgiumSchool` directories. Directories can be nested: `comics` contains three repositories. The `belgiumSchool` directory contains `tintinEtLesPicaros`.


![Some directories and files organised in a file system. % width=50&anchor=figdirectories](figures/comicsFileTree.png)
![Some directories and files organized in a file system. % width=50&anchor=figdirectories](figures/comicsFileTree.png)


### Studying a first scenario
Expand All @@ -370,14 +370,15 @@ In the rest of this book, we will code such examples as tests that can automatic
For now, it would make the discourse too complex, so we just use little code examples.

We create two directories.

```
| dComics dOldComics dManga |
dComics := MFDirectory new name: 'comics'.
dOldComics := MFDirectory new name: 'oldcomics'.
```


We add the oldcomics folder to comics and we check that the parent children relationship is well set.
We add the oldcomics folder to comics and we check that the parent-child relationship is well set.

```
...
Expand Down Expand Up @@ -407,7 +408,7 @@ dComics parent
```


Here we verify that `dOldComics` is comprised in the children of `dComics`.
Here we verify that `dOldComics` is comprised of the children of `dComics`.
```
...
dComics children includes: dOldComics.
Expand Down Expand Up @@ -578,7 +579,7 @@ MFDirectory >> printOn: aStream
Try it and it should print the expected results.
What do we see with this definition: it is a kind of recursive definition. The name of a directory is in fact the concatenation (here we just add in the stream but this is the same. ) of the name of its parents (as shown in Figure *@InstancesRecursion@*).

Similar to a recursive function navigating a structure composed of similar elements (like a linked-list or any structure defined by induction), each parent receives and executes another time the `printOn:` method and returns the name for its part.
Similar to a recursive function navigating a structure composed of similar elements (like a linked list or any structure defined by induction), each parent receives and executes another time the `printOn:` method and returns the name for its part.

### Adding files

Expand Down
6 changes: 3 additions & 3 deletions Chapters/SimpleLan/Simple-LAN-Definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ From an object-oriented point of view, it is really interesting because it shows

You will define step by step an application that simulates a simple Local Area Network (LAN). You will create several classes: `LNPacket`, `LNNode`, `LNWorkstation`, and `LNPrintServer`. We start with the simplest version of a LAN. In subsequent exercises, we will add new requirements and modify the proposed implementation to take them into account.

![An example of a LAN with packets.](figures/lan-simple width=60)
![An example of a LAN with packets. % width=60](figures/lan-simple)


### Creating the class `LNNode`
Expand Down Expand Up @@ -39,7 +39,6 @@ To help you write tests, we will define a test class.

```
TestCase << #LNNodeTest
slots: {};
package: 'SimpleLAN'
```

Expand All @@ -51,6 +50,7 @@ Create a subclass of `Object` called `LNNode`, with two instance variables: `nam


#### Exercise: Accessors

Create accessors and mutators for the two instance variables. Document the mutators to inform users that the argument passed to `name:` should be a `Symbol`, and the arguments passed to `nextNode:` should be a `LNNode`.
Define the following test to validate such a simple behavior.

Expand Down Expand Up @@ -108,7 +108,7 @@ Note that:

##### A little example.

The following snippet shows basic behavior of an open LAN composed of two nodes, Mac and PC1.
The following snippet shows the basic behavior of an open LAN composed of two nodes, Mac and PC1.

```
(LNNode new
Expand Down
8 changes: 4 additions & 4 deletions Chapters/SnakesAndLadders/SnakesAndLadders.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ game play

Since we want to focus on the game logic, you will develop a textual version of the game and avoid any lengthy user interface descriptions.

The following is an example of game execution: Two players are on the first tile. The board contains two ladders, \[2->6] and [7->9], and one snake [5<-11].
The following is an example of game execution: Two players are on the first tile. The board contains two ladders, [2->6] and [7->9], and one snake [5<-11].

Jill rolls a die, throws a 3, and moves to the corresponding tile.
Jack rolls a die, throws a 6, moves to the corresponding tile, and follows its effect, climbing the ladder at tile 7 up to tile 9.
Expand Down Expand Up @@ -675,7 +675,7 @@ SLTile >> removePlayer: aPlayer


Now propose an implementation of the method `movePlayer: aPlayer distance: anInteger`.
You should get the destination tile for the player, remove the player from its current tile, add it to the destination tile and change the position of the player to reflect its new position.
You should get the destination tile for the player, remove the player from its current tile, add it to the destination tile, and change the position of the player to reflect its new position.

```
SLGame >> movePlayer: aPlayer distance: anInteger
Expand All @@ -688,7 +688,7 @@ We suspect that when we will introduce ladder and snake tiles, we will have to r
#### About our implementation


The implementation that we propose below for the method `movePlayer: aPlayer distance: anInteger` is not as nice as we would like it to be. Why? Because it does not give a chance to the tiles to extend this behavior and our experience tells us that we will need it when we will introduce the snake and ladder. We will discuss that when we will arrive there.
The implementation that we propose below for the method `movePlayer: aPlayer distance: anInteger` is not as nice as we would like it to be. Why? Because it does not give a chance to the tiles to extend this behavior and our experience tells us that we will need it when we will introduce the snake and ladder. We will discuss that when we will arrive there.

```
SLGame >> movePlayer: aPlayer distance: anInteger
Expand Down Expand Up @@ -950,7 +950,7 @@ SLSnakeTile >> printInsideOn: aStream
#### super does not have to be the first expression


Now we show you our definition of `printInsideOn:` for the class `SLSnakeTile`. Why do we show it? Because it shows you that an expression invoking an overriden method can be placed anywhere. It does not have to be the first expression of a method. Here it is the last one.
Now we show you our definition of `printInsideOn:` for the class `SLSnakeTile`. Why do we show it? Because it shows you that an expression invoking an overridden method can be placed anywhere. It does not have to be the first expression of a method. Here it is the last one.

```
SLSnakeTile >> printInsideOn: aStream
Expand Down

0 comments on commit 760ecad

Please sign in to comment.