In OOP there exists a design pattern for extending functionality of methods via a form of composition, this pattern is commonly referred to as the Decorator Pattern. You can find this design pattern in your dusty copy of the gang of four. In case you don’t have a copy handy here’s the wikipedia definition.
In object-oriented programming, the decorator pattern (also known as Wrapper,
an alternative naming shared with the Adapter pattern) is a design pattern that
allows behavior to be added to an individual object, either statically or
dynamically, without affecting the behavior of other objects from the same
To model this design pattern in Scala with a user service that we would want to decorate with caching and logging. We would first define a common interface as an abstract trait. We would then implement the base user service such that it implements the abstract trait and all the decorators such that they also implement the abstract trait. You would then compose them together via dependency injection. This would look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
In the decorator pattern, the composition happens at the value level when we inject the obect to decorate into the decorator. Scala provides a means for us to do this at the type level through a feature known as Stackable Traits. For the most part Stackable Traits are exactly what they sound like, that’s traits that allow you to compose methods by stacking them on top of each other.
Stackable methods in scala must be identifed with the keywords
def keyword. Yes you read that right and it’s not a typo. It seems
like a weird use of the
abstract keyword, but it is correct. This combination
of keywords give you access to a
super pointer in your method which reference
the next class or trait in the stack.
Modeling our previous implementation of the decorator pattern using Stackable Traits instead would look something like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
Now we’ll know if our decorators are implemented properly if our program type checks. In the example above were also leveraging existential types via the Cake Pattern in lieu of constructor injection.
The stacked traits are executed from right to left such that in our example,
LoggingUserService will run first and it’s invocation of
will pass control to the
CacheUserService that when it invokes it’s
super.getUser() will pass control to