August 16, 2015

Java enums best practices by example (part 3 of 3)


Pages: 1 2 3

Enum as a template

Let’s examine again this API which makes use of enum input parameters, thus adhering to “best practices”:
public static enum Format {
    PDF, EXCEL, CSV;
}
    
public static void formatFile(File file, Format format) {
    // do something based on format
}
Now, you may argue that this still violates the Open / closed principle: As we are constrained to using an enum value of Format, extending the formatFile(…) method for additional format support forces us to extend the Format enum.

If you really need this flexibility, but still keep the API clear and “don’t make me think” compliant, I propose a compromise to keep the enum API for simple cases, but keep it open for extensions.

Let’s assume we want to be able to provide a custom BaseFormat, but have a pre-defined set of formats available as an enum:
public static abstract class BaseFormat {
    public abstract void formatFile(File file);
}

// BaseFormat implementations…

public static enum Format {
    PDF(new PdfFormat()),
    EXCEL(new ExcelFormat()),
    CSV(new CsvFormat());
    
    public final BaseFormat format;

    private Format(BaseFormat format) {
        this.format = format;
    }
    
    public void formatFile(File file) {
        format.formatFile(file);
    }
}

public static void formatFile(File file, BaseFormat format) {
    format.formatFile(file);
}

public static void formatFile(File file, Format format) {
    formatFile(file, format.format);
}
Now, because an enum cannot extend a class, you would use it as a wrapper. Consider the two flavors of formatFile(…): You can either provide a Format enum (i.e. choose from a predefined format for convenience) or provide a custom implementation of a BaseFormat.

If you are actually able to extract the desired functionality to an interface, the templating is a lot simpler because both the enum and a hypothetical custom extension can just implement the same interface:
public static interface BaseFormat {
    public void formatFile(File file);
}

public static enum Format implements BaseFormat {
    PDF {
        @Override
        public void formatFile(File file) {
            System.out.println("PDF format");
        }
    }, EXCEL {
        @Override
        public void formatFile(File file) {
            System.out.println("Excel format");
        }
    }, CSV {
        @Override
        public void formatFile(File file) {
            System.out.println("CSV format");
        }
    };
}

public static void formatFile(File file, BaseFormat format) {
    format.formatFile(file);
}

Assuring enum value order

I’d like to discuss one last “enum best practice” here which covers these two quite common special cases:
  • you want to assure an inner order of the enum values;
  • you want to assure that for a given enum value, it matches a given #ordinal() number.
For example let’s imagine we have this enum:
public static enum Direction {
    NORTH, EAST, SOUTH, WEST
}
It’s desirable to have the values in this very order (N-E-S-W) when presented in a UI, and to make sure that our fellow developers don’t accidentally mess up the order when they modify / extend the enum (e.g. add intermediary directions).

You can assure this by simply adding an explicit ordinal field:
public static enum Direction {
    NORTH(0),
    EAST(1),
    SOUTH(2),
    WEST(3),
    ;
    
    int n;

    private Direction(int n) {
        this.n = n;
    }
}
In a unit test, you can then test that developers don’t forget to count up the field if they insert a new enum value.
@Test
public void testOrder() {
    Direction[] values = Direction.values();
    for (int i = 0; i < values.length; i++) {
        assertEquals(i, values[i].n);
    }
}
For enums with many values, this also helps the developer identifiying an enum value for a given ordinal if some circumstances force him to manually do this. Of course, use this technique of deliberately introducing redundancy only when really needed!

Conclusion

As I have written in the introduction, I think that enums really are a highly useful and powerful part of Java with the potential to significantly increase code readability and maintainability – if properly used. They both require and foster understanding the all-important difference of compile time and runtime checking, and when to apply which of them.

I hope this article made you discover new enum techniques or confirmed that you’re already doing it right. If you see potential problems with one of these practices or if you think that something is missing – please share your thoughts in the comments section below.

Update August 22, 2015: Read on: JSF / JEE enums best practices


Pages: 1 2 3

No comments:

Post a Comment