Favour the Typesafe Enum pattern in Apex

A frequent need is to have behaviour that varies according to a value such as picklist String value. The simplest approach is to pass that value around as a String and to compare the value against inline String constants or static final Strings declared somewhere. But weaknesses of this approach are:

  • Not typesafe – other values can be accidentally introduced
  • Not self-documenting – using the type String says nothing about the domain/purpose
  • Any related attributes or logic has to be added inline or in a separate class using if/elseif/else chains

(Java had the enum types language feature added in version 5 to address these problems; Apex’s enum language feature is basic in comparison.)

A pattern that addresses these problems and was often recommended in Java before enum types were added is the “typesafe enum” pattern. Here is an Apex example:

public class Status {

    private static final Map<String, Status> STATUSES = new Map<String, Status>();
    
    public static final Status PENDING = new Status('Pending', 5);
    public static final Status OPEN = new Status('Open', 30);
    public static final Status CLOSED = new Status('Closed', 60);
    
    public static Status valueOf(String name) {
        return STATUSES.get(name);
    }
    
    public static Status[] values() {
        return STATUSES.values();
    }
    
    public String name {get; private set;}
    public Integer days {get; private set;}
    
    private Status(String name, Integer days) {
        this.name = name;
        this.days = days;
        STATUSES.put(name, this);
    }
    
    public override String toString() {
        return name;
    }
     
    public Boolean equals(Object o) {
        if (o instanceof Status) {
            Status that = ((Status) o);
            return this.name == that.name;
        }
        return false;
    }
    
    public Integer hashCode() {
        return name.hashCode();
    }
}

Code using it looks like this:

    private void method1() {
        ...
        method2('xyz', Status.OPEN);
        ...
    }
    
    private void method2(String v, Status s) {
        ...
        Date d = Date.now().addDays(s.days);
        String message = 'Status is ' + s;
        ...
    }

To convert a picklist value (or other String) to an instance of this object:

Status status = Status.valueOf(picklistValue);

To iterate over all the values:

for (Status status : Status.values()) {
    ...
}

Extra methods can be added and so can extra data values.

Note that the equals/hashCode methods are only needed if references are serialized. Examples of where that can happen are in Visualforce’s view state or when Database.Stateful is used in a Batchable.

PS

This approach can be used for run-time loaded values (e.g. using describe calls on picklist fields or loading from JSON) though that of course means not having constants for each value. If that is done I strongly recommend lazy loading to avoid filling the debug log with entries for cases where the full set of values are not needed.

Advertisements

Breaking managed package dependencies

We have several managed packages with customers sometimes installing just one of them and other times several of them (depending on the set of features they want). Calls can be needed between the packages: global interfaces and classes defined in one package – lets call it B – are called from another package – lets call it A.

But once the direct calls are added and new managed package versions created, A cannot be installed without B first being installed because the platform’s package dependency approach is rigid and enforced at installation time. This dependency (illustrated with UML dependency notation) is not what we want:

Dependencies

So how to allow managed package A to call managed package B without the fixed dependency? The trick is to have no compile-time dependency between A and B but instead to introduce a third entity C (that could be another managed package or non-namespaced local code) that calls are made through where dependency on both A and B is not a problem:

Broken

Here is an example of the pattern. The API used to illustrate the pattern has a single method to send an SMS (text) message.

The implementation (that we want to use from other packages) is in package B:

global interface Sms {
    global void send(String number, String message);
}

global class Factory {
    global static Sms createSms() {...}
}

In package A the interface is duplicated together with a mechanism to register a type that implements the interface. The package A code references only this interface and class:

global interface Sms {
    global void send(String number, String message);
}

global class Factory {
    global static Sms createSms() {
        // Use name of type from custom setting
        String s = ...;
        Type t = Type.forName(s);
        return (Sms) t.newInstance();
    }
    global static void registerSmsType(Type t) {
        // Store name of type in a custom setting
    }
}

...
    Factory.createSms().send('38383', 'PREZ');
...

Then in C, a class is implemented that has the signature defined in A and delegates to B to do the work. At some point this class name must be registered with A. If C is a managed package that could be in an InstallHandler or it could be a manual configuration step:

global class Sms implements A.Sms {
    global void send(String number, String message) [
        B.Factory.createSms().send(number, message);
    }
}

...
    A.Factory.registerSmsType(Sms.class);
....

So A and B remain independent and C is the “glue” that connects then together. A can be installed on its own and so can B. If they are both installed, then adding the C managed package or non-namespaced local code allows the call between the packages to be made.

PS

Stephen Wilcox’s Apex Calls Between Independent Packages describes the same pattern.