Search code examples
javadesign-patternsenumsextensibilitysoftware-quality

How to avoid changing many parts of code when adding new enum value, thus providing easier extensibility?


I am trying to make my code easier to extend in terms that a little change will not affect much other code.

I have an enum MyEnum, which values might increase in future.

Then, there are classes that holds instance of it and has many behaviors affected by that enum's concrete value. In other words, there are many places where I switch over it's value.

public enum MyEnum
{
    FIRST, SECOND, THIRD, FOURTH;
}


public class A
{
    private MyEnum myEnum
    
    public A(MyEnum myEnum)
    {
        this.myEnum = myEnum;
    }
    
    // as you will see, there is a lot of switching over its value
    public void boo()
    {
        switch(myEnum)
        {
            case FIRST: // do smtng
            case SECOND: // do smthing else
            case THIRD: // do smthing else
            case FOURTH: // do nice thing
        }
    }

    public int goo()
    {
        switch(myEnum)
        {
            ...
        }
    }
   

    public AnotherObject foo()
    {
        switch(myEnum)
        {
            ...
        }
    }
}

public class B
{
    private MyEnum myEnum
    
    public B(MyEnum myEnum)
    {
        this.myEnum = myEnum;
    }
    
    public double doo()
    {
        switch(myEnum)
        {
            ...
        }
    }

    public void soo()
    {
        switch(myEnum)
        {
            ...
        }
    }
   
    public boolean xoo()
    {
        switch(myEnum)
        {
            ...
        }
    }
}

The thing here is that mostly I will need to add new case to all places where we switch over it's value => will need to do many changes to code when I add new enum value.

Did anyone else faced this problem? By now, I guess it is just downside of using enums this way.


Solution

  • Don't bind your code to the enum bind your code to an interface. Then have your enum provide the standard implementations of the interface.

    public interface RibbonColor {
       public String getColor();
    }
    
    public enum DefaultRibbonColors implements RibbonColor {
       FIRST() {
          public String getColor() {
            return "blue";
          }
       }, 
       SECOND() {
          public String getColor() {
            return "red";
          }
       },
       THIRD() {
          public String getColor() {
            return "white";
          }
       },
    }
    
    public class Awards {
    
       private List<RibbonColor> ribbons;
    
       public Awards(List<RibbonColor> ribbons) {
          this.ribbons = ribbons;
       }
    
       public RibbonColor awardFor(int placeIndex) {
          if (placeIndex < ribbons.length()) {
            return ribbons.get(placeIndex).getColor();
          }
          return null;
       }
    
    }
    

    Notice that now you can easily add in a new list of all the default Awards by

    Awards awards = new Awards(Arrays.asList(DefaultRibbonColors.values()));
    

    while you could also create custom awards sets.

    List ribbons = new ArrayList<RibbonColor>();
    ribbons.addAll(DefaultRibbonColors.values());
    ribbons.addAll(ExtendedRibbonColors.values());
    ribbons.addAll(new BlackAndPinkPolkaDotRibbonColor());
    
    Awards awards = new Awards(ribbons);
    

    The key is to never make the code actually depend on the enum because you can't modify an enum without recompiling, and that triggers the need to search for switch statements that lack default: blocks or more explicit settings for the added value.

    Objects are "code and data written together" while procedural code is "code and data managed separately" The switch statement puts the logic "code" outside of the type "data" and is a programming mistake in 100% insanely object oriented design. That said, it is often useful, and people still structure programs in Java and other languages in ways that effectively separate code from data (object that hold all the data, and "object routines" that manipulate another object's data. This kind of separation of an object's data from its routines is an antipattern called anemic objects.

    Enums are Objects so don't be afraid to put methods in them! Give them interfaces where they should be replicable, and avoid switch statements because it's probably a good sign that the logic should be in the thing you are switching on (provided it is an Object).