Monday, January 29, 2007

Thinking Differently with Design Patterns, Java and Accidental Complexity

Bob Lee has posted a very useful tip on performant singletons. Have a look if you are writing concurrent Java applications for the enterprise. From plain old synchronization to DCL to IODH - I guess this pattern implementation has come a full circle in Java. If you are into Java, follow this idiom .. possibly this is the best you can get for a fast, thread-safe, lazily loaded singleton with JLS guarantee.

This post is not about singletons, although we start with the code for implementing the same pattern based on post Java 5 specifications :


public class Singleton {
  static class SingletonHolder {
    static Singleton instance = new Singleton();
  }

  public static Singleton getInstance() {
    return SingletonHolder.instance;
  }
}



Everytime you need a singleton in your application, make use of the above idiom. Unless you are coding a trivial application, very soon you will feel the spiralling cost of the growing number of classes. This is a basic problem of many of the Java idioms and design patterns when we try to force functional programming paradigms through nested classes or anonymous inner classes. This is, what many refer to as accidental complexity in modeling, which, very often, tends to overshadow the domain complexity, thereby resulting in lots of glue codes.

I am not in the league to snub Java. Myself, I am a Java programmer and have been doing OO with Java and C++ for the last 10 years. The GOF design patterns book has been my bible and all my thoughts have, so far, been soaked in the practices and principles that the book professes. With Ruby and Lisp, I have started to think about programming a bit differently. And as Alan Perlis has epigrammed, "A language that doesn’t affect the way you think about programming, is not worth knowing".

Singleton Pattern Elsewhere


require 'singleton'
class Foo
  include Singleton
end



That's it ! Ruby's powerful mixin functionality automatically makes our class a singleton - the new method is rendered private and we get an instance method for getting the object.

Another new generation language Scala offers the object keyword for implementing singletons.


object SensorReader extends SubjectObserver {
  // ..
}



In this example the declaration for SensorReader creates a singleton class that can have a single instance.

Design Patterns in Java and Accidental Complexity

As a language, Java does not offer powerful functional abstractions that Ruby, Scala or Lisp provides. Hence many design patterns which look invisible in these languages stand out as elaborate design constructs in Java. These add to the *accidental complexity* in a Java application codebase and often turns out more difficult to manage than the *actual complexity*, which is the complexity of the domain that you are trying to model. Technologies like aspects and metadata based annotations are attempts to improve the abstraction level of the Java programming language. Unfortunately these can never give the programmer that seamlessness in extending the syntax of the core language. The programmer will never be able to program bottom up or carve out a DSL as elegant as Rails using Java. Norvig has an excellent presentation on how dynamic languages make many of the GOF patterns invisible within them. The presentation illustrates how macros can make the implementation of Interpreter design pattern easier, method combinations can make Observers seamless and multi-methods can ease the implementation of Builder design pattern. Mark Dominus has posted a very thought provoking essay that concludes that patterns are signs of weakness in programming languages. What he means is that, languages where we need to write repetitive code to implement solutions to recurring problems lack in the abstraction power. The very fact that we have to repeat the code for implementing the Strategy design pattern for every instance of application of the pattern in Java, implies that the language lacks the extensibility to imbibe the design construct within itself. And by doing so, the implementation inherits lots of *accidental complexity* or yellow marker as part of the codebase. OTOH, in a typical functional implementation, the strategy is a simple variable whose value is a function, and with first class functions, the pattern is invisible.

The singleton pattern implementation in Java, despite providing a performant solution, also contributes to this accidental complexity of the codebase. This is more true for many other pattern implementations in Java or C++.

Thinking Differently

I cannot imagine myself writing about lack of abstractions in OO languages had I not been exposed to Ruby, Scala or Lisp. I realize the truth in the 19th epigram of Alan Perlis - these languages have really affected my thinking on programming at large. Now I can appreciate why Steve Yeggey thinks design patterns [in Java] as mostly pounding star shaped pegs into square holes.

4 comments:

Ricky Clarkson said...

Bob's singleton pattern can be made into one class, if there's nothing else in the class, i.e., the instance can be part of Singleton, rather than SingletonHolder.

The reason for not doing this is that the laziness won't happen if you use the class for something else too. As soon as you use Singleton, its static initialisers will run.

Personally I don't care about laziness in this case, so I'd just make the field be part of Singleton as a public static final.

Visitor is a much better vehicle for demonstrating the problems that Java has compared to Lisp.

Unknown said...

You're example of the singleton pattern is incorrect, getInstance() should not be synchronised

Unknown said...

[for ricky]:

Very true. The idea of the post is to introspect how typical implementation of design patterns in Java introduce accidental complexity in the codebase, which often becomes difficult to handle than the domain complexity (or actual complexity). May be the nested class is required for lazy loading, but ideally in an application, the developer always wants to use the most optimized version of implementation (which may be premature optimization as well), which, in this case happens to be Bob's one. I will use it for any singleton implementation if I am writing a performant application in Java. But, u r correct - Visitor would have been the king of accidental complexity in any Java code.

Cheers.
- Debasish

Unknown said...

[for Don]:

I don't find any *synchronized* getInstance() in the code :-(