Java – Design Patterns

Common Design Patterns

The Java API and applications are built using many common design patterns and conventions. Some of them represent original GoF patterns.

  • Singleton
  • Factory
  • Builder
  • Adapter
  • Facade
  • Proxy
  • Observer
  • Command
  • Iterator

Singleton

A Singleton is a class for which we can have only one instance. This is roughly the equivalent of a class having all static methods. Note that the instance of the class is created statically in this case and constructor is private. Let us start by creating a simple Singleton possible:

public class SimpleSingleton {
    private static final SimpleSingleton INSTANCE = new SimpleSingleton();

    private SimpleSingleton() {}

    public static SimpleSingleton getInstance() {
        return INSTANCE;
    }

    public void doSomething() {
        System.out.println("I am simple singleton method.");
    }

    public static void main(String[] args) {
        SimpleSingleton obj = SimpleSingleton.getInstance();
        obj.doSomething();
    }
}
// will print "I am simple singleton method."

Why do we need this pattern if we can just use static methods ?

The main benefit of singleton is in its ability to evolve as an API. In the example below we can provide different functionality when running on Windows or on Mac.

public abstract class PlatformSingleton {
    private static final PlatformSingleton INSTANCE;

    static {
        if(System.getProperty("os.name").toLowerCase().contains("mac")) {
            INSTANCE = new Mac();
        } else {
            INSTANCE = new Windows();
        }
    }

    public static PlatformSingleton getInstance() {
            return INSTANCE;
        }
    
        public abstract void platformMethod();
    
        static class Mac extends PlatformSingleton {
            @Override
            public void platformMethod() {
                // mac implementation
                System.out.println("I am on Mac.");
            }
        }
    
        static class Windows extends PlatformSingleton {
            @Override
            public void platformMethod() {
                // windows implementation
                System.out.println("I am on Windows.");
            }
        }
        public static void main(String[] args) {
            PlatformSingleton obj = PlatformSingleton.getInstance();
        obj.platformMethod();
    }
}
// This will print "I am on Mac."

But there is still a major limitation, we created the instance statically as the class loads, which can be very early in the application and we might not need the class at all. In such cases we might need to create instance lazily, such as:

public class LazySingleton {
    private static LazySingleton INSTANCE;
    private static final Object LOCK = new Object();

    private LazySingleton() {
    }
    //synchronized is slow, so we don't want to surround the whole method.
    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (LOCK) {
                if (INSTANCE == null) {
                    INSTANCE = new LazySingleton();
                }
                return INSTANCE;
            }
        }
        return INSTANCE;
    }

    public void doSomething() {
        System.out.println("I am lazy singleton method.");
    }

    public static void main(String[] args) {
        LazySingleton obj = LazySingleton.getInstance();
        obj.doSomething();
    }
}
// This will print "I am lazy singleton method."

Leave a Reply

Your email address will not be published. Required fields are marked *