Scala & the Proxy design pattern
In this article we will focus on the Proxy design pattern as defined by the GoF.
In short, the definition of the "Proxy" pattern could be reduced to "something for something". That's why it will be presented here as the Quid Pro Quo pattern, "Quo" meaning the entity that will be controlled/wrapped by the "Quid" entity (i.e. the proxy itself).
In order to implement the Proxy pattern, here is what is needed first:
Here comes the beauty of the Scala language ... Let's use the implicit conversion feature:
This code allows to automatically add a createProxy method to any trait or class. It applies an implicit conversion to a QuidPro[Quo] instance (as soon as the compiler meets a createProxy invocation on a Quo instance).
Note that the scope of the createProxy method can be reduced by changing the type of the toProxy implicit definition (e.g. toProxy[Quo <: AcceptProxy] where AcceptProxy is some marker trait).
By the way, this technique of enriching an abstraction without specifying extensions (using inheritance) corresponds to the Decorator design pattern. We have to admit that the Scala answer to this pattern is unique and quite efficient.
In order to get the whole picture, we need to see the implicitly generated instance of QuidPro[Trait1] as a QuidProTrait1 instance. This can be achieved using an additional implicit conversion: Then, the following code: will output this:
Note that we can also parametrize the Quid, by using the implicit parameter feature. That's what is done in the following implementation of Trait2 (which does the same as Trait1 but manages an additional "creator name" field.
Here is the complete example: You should obtain something like this:
The execution of this example should result in the following output:
In short, the definition of the "Proxy" pattern could be reduced to "something for something". That's why it will be presented here as the Quid Pro Quo pattern, "Quo" meaning the entity that will be controlled/wrapped by the "Quid" entity (i.e. the proxy itself).
Using implicit conversions
Let's see the benefit of the Scala language features when applied to such a useful pattern...In order to implement the Proxy pattern, here is what is needed first:
-
A generic implementation of the QuidProQuo concept: a trait called QuidPro defined over the Quo type and that basically wraps a Quo instance:
-
The "Quo" entity (i.e. the one that we would like to be controlled by a proxy). For instance, this awesome trait:
- And finally the implementation of the proxy for Trait1. Let's call it QuidProTrait1. This trait extends Trait1 (the "Quid" must override all the methods of its nested "Quo" instance). The QuidProTrait1 is a QuidPro[Trait1] as well (explicitly declares that it's a "Quid" for Trait1):
Here comes the beauty of the Scala language ... Let's use the implicit conversion feature:
This code allows to automatically add a createProxy method to any trait or class. It applies an implicit conversion to a QuidPro[Quo] instance (as soon as the compiler meets a createProxy invocation on a Quo instance).
Note that the scope of the createProxy method can be reduced by changing the type of the toProxy implicit definition (e.g. toProxy[Quo <: AcceptProxy] where AcceptProxy is some marker trait).
By the way, this technique of enriching an abstraction without specifying extensions (using inheritance) corresponds to the Decorator design pattern. We have to admit that the Scala answer to this pattern is unique and quite efficient.
In order to get the whole picture, we need to see the implicitly generated instance of QuidPro[Trait1] as a QuidProTrait1 instance. This can be achieved using an additional implicit conversion: Then, the following code: will output this:
Hello World !!
scaladudes.ProxyTestApp$$anon$1@c2ff5 says: "Hello World !
true
scaladudes.ProxyTestApp$$anon$1@c2ff5 says: "Hello World !
true
Note that we can also parametrize the Quid, by using the implicit parameter feature. That's what is done in the following implementation of Trait2 (which does the same as Trait1 but manages an additional "creator name" field.
Here is the complete example: You should obtain something like this:
Hello World !!
Hi Everybody !!
scaladudes.ProxyTestApp$$anon$1@166a22b says: "Hello World !!"
scaladudes.ProxyTestApp$$anon$2@120cc56 says: "Hi Everybody !!" (created by a dude)
Hi Everybody !!
scaladudes.ProxyTestApp$$anon$1@166a22b says: "Hello World !!"
scaladudes.ProxyTestApp$$anon$2@120cc56 says: "Hi Everybody !!" (created by a dude)
Alternative: back to Java Proxy
Relying on the Scala's interoperability with Java, we can use the Java Proxy (from package java.lang.reflect). This is the purpose of the following code snippet:The execution of this example should result in the following output:
>>> scalaInvocationHandler: method "sayHello" intercepted.
Quo says: "Hello Scala Dudes !!"
>>> scalaInvocationHandler: method "say" intercepted.
Quo says: "Hi Folks !!"
Quo says: "Hello Scala Dudes !!"
>>> scalaInvocationHandler: method "say" intercepted.
Quo says: "Hi Folks !!"
As a conclusion ...
... I'd only say that the Scala translation of the Proxy pattern is not that different from the Java one, but the implicit conversion and implicit parameter features make it a bit more elegant...- Tags:
