I've always loved the Template Method design pattern. If there were ever a good reason to use implementation inheritance (i.e. an abstract class) this seems like it. I find it natural to want to define the outline of an algorithm in a parent class, and then provide the implementation details within concrete subclasses. A while back, Alex Miller provided a great criticism of the pattern, pointing out several deficiencies in it. Particularly, it is sometimes hard to comprehend and maintain as it grows in complexity.
After working more with Groovy, another limitation of how the pattern is traditionally written has become apparent. In multi-step algorithms, where a skeleton algorithm in an abstract class calls several steps in a subclass, each of the steps are tightly coupled to one another. A concrete subclass locks you into using a certain composition of steps at compile time, and as the number of steps grows, the combinatorial explosion of subclasses becomes unmanageable. An example might make this clearer.
Consider an encryption algorithm composed of two steps (double encryption must be more secure, right?). Say you want step one to reverse the text of a message, and step two to perform a rot13 operation on it. (Okay, okay, this is really a cipher and not an encryption, but that's not the point). In this scenario you would define the algorithm template in an abstract class, say TwoPhaseEncryption. Then you would create a subclass with two methods, one to perform the reverse and one to perform the rot13.
Here is the traditional Java code, with the subclass implemented as an inner class for brevity:
public abstract class TwoPhaseEncryption {
public String perform(String text){
return phase2(phase1(text));
}
protected abstract String phase1(String text);
protected abstract String phase2(String text);
public static final class ReversingRot13 extends TwoPhaseEncryption {
protected String phase1(String text) {
return new StringBuffer(text).reverse().toString();
}
protected String phase2(String text) {
String result = "";
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c >= 'a' && c <= 'm') c += 13;
else if (c >= 'n' && c <= 'z') c -= 13;
else if (c >= 'A' && c <= 'M') c += 13;
else if (c >= 'N' && c <= 'Z') c -= 13;
result += c;
}
return result;
}
}
}
I tried to choose a dead simple example, but the rot13 method is still 10 lines of code. The rest of the class should be pretty familiar, though. The abstract TwoPhaseEncryption provides a perform(String) method that calls phase1() and phase2() on its subclasses, and then the Rot13Reverser subclass reverses the string as phase one and shifts all the letters 13 positions as phase 2. As is evidenced by the usage, this code does indeed provide symmetrical encryption and decryption:TwoPhaseEncryption encryptor = new ReversingRot13();
String encrypted = encryptor.perform("abcdefghijklmnopqrstuvwxyz");
String decrypted = encryptor.perform(encrypted);
//prints Encrypted: mlkjihgfedcbazyxwvutsrqpon
System.out.println("Encrypted: " + encrypted);
//prints Encrypted: abcdefghijklmnopqrstuvwxyz
System.out.println("Decrypted: " + decrypted);
This is elegant to me. It's easy to understand, especially if you already know what a template method is. It's easy to test, which I place a high value on. But what happens when you want to reverse the two steps... say you needed a Rot13Reverser instead of a ReversingRot13? Or maybe you have a new DES encryption step, and now you need all the possible combinations: a Rot13DESer, a DESRot13er, a ReversingDES, and a DESReverser. Do you really want 6 subclasses? (Your answer is no). How about adding one more encryption type? That's too many types.Please don't be deceived by the simplicity of this example! This code shows all of the steps being homogenous, they can be mixed and matched any which way, so a composition might be a better answer. But oftentimes the steps aren't homogenous. Perhaps you have an algorithm with a common setup-run-teardown phase, where each step can't be substituted with one another, yet different run phases might be used with different setup/teardown combinations. There are times where a template method is the desired pattern. But I'm here to tell you that subclassing probably isn't the desired implementation.
Let's see how you would program a template method in Groovy, which provides simple and easy to use closures as part of the language.
class TwoPhaseEncryptor {
def phase1;
def phase2;
TwoPhaseEncryptor(phase1, phase2) {
this.phase1 = phase1;
this.phase2 = phase2;
}
//perform two phase encryption
def perform = { text ->
return phase2(phase1(text));
}
}
Seriously? That's it? Sort of. This is just a class that takes two closures (the phases) and runs the supplied text through each in the perform() method. But the implementation of the algorithm can be done whereever you want. In my example I do it in the client script that uses the TwoPhaseEncryptor, but they could be defined on a common class and then assembled by the client as need be://closure that reverses a string
reverser = { return it.reverse() }
//closure that performs rot13 substitution on a string
rot13 = {
def rot13Array = it.collect {
if (it >= 'A' && it <= 'M') return (char)(it.charAt(0) + 13);
if (it >= 'N' && it <= 'Z') return (char)(it.charAt(0) - 13);
if (it >= 'a' && it <= 'm') return (char)(it.charAt(0) + 13);
if (it >= 'n' && it <= 'z') return (char)(it.charAt(0) - 13);
return it; //out of range
}
def result = ""
rot13Array.each {
result += it;
}
return result
}
encryptor = new TwoPhaseEncryptor(reverser, rot13)
//encrypt the alphabet
def encrypted = encryptor.perform("abcdefghijklmnopqrstuvwxyz")
//decrypt the encrypted alphabet
def decrypted = encryptor.perform(encrypted)
println "Encrypted: " + encrypted
println "Decrypted: " + decrypted
So in this example, the first closure takes the place of the phase1() Java method, and the second closure takes the place of the phase2() java method. The phase1() and phase2() methods are then simply passed to the TwoPhaseEncryptor as parameters at runtime, rather than provided by the concrete subclass.At first glance, the code doesn't look as clean to me. I like the way Java declares the intent of the algorithm by making you declare abstract methods, and all the code for the phases are in one place, making it easy to take it all in quickly. But the Groovy way has a much larger advantage: low coupling.
What needs to change in the Groovy version if you want to do the rot13 first instead of second? You switch the order of the parameters. Hmmm, that was easy. What needs to change to introduce a DES reverser? Well, you write the method and then pass it as a parameter. Where is the class explosion? Where are the multitudes of types? They aren't there! Don't like how the calling code needs to know about the phases? Move the phases to a helper class, or better yet, use Spring to inject your calling code with a constructed TwoPhaseEncryptor.
The Gang of Four Design Pattern book was written with C++ users in mind, so the high coupling between the template method and its implementations wasn't immediately apparent. Having written quite a few template methods in C++ myself, it wasn't until I discovered Groovy that I saw the Path to the Better Way. As Neal Ford argues, many of the design patterns are recipes, they show you how to code around common issues using the languages of the day. But they are seasonal recipes, and as languages come and go, the recipes are going to change while design problems won't. And at this point, my recipe for template methods no longer requires subclassing because I can better achieve the object oriented principles I strive for (low coupling, high cohesion) by using object composition and dependency injection.
And if anyone can write a better rot13 method in Groovy then please post it!
11 comments:
You can use the modulo operator to substantially shorten your Rot13.
What you're looking for there is either A) the "Decorator" design pattern, or B) functional programming.
I love groovy myself, but you could design the phases using the Strategy pattern if you wanted to stick to java.
I feel like all these "patterns" really start to look the same once you introduce closures and remove static types. To achieve variation of control in Java, the design patterns often contain subclassing. In Groovy it is easier for me to give objects closures for the methods that would normally be overridden. I think that if you take the UML diagrams for the GoF patterns, and you remove the interfaces and add closures, then the diagrams will be mostly the same. I'll try to work on this thought some more this week.
Thanks for posting a comment!
I don't get what point you're trying to make:
So you've used the Template Method pattern in Java and in Groovy you use a Strategy.
Subclassing has a bad reputation in Java anyway - you should have used a Strategy in Java in the first place.
@sven
Exactly correct. Things like the Template Method may not have a place in Groovy.
Whether or not Template Method is useful in Java is another discussion. Strategy is certainly an alternative, but perhaps a Groovy strategy pattern wouldn't look that similar to the GoF version either.
Hi Hamlet.
Nice post.
You wrote:
> And if anyone can write a better rot13 method in Groovy then please post it!
Here's one I think could be a good one.
However, this has redundant code in it, so with a little refactoring I think, that this one's better.
Bernd
Nice post! Here's my template method blog if anyone is interested: Patterns I Hate #2: Template Method.
@Alex
Sorry about the lack of link. It should have been there but I fat fingered an extra quote and it never got rendered :(
No problem. By the way, I pitched an abstract for JavaOne 2008 about design patterns based on that series of blogs. Probably won't make it, but you never know.
Picking on the example a little bit I would have done this with an interface and one concrete class:
interface Encryptor {
String encrypt(String msg);
}
class MultipleEncryption implements Encryptor {
private Encryptor[] encryptors;
public MultipleEncryptiont(Encryptor... encryptors) {
this.encryptors = encryptors;
}
public String encrypt(String input) {
String mutated = input;
for (Encryptor encryptor : encryptors) {
mutated = encryptor.encrypt(mutated);
}
return mutated;
}
}
The weird part of that is the for loop where you assign the result of an operation back to what was being operated on. That could be "improved" with a mutatable String class, but looks better in groovy / scala with a forEach type loop.
BTW, Raganwald is hot on your heels:
Another problem with problems with design patterns
Welcome to the club. :-D
Post a Comment