Today we will carry on with the previous session
where I explained that small methods were good.
Today I'll show some examples.
But first, let's quickly review our previous session.
Sending a message leads to a choice.
A masked choice because several possible implementations
will be called up.
And Pharo makes a choice depending on the message receiver.
Class hierarchy defines choices: More classes = more choices.
And you can easily add new choices by creating subclasses.
Subclasses can redefine or refine the code of their superclass.
And sending a message
means giving subclasses the possibility to change behavior.
Today we'll look at the Design Pattern template method.
You'll find it in the Design Pattern manual.
What is a template method?
It's a skeleton that defines the overall behavior
of an algorithm, for example, with hooks inside.
And these hooks can be redefined by subclasses.
This is an algorithm.
It does something, we don't exactly know what.
Next, it does something in hookMethod 1,
then something else,
and then something in hookMethod2.
Since hookMethods 1 and 2 are both complete methods,
they can be redefined in the subclasses.
There are 2 possibilities, one each for hookMethod 1 and 2.
These methods may or may not
have a default behavior.
Here, let's say that hookMethod 1 has a default behavior.
This means that if the subclass proposes nothing,
hookMethod 1 has a default behavior that functions.
Likewise, we could say
that hookMethod 2 does not have a default behavior.
The subclass must imperatively define one.
As designer of the class, you can choose
whether or not to determine a default behavior.
We'll use printString as a first example.
The idea is that if I send a printString message to an object,
I get a string of characters that represents this object.
Here I have a delay.
I've created a 10-second delay.
If I send a printString message to this delay,
I get a delay and in parentheses, a millisecond value for the delay.
The printString method is implemented in the object class like this.
It sends the message printStringLimitedTo
This implementation
recovers a string of characters that represents
the object.
And if this string of characters is too long,
we can cut it off at a certain limit.
Then at the end, we concatenate with '...etc...'
to say the string is not done.
The important thing here
is that printStringLimitedTo sends a printOn message to self.
This is the method
that will or will not be redefined in the subclasses.
If I look at what printString returns
for a Node and for an Apple,
Node new returns a Node.
This is the printString of the Node class.
And here in Apple, we have the printString of the Apple class.
We see that this behavior and the default behavior
are implemented in the class object.
So the default behavior of printString for any object whatsoever
is one:
Recover the class names.
In this case, Node and Apple.
And two, if the class name begins with a vowel,
we use the prefix "an".
If it starts with a consonant, we use the prefix "a".
This is how we get "a node" and "an apple".
This is the default behavior.
But it is possible to change this default behavior
by refining printOn.
For a delay,
we see that the printString of a delay
starts with the default printOn return.
That is "a Delay."
But afterwards, we can add the delay in milliseconds in parentheses.
This is exactly what the printOn method does.
It starts by asking the superclass for the default printString.
Afterwards, it opens a parentheses
with a preset number of milliseconds,
and then it closes the parentheses.
We just looked at refinement.
The delay class refines the implementation
of the printOn method in the object class.
But a class can also completely redefine behavior.
This is the case for booleans, for example.
If I display false,
it will return false.
We don't have "a false",
simply "false".
For this, we just send the "false" string of characters
in the printOn variables stream.
That's a complete redefinition.
Another example of complete redefinition is intervals.
An interval defines a set of values
situated between a minimum and maximum value.
The interval 1 to 100 is displayed by: (1 to: 100)
The interval 1 to: 100 by: 3
1, 4, and so on,
is displayed in the same manner with the additional "by".
How is this implemented?
The interval class redefines the printOn method
and sends different messages to stream variables.
We start by opening parentheses.
The parentheses here and here.
Then we display the initial value or the first number of the interval,
that is this "1" here, and this "1" here.
Then we write 'to:'.
And we write the final value: "100",
and "100".
If there is a "by" that is different from the default "1",
we write the "a".
And at the end, we close the parentheses.
We saw that printString
uses the Design Pattern template method so that classes
can implement their own textual representation.
Now we'll look at another example, which is object copy.
What does copy do?
It enables you to take an object and create a code from it.
Copying objects is complex.
There can be different strategies.
Each class can decide
what the copy of its instances should look like.
There is a simple template method solution
that uses copy and postCopy.
Copy is a template method and postCopy is a hook.
In the object class there is a copy method.
I'll let you read the commentary.
In this method, we send shallowCopy
as a message to self,
followed by postCopy on the result.
What does shallowCopy do?
It creates a new object that shares all instance variables
with the base object.
We have two objects and their instance variables are the same.
If I modify the instance variable of one object, I modify the other too.
That is the default behavior
of shallowCopy.
Depending on what postCopy does,
variables will or will not be shared.
If postCopy is empty, all variables are shared.
But the classes can decide to put different things into postCopy
in order to share certain variables or nothing at all.
The postCopy default setting shares everything.
It simply returns the current object.
But we could also imagine other applications for postCopy.
For example, in the Bag class.
Bag is a type of collection
and its postCopy method copies its contents.
Thus, a Bag is a contents instance variable,
and the designers of the Bag class decided that
when you copy a Bag,
you don't want to share the contents variable,
you want separate variables.
They have the same initial value but you can modify only one.
Here the idea is:
postCopy is a message sent to copy, or the new object,
which shares all of the instance variables.
If we don't want to share them,
we create new ones and decide upon their value.
Here in my contents variable,
I'll put a copy of the initial contents.
That way I don't share them.
Each copy of my Bag has its own contents.
With the dictionary, we go even further.
A dictionary is a collection of key-value pairs,
or a collection of associations.
Not only do we want to copy the collection...
If we copy a dictionary, we want to copy the collection,
and each dictionary has its own collection of pairs...
But we also want each pair to be different.
So that if I modify one, it won't modify the other side.
To do this, I copy the table, but I also copy
each pair inside.
This is what's going on here.
To conclude, the Design Pattern template method is very common.
It's a sign of good design.
If you do good object design, you'll have lots of template methods.
It's perfectly normal and a good thing.
So don't hesitate to use them.
This technique enables subclasses to define behavior
and partially modify the behavior of the superclass.