Modeling a Concrete Price


In this exercise, we model a concrete price using EDD.

Eventually we will have other kinds of discounted prices, and probably an abstract Price class as a common parent, but we will start by simply implementing this concrete case.


In this exercise we will just consider the most basic of our requirements.

A price can be something like 100 EUR. Prices can be added or multiplied.

We assume that we already have classes to model different kinds of Money.

Getting started with EDD

Instead of writing a test first, we start by constructing an example. Instead of directly coding an example method, instead we start by writing a snippet that creates an instance of the domain entity we want to model. Once we have an object, then we can extract it as an example. We can then explore it, and when we understand it, we express our understanding in terms of assertions.

That is, the specific examples and the tests emerge from oyr exploration .

We start with our requirement, A price can be something like 100 EUR. and code it up. Of course this code won't run because we haven't implemented anything yet.

ConcretePrice new money: 100 euros.  

Task: The class ConcretePrice does not exist. Create it using the fixit dialogue (click on the wrench icon). Give it the package name EDDPrices (or anything else you like). Also give it a money slot. When you have specified the new class, click on the checkmark at the bottom left of the dialogue.

Tip: You will also have to create the money: setter. You also can do this with the fixit dialogue, but there is a better way, using a Create accessors refactoring. To do this: (1) open the code bubble for the the ConcretePrice class (grey triangle), (2) click on the grey back underneath the class name to show the class definition, and (3) right-click on the money slot to reveal the menu including the Create accessors refactoring. (4) Select it, and click the checkmark to accept it and generate the money and money: getter and setter.

Now you can inspect the result. You should see a brand-new ConcretePrice instance.

Adding a factory method

We'd like to generate a Price from a Money by sending it asPrice. We start by inspecting a Money:

100 euros.

Task: In the playground of the Money instance, inspect ConcretePrice new money: self. This is what the money instance should create when we send it asPrice. We could implement this method directly, or we could apply an Extract method refactoring to the snippet. To do this, select the entire code snippet, and right-click to see the menu with the refactoring. Do this and call the new method asPrice.

Now you should see instead the snippet self asPrice. Inspect that and verify that you have a ConcretePrice instance.

The new asPrice method should be an extension method of our package, not a change to the GToolkit-Tutorial-Prices package. To do this, we simply change the method category of the new method to our package.

Task: Open the asPrice code bubble and change the category to *EDDPrices.

Hint: at the bottom right of the method coder are two buttons, one for the method category and the other for the instance vs class methods. Click on the first button that says accessing and change the category to *EDDPrices.

Adding a view

If we inspect a ConcretePrice instance, we only get an ugly, generic view.

100 euros asPrice.

If we inspect the money slot in the Raw view of the price, its Detail view looks quite nice:

Task: Add a Money view to the concrete price that reuses the Details view from GtTMoney Object subclass: #GtTMoney instanceVariableNames: '' classVariableNames: '' package: 'GToolkit-Tutorial-Prices-Model' .

Hint: Alt-click on the Details view to see its source code in the Source view of the method. Select and copy this code. Now go back to the Meta view of the ConcretePrice object and click on the + to add a new method.

Paste the code you copied. Change the method name to gtMoneyFor: and change the title to 'Money'. Add a priority: 10; after the title. Also change this line:

text: self gtDisplayString asRopedText;

should instead be:

text: self money gtDisplayString asRopedText;

Save and commit this.

Now you'll see a nice Money view for the price object.

Extracting the example

This looks like it might be a nice example.

PriceExamples new hundredEuros

Let's extract it as an example.

Task: Apply the Extract example refactoring to the snippet above.

Hint: To apply the refactoring, right-click somewhere within the code snippet (or select all the code and then right-click). You then need to give the class name PriceExamples, the example method name hundredEuros and the package name EDDPrices. Accept the refactoring by selecting the checkmark. After refactoring the snippet should look like this:

PriceExamples new hundredEuros.

Task: Open the hundredEuros code bubble and browse the extracted code. Inspect the example directly from the method coder. Notice that the <gtExample> pragma has been added, and price has been defined as a temporary.

NB: As with GtTMoneyExamples Object subclass: #GtTMoneyExamples instanceVariableNames: '' classVariableNames: '' package: 'GToolkit-Tutorial-Prices-Examples' , we put examples in a separate class from that of the example instances. As we later add different kind of Price classes, we'll add all our examples to the PriceExamples, starting with examples of our ConcretePrice class and then our discounted prices.

Adding assertions

We still aren't testing anything. We could directly add some assertions to our example method, but let's play with it first.

PriceExamples new hundredEuros.

We would expect our example to be equal to another 100 euro instance.

Task: Inspect the example and then inspect self = 100 euros asPrice in its playground. Why is the answer false?

Hint: Open the code bubble for =. Since we did not implement the method = for our ConcretePrice class, we inherit the default implementation from Object ProtoObject subclass: #Object instanceVariableNames: '' classVariableNames: 'DependentsFields' package: 'Kernel-Objects' , which tests for object identity, not equality of values.

We will have to implement = for ConcretePrice instances so it does the right thing. Instead of directly implementing it, we'll first prototype it in the playground, and then extract the method.

Task: Evaluate other := 100 euros asPrice. in a playground snippet of our 100 euro price object. Now write self money = other money. in a fresh snippet and verify that it's true. Apply an Extract method refactoring to this to extract an = method for the ConcretePrice class.

Hint: Select the entire code self money = other money, and right-click to see the Extract method refactoring. Call the new method =. (Ignore the warning that the method is already defined in Object.)

Verify that self = 100 euros asPrice is now true. Finally add the assertion self assert: price equals: 100 euros asPrice. to the hundredEuros example.

PriceExamples new hundredEuros.

Extra task: Have a look at GtTCurrencyMoney>>#= = aMoney ^ (self isZero and: [ aMoney isZero ]) or: [ (aMoney class = GtTCurrencyMoney) and: [ aMoney amount = self amount and: [ aMoney currency = self currency ] ] ] . It's a bit more complex than the = method that we just defined, because it will work for any object as an argument, not just another money. Generalize ConcretePrice>>#= in the same way and verify that it works.

Supporting addition and multiplication

This last part is open-ended.

Extra Task: Using the same EDD style, develop examples and code for addition and multiplication.

You could start with this snippet.:

100 euros asPrice + 15 euros asPrice.

Saving your work

If you'd like to save your work before continuing, of course you can just save the image and start it again later.

You can also file out your changes as source code that you can file in to another image later. To file out your changes, just send the message fileOut to the class or a package that you want to export. (To get the package of a class, send it the message package. So ConcretePrice package fileOut will create a file that you can later view with the File System tool and file back in with the File In button.

Finally, you could set up a GitHub repository and save your changes there. You can find instructions in the GT Book in How to work with GitHub.