Wednesday, January 2, 2013

Class Clusters in Java

Introduction

Every good Objective-C programmer knows (or at least should know) what is a class cluster. Indeed this term is frequently mentioned in Apple documentation. It is one of the core patterns of Cocoa and Cocoa Touch. Cocoa Design Patterns book – a must-read for every Objective-C developer – even has a dedicated chapter about class clusters. Is this notion relevant outside Objective-C world? Yes, it is. The following sections describe class clusters in the Java programming language context.

Class Clusters Explained

Basically a class cluster is an abstract class that groups a set of concrete private subclasses together to provide a simplified interface to the user through the abstract class. The main reason for this is to instantiate an appropriate or a more efficient implementation depending on certain user needs. Let’s look at an example to make this more specific. Assume you need to implement Resource class which would allow its users to load and work with different kinds of resources. At first shot you want to add support for http, ftp and file system resources. Using class clusters this example could be implemented as follows:
public abstract class Resource {
    private static final String PREFIX_FILE_RESOURCE = "file://";
    private static final String PREFIX_HTTP_RESOURCE = "http://";
    private static final String PREFIX_FTP_RESOURCE = "ftp://";

    private String url;

    protected Resource(String url) {
        this.url = url;
    }

    protected String getResourceUrl() {
        return url;
    }

    public static Resource getResource(String url) {
        final Resource result;

        if (url.startsWith(PREFIX_FILE_RESOURCE)) {
            result = new FileResource(url);
        } else if (url.startsWith(PREFIX_HTTP_RESOURCE)) {
            result = new HttpResource(url);
        } else if (url.startsWith(PREFIX_FTP_RESOURCE)) {
            result = new FtpResource(url);
        } else {
            throw new IllegalArgumentException(String.format(
                    "unknown protocol for resource: %s", url));
        }

        return result;
    }

    public abstract InputStream getAsStream();

    private static class FileResource extends Resource {
        protected FileResource(String url) {
            super(url);
        }

        @Override
        public InputStream getAsStream() {
            // open and return InputStream for file resource
        }
    }

    private static class HttpResource extends Resource {
        protected HttpResource(String url) {
            super(url);
        }

        @Override
        public InputStream getAsStream() {
            // open and return InputStream for http resource
        }
    }

    private static class FtpResource extends Resource {
        protected FtpResource(String url) {
            super(url);
        }

        @Override
        public InputStream getAsStream() {
            // open and return InputStream for ftp resource
        }
    }
}
Let’s break up this listing. The Resource class contains the following two methods intended for interfacing with client code:

  • getResource() – used to construct a Resource instance from the URL.
  • getAsStream() – used to get resource contents as InputStream.

Each type of resource is implemented as a private static nested Resource subclass. They are FileResource, HttpResource and FtpResource for accessing file, http and ftp resources respectively. For convenience Resource class also has a private instance field url plus protected constructor and getter to facilitate its usage by subclasses. Method getResource() is the most interesting part of the listing. Its code is repeated below for convenience:
public static Resource getResource(String url) {
    final Resource result;

    if (url.startsWith(PREFIX_FILE_RESOURCE)) {
        result = new FileResource(url);
    } else if (url.startsWith(PREFIX_HTTP_RESOURCE)) {
        result = new HttpResource(url);
    } else if (url.startsWith(PREFIX_FTP_RESOURCE)) {
        result = new FtpResource(url);
    } else {
        throw new IllegalArgumentException(String.format(
                "unknown protocol for resource: %s", url));
    }

    return result;
}
There are a few important points to note about this method:

  • The most suitable Resource subclass is selected at runtime depending on certain criteria. Client’s code doesn’t have a compile-time dependency on a certain implementation but rather works in terms of abstract Resource class.
  • Client’s code is unaware of multiple implementations existence. Hence private Resource subclasses can be safely added, merged together or removed without affecting client’s code as long as the new version of abstract Resource class is kept consistent in the behavior with the previous one.

Abstract Resource class and its private nested subclasses (FileResource, HttpResource and FtpResource) effectively form a class cluster. At first glance a class cluster might resemble Abstract Factory design pattern. There are certain similarities indeed, but these patterns have different purposes. Abstract Factory is intended to provide clients with a set of products from the same family (e.g. UI widgets implemented using GTK+, DAO objects implemented using JDBC, etc.). Each concrete factory matching the abstract one instantiates objects from its own product family. Class cluster on the other hand is built around a single abstract class. Its purpose is to group together multiple private subclasses and provide access to them through the interface of the common abstract class. The following program gives a simple example of Resource class usage:
public class ClassClusterExample {
    public static void main(String[] args) {
        final String httpResourceUrl = "http://www.google.com";
        final String fileResourceUrl = "file:///C:/web/resource.txt";
        final String ftpResourceUrl = "ftp://fake.host/resource.txt";

        Resource httpResource = Resource.getResource(httpResourceUrl);
        Resource fileResource = Resource.getResource(fileResourceUrl);
        Resource ftpResource = Resource.getResource(ftpResourceUrl);

        System.out.println(httpResource.getClass().getSimpleName());
        System.out.println(fileResource.getClass().getSimpleName());
        System.out.println(ftpResource.getClass().getSimpleName());
    }
}
This program unsurprisingly will print the following output:

HttpResource
FileResource
FtpResource

Class Clusters in JDK

It turns out that class clusters can be found in JDK itself (at least when it comes to Oracle’s JDK 5 and 6). A typical example is EnumSet<E>. As you might know this class is the most effective way to store sets of enums in Java and it cannot be instantiated directly. You should use static methods to build EnumSet<E> objects. If enumeration used in conjunction with EnumSet<E> contains more than 64 elements then JumboEnumSet<E> will be instantiated. Otherwise RegularEnumSet<E> will be used. Both RegularEnumSet<E> and JumboEnumSet<E> are package-private subclasses of EnumSet<E> and they provide access to their functionality via EnumSet<E> interface. These three classes taken together form a class cluster. Of course everything described in this section is an implementation detail and it might change in the future releases of Oracle’s JDK.

Conclusion

As you can see class cluster is a useful design pattern which goes beyond Objective-C world. It is a convenient way to group together multiple implementations of the same notion and provide access to them through simplified common interface. This approach helps to decouple client’s code from a particular implementation and gives to implementer the flexibility for future modifications.

Thanks for reading,
See you soon!

No comments:

Post a Comment