I started playing with the memento pattern for a use case I was researching when I realized that the Kotlin implementation had a, potentially, show stopper in comparison with the Java one:
I could not use a private property from within the same file
Why was that a show stopper? We’ll see, but first, what is the memento pattern?
Memento pattern
This pattern is a good way to implement a functionality that helps in restoring previous states. One good example is the undo in our text editors. You can write, edit, delete and then, by hitting undo, take each action back.
There are three main ingredients for this pattern:
the originator that holds the current state and creates snapshots of itself,
the memento that, in essence, is the snapshot with perhaps some additional metadata and
the caretaker that orchestrates the backup/restore of the state
So in our example the originator is the editor which knows what the text is, the carets position etc, the memento a copy of those values and the caretaker can be the interface between the user and the editor.
Java implementation
Lets try to have an overly simplified version of the above example in Java:
Here the editor, besides manipulating text, is able to produce snapshots of its state in a way that only itself can access the state’s values. The Memento class might be public, in order to allow the caretaker to handle instances of it, but its fields are private and only the originator can read them. A great way to copy something while having the smallest possible API surface and maximum privacy.
As a matter of fact, here is the caretaker and its usage:
classUI(
privatevalscreen:Screen
) {
privateval editor =Editor()
privateval backups = mutableListOf<Memento>()
funwrite(text:String) {
backups.add(0, editor.backup())
editor.write(text)
editor.render(screen)
}
funedit(index:Int, text:String) {
backups.add(0, editor.backup())
editor.edit(index, text)
editor.render(screen)
}
fundelete(index:Int) {
backups.add(0, editor.backup())
editor.delete(index)
editor.render(screen)
}
funundo() {
val memento = backups.removeAt(0)
editor.restore(memento)
editor.render(screen)
}
}
funmain() {
val screen =StdoutScreen()
val ui =UI(screen)
with(ui) {
write("Hello, there! ")
write("How are you? ")
write("I hope you feel good ๐")
edit(1, "Kotlin! ")
delete(1)
undo()
undo()
undo()
undo()
undo()
}
}
/* which produces this:
Hello, there! |
Hello, there! How are you? |
Hello, there! How are you? I hope you feel good :)|
Hello, there! Kotlin! |I hope you feel good ๐
Hello, there! |I hope you feel good ๐
Hello, there! Kotlin! |I hope you feel good ๐
Hello, there! How are you? I hope you feel good :)|
As you can see the UI uses the editor to write, edit, delete but before that it saves a backup with the editor’s state in order to restore it every time the user hits undo!
Kotlin implementation
So lets move originator and memento to Kotlin. Ctrl+Alt+Shift+K and boom.. we have a problem:
Kotlin, in contrast with Java, does not allow accessing private properties when in the same file.
What do we do? Well we can always make the properties public:
but this way we, indirectly, expose the editors state:
Another way to implement the pattern is to have Memento as an interface with no state for the public API and have a private implementation of it for internal usage:
this way we do not expose any state but we do open a bit our API. We now have an interface that can be implemented and given to the restore() function.
Inner classes
Fortunately Kotlin has inner classes. An inner class can access the outer class’s members but, most importantly, can be extended only from within the outer class. This means that this:
This is an implementation of the Strategy Pattern where every strategy is a certain action allowing us to add as many actions as we want as long as there is a one-to-one relation with what the response sends:
One day the external service decides to add a print error action but it does not do it by having a new action value. Instead it decides to have an is error flag which must be taken under consideration when the action value is print!
which defeats the purpose of the strategy pattern and violates the SRP. Each strategy should do one thing and one thing only. By having an if in the strategy we allow it to implement two actions thus having two reasons to change.
Adding a chain
What we need is a way to allow the representation of a single action value from multiple strategies where each one implements a unique action depending on the values of the response.
This is where the Chain of Responsibility Pattern comes in helping as into having multiple actions tied together, passing the response to each other until one of them can execute it.
There are two ways to implement the pattern depending on the size and state of your project.
Using inheritance
If you have a small project with just a few actions and you don’t mind touching many files you can use inheritance and have each action containing the next in the chain:
abstractclassBaseAction(
privatevalnextAction:Action?
) : Action {
overridefunexecute(response:Response) {
nextAction?.execute(response)
}
}
classPrint : Action {
overrideval name:String="print"
overridefunexecute(response:Response) {
println("printing ${response.value}…")
}
}
classPrintInErrorStream(
nextAction:Action?
) : BaseAction(nextAction) {
overrideval name:String="print"
overridefunexecute(response:Response) {
if (response.isError) {
System.err.println("printing (in error stream) ${response.value}…")
If we do not wish to change any of the existing actions then composition is the only way.
What we need is a way to wrap an existing action in an object that will (a) hold the next action in the chain and (b) decide which action will be executed, the wrapped or the next one:
classChainAction(
privatevalaction:Action,
privatevalcanExecute: (Response) ->Boolean
) : Action {
privatevar nextAction:Action?=null
overrideval name:String= action.name
overridefunexecute(response:Response) {
if (canExecute(response)) {
action.execute(response)
return
}
nextAction?.execute(response)
}
funor(action:ChainAction): ChainAction {
nextAction = action
returnthis
}
}
fun Action.asChain(canExecute: (Response) ->Boolean): ChainAction =
In a recent code review one of my colleagues was concerned that part of the code I wrote might cause us problems because of the null object pattern I chose to use.
The code was something like this:
classTask(
valdescription:String,
valassignedTo:AssignedTo
)
sealedclassAssignedTo {
objectNobody:AssignedTo()
classUser(valname:String) : AssignedTo()
}
funmain() {
val buyMilk =Task("Buy milk", AssignedTo.Nobody)
val writePost =Task("Write post", AssignedTo.User("le0nidas"))
print(buyMilk, writePost)
}
privatefunprint(varargtasks:Task) {
tasks.forEach { task ->
val user =when(task.assignedTo) {
AssignedTo.Nobody->"nobody"
isAssignedTo.User-> task.assignedTo.name
}
println("Task '${task.description}' is assigned to $user")
and my colleague was referring to the Nobody usage.
That got me thinking. Can this usage of sealed classes be considered as an implementation of the null object pattern? Also, why is the usage of this design pattern a bad thing?
So, what is this pattern? This pattern is one way to solve the
we don’t want a method to return a null value and force ourselves to check it before usage
my definition ๐
Ok, it might be better to have an example:
Lets say that we have a service that returns the workout we did in a particular date. Each workout has the duration of the exercise and if we did not workout on the given date the service returns null (we’ll pretend for a moment that Kotlin does not have null safety or those great extensions like map, sum etc):
From the example we could easily conclude that this pattern is quite helpful. Right?
Well, as everything in our industry: it depends! When we need a way to have some default values so that our calculations won’t crash and burn then it is a good choice. But if we use it in ways that we hide things we might end up in false successes.
For example, lets assume that we have a storage interface and a factory function that returns the proper storage implementation depending on a given value:
If something is not configured well we might end up using, through out our entire project, the NullStoragethinking that we have saved everything when in reality we lost our data!
In case you missed it there is a space in the cloud-type so no matter how many times we ask for the “cloud”-storage we will get the null-storage back. Silly example with a silly mistake but imagine what could go wrong in real projects!
So, back to the code that started all this
First thing is first:
Q: Is this usage of sealed classes an implementation of the null object pattern?
A: No. The Nobody object is just a representation of a valid state for the AssignedTo concept and does not provide any default values or hides any method usage.
Q:Can sealed classes be used for implementing the null object pattern?
A: Yes. Sealed classes can help us in having many values for one concept but the fact that we can use full fledged classes, that can also inherit a bunch of things, is quite powerful and can be easily misused. I don’t see anything stopping us from implementing the storage-example using sealed classes so we just need to think things carefully.
And now the twist:
As is, the code does not implement the design pattern but with a small change we can make it not only to implement it but to do it badly too! We’ll just move the name property to the parent and have Nobody provide an empty value:
classTask(
valdescription:String,
valassignedTo:AssignedTo
)
sealedclassAssignedTo(valname:String) {
objectNobody:AssignedTo("")
classUser(name:String) : AssignedTo(name)
}
funmain() {
val buyMilk =Task("Buy milk", AssignedTo.Nobody)
val writePost =Task("Write post", AssignedTo.User("le0nidas"))
print(buyMilk, writePost)
// Task 'Buy milk' is assigned to
// Task 'Write post' is assigned to le0nidas
}
privatefunprint(varargtasks:Task) {
tasks.forEach { task ->
println("Task '${task.description}' is assigned to ${task.assignedTo.name}")
Q:Why is this bad? It does not hide anything, it provides a default value and to be honest we just need to pass “nobody”, instead of an empty string, to the super constructor.
A: Well, this time it depends on where we use the pattern and not how. If this code is part of our core layer then we are allowing this layer to decide on presentation issues! It should be the presentation layer that will check what value the assignedTo has and print “nobody”!
Our previous implementation of AssignedTo was forcing us to make the distinction between all of its values so we did not have much of a choice but to let the print function decide.
In conclusion
Think twice before using the null object pattern and, if you are in a team, try to put code reviews as part of your work flow. It is always good to have a fresh pair of eyes look at your code and even better to have a few people to discuss (and argue) about your choices.
Trying to explain something will make you understand it better!