Dienstag, 27. November 2007

Using Enums as Proxies/Decorators in Java - forget about switch

Every Java developer knows that you have enums in Java since Java 5.0.

Enums are cool. Especially the ability of getting the right instance of an enum-class by calling the static valueOf()-method with the respective name string makes enums a nice choice in configurations.

In this example I'm using an enum to determine the style of notification for an event or user.


public enum NotificationType {
NO_NOTIFICATION,
EMAIL;
}


While NotificationCommand is a simple interface with implementations like DoNothingCommand, EmailNotificationCommand and SmsNotificationCommand:


public interface NotificationCommand {
void execute(NotificationCmdReceiver rec);
}


Like I said, we want to determine the type of notification by the NotificationType enum.

Most people use enums in switch-statements like this:


public void notify(
NotificationType type,
NotificationCmdReceiver rec) {

NotificationCommand cmd;

switch (type) {
case EMAIL:
cmd = new EMailCommand();
break; // DO NOT FORGET THIS BREAK OR IT WILL BREAK YOUR NECK
case NO_NOTIFICATION:
default:
cmd = new DoNothingCommand();
break;
}

cmd.execute(rec);
}



I myself hate switch/case statements and I think switch/case/break is not OOP-style.
Everytime I use this kind of statement I make mistakes. Often I forget to test some of the several paths of execution, sometimes I forget a break, sometimes I forget the default case and so on.

One problem in particular is refactoring switch-break-statements from or to a version where you use (multiple) returns inside the switch. NEVER DO THIS WITHOUT GOOD UNIT-TESTS!!! (I write from experience...)


So this would be a better version because it eliminates the switch statement.


public enum NotificationType {

NO_NOTIFICATION(new DoNothingCommand()),
EMAIL(new EMailCommand());

NotificationCommand command;

private NotificationType(NotificationCommand command) {
this.command = command;
}

public NotificationCommand getCommand() {
return command;
}
}


You must be aware, that this version does not recreate a new Command-Object for every call. If you want to do so, you have to write something like this:


public enum NotificationType {

NO_NOTIFICATION{
public NotificationCommand getCommand() {
return new DoNothingCommand();
}
},
EMAIL{
public NotificationCommand getCommand() {
return new EMailCommand();
}
};


public abstract NotificationCommand getCommand();
}


In both cases the notify()-method looks like this:


public void notify(
NotificationType type,
NotificationCmdReceiver rec) {

NotificationCommand cmd = type.getCommand();

cmd.execute(rec);
}


Much more simpler, isn't it? (Update: Okay, I have to admit that it actually does not look simpler. But thats because this example simplifies the logic in the different switch-cases a lot. In real world projects there will be more than 2 different enum values (which means more breaks) and there are more lines for each case. Using this technique lets you break up this one method into some smaller ones. To my mind it's much easier to test n different methods with one execution path each than to test one method with n different execution paths.)

But you can do even more simpler. While it is true that all emums in Java inherit from java.lang.Enum and can't extend any other class, developers seem to forget that enums can implement interfaces:


public enum NotificationType
implements NotificationCommand {

NO_NOTIFICATION{
public NotificationCommand getCommand() {
return new DoNothingCommand();
}
},
EMAIL{
public NotificationCommand getCommand() {
return new EMailCommand();
}
};


public abstract NotificationCommand getCommand();

public void execute(NotificationCmdReceiver rec) {
getCommand().execute(rec);
}
}

The enum NotificationType is now something like a decorator / proxy for NotificationCommands. And the notify()-method boils down to only one line:


public void notify(
NotificationType type,
NotificationCmdReceiver rec) {

type.execute(rec);
}


And now you see much clearer that this method could be static. To my mind, often static methods are a signal of poor design and should be refactored. You can inline the last line and remove the notify()-method.

Update: I want to stress that this post actually is more about that enums are able to implement interfaces and it can be useful to do so than it is about not using switch. In fact even if you let your enums implement interfaces you still can use them with switch. But additionally you get all the advantages that comes with it, especially to use your enum transparently.

2 Kommentare:

jeremyrdavis hat gesagt…

Nice post. Made me realize that I don't use enums as effectively as I could. I agree about switch statements too.

cg hat gesagt…

public enum NotificationType
implements NotificationCommand {

NO_NOTIFICATION(new DoNothingCommand()),
EMAIL(new EMailCommand());

private NotificationCommand cmd;

private NotificationType(NotificationCommand cmd) {
this.cmd = cmd;
}

public void execute(NotificationCmdReceiver rec) {
this.cmd.execute(rec);
}
}

Powered By Blogger