Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code duplication in enums inheriting a common interface

I have several enums which comply to a common interface:

    interface TableColumns
    {
      String getColumnName();
      int getColumnIndex();
      ColumnType getColumnType();
      boolean isEditable();
      int getColumnWidth();
    }

A typical implementation is:

enum PointsTableColumns implements TrendTableColumns
{
    POINTS_COLUMN("points_column", false, ColumnType.TEXT, 400, 0);

    private String columnName;
    private boolean editable;
    private ColumnType columnType;
    private int columnWidth;
    private int columnIndex;

    private PointsTableColumns (String columnName, boolean editable, ColumnType columnType,
        int columnWidth,
        int columnIndex)
    {
        this.columnName = columnName;
        this.editable = editable;
        this.columnType = columnType;
        this.columnWidth = columnWidth;
        this.columnIndex = columnIndex;
    }

    public boolean isEditable()
    {
        return editable;
    }

    public int getColumnIndex()
    {
        return columnIndex;
    }

    public String getColumnName()
    {
        return columnName;
    }

    public int getColumnWidth()
    {
        return columnWidth;
    }

    public ColumnType getcolumnType()
    {
        return columnType;
    }
}

I have several implementaions like this (10+), each having multiple and almost different values. Now, the problem is that I see a lot of code duplication here, as the methods in all of the implementation are identical word by word. I know that in Java this is virtually impossible as enums can't extend an implementation. What I need here is a suggestion or a different strategy where this can be done in a cleaner way. Is there already some existing pattern regarding this?

like image 217
abksrv Avatar asked Jul 17 '15 09:07

abksrv


2 Answers

If you can live with one level of indirection, then the approach I present below will reduce repeated code to a minimum.

First, consider the following supplier interface, along with its inner class:

public interface PropertiesSupplier {

    Properties properties();

    public static final class Properties {

        private final int value1;

        private final String value2;

        private final double value3;

        private Properties(int value1, String value2, double value3) {
            this.value1 = value1;
            this.value2 = value2;
            this.value3 = value3;
        }

        public static Properties of(int value1, String value2, double value3) {
            return new Properties(value1, value2, value3);
        }

        public int getValue1() {
            return this.value1;
        }

        public String getValue2() {
            return this.value2;
        }

        public double getValue3() {
            return this.value3;
        }

        @Override
        public String toString() {
            return "Properties [value1=" + this.value1 + ", value2=" + this.value2 + ", value3=" + this.value3
                    + "]";
        }
    }
}

Nothing magic here. The inner class is just a bean with private final fields, a private constructor to initialize them, public getters, a factory method and an overriden toString() method. The interface defines only one method that returns an instance of the inner class. Note that the inner class is final. The idea is to enforce immutability, so that its properties are not allowed to change.

Then, let's create a couple of enums that will implement this interface. Let's start by MyEnum1, which defines two values:

public enum MyEnum1 implements PropertiesSupplier {
    ENUM_1_CONST_1(Properties.of(1, "hello", 0.123)), 
    ENUM_1_CONST_2(Properties.of(2, "goodbye", 7.54));

    private final Properties p;

    private MyEnum1(Properties p) {
        this.p = p;
    }

    @Override
    public Properties properties() {
        return this.p;
    }
}

Next comes MyEnum2, which defines only one value:

public enum MyEnum2 implements PropertiesSupplier {
    ENUM_2_CONST_1(Properties.of(9, "hey dude", 547.21578));

    private final Properties p;

    private MyEnum2(Properties p) {
        this.p = p;
    }

    @Override
    public Properties properties() {
        return this.p;
    }
}

As you see, both enums implement the PropertiesSupplier interface, so they must provide an implementation for the Properties properties() method. And in order to comply with this, they must encapsulate an instance of Properties, which they receive in their constructor.

So now, after this indirection, the only code that is repeated among all enums is just the Properties field, the constructor that receives it as an argument and its getter method.

This is a sample showing how the enums would be used:

MyEnum1 e1 = MyEnum1.ENUM_1_CONST_2;
MyEnum2 e2 = MyEnum2.ENUM_2_CONST_1;

System.out.println(e1.name() + " - " + e1.properties());
System.out.println(e2.name() + " - " + e2.properties());

This code produces the following output

ENUM_1_CONST_2 - Properties [value1=2, value2=goodbye, value3=7.54]
ENUM_2_CONST_1 - Properties [value1=9, value2=hey dude, value3=547.21578]
like image 104
fps Avatar answered Sep 16 '22 22:09

fps


You could use composition instead of inheritance, putting the common code in a base component that becomes an attribute of your enums. Also this solution introduces a level of indirection, and requires 4 lines of boilerplate code for each new enum class.

Example:

/*
 * Example of sharing common code between enum classes wityh same 'structure' using 
 * an EnumBase component.
 * This is a work-around on the restriction that  enum cannot inherit from other classes
 */
package provajava;

/**
 *
 * The base class wich contains the shared code
 * In this example we want to define  many enums 
 * which are composed of a field 'code' and a field 'name'
 */

class EnumBase {
    public EnumBase( int code, String name ) {
        theCode = code;
        theName = name;
    }

    // The attributes  can be  declared final public 
    // In this case you do not need getters
    // but you still make sure that they cannot be changed  outside the constructor
    final public int theCode;
    final public String theName;

}


public class TestEnum2 {

    /**
    * A first enum class
    */

    enum BOOLEAN {
        TRUE ( 1, "True "),
        FALSE (2, "False");

        // This is boilerplate code that need to be repeated for each enum
        // Annoying, but is much shorter than shared code, especially if there are many enum values
        final public EnumBase base;
        BOOLEAN (int c, String n) {
            base = new EnumBase(c,n );
        }

    }

    /**
    * A second enum class
    */

    enum NUMBER {
        ONE ( 1, "One"),
        TWO (2, "Two"),
        THREE(3, "Three");

        // This is boilerplate code that need to be repeated for each enum
        // Annoying, but is much shorter than repeating shared code, especially if there are many enum values
        final public EnumBase base;
        NUMBER (int c, String n) {
            base = new EnumBase(c,n );
        }

    }  


    public static void main(String args[] ) {

        BOOLEAN b = BOOLEAN.FALSE;
        NUMBER  n = NUMBER.TWO;

        System.out.println( b + "->" + b.base.theCode + "," + b.base.theName  );
        System.out.println( n + "->" + n.base.theCode + "," +  n.base.theName  );

        //  n.base.theCode = 2; // ERROR : cannot assign to a final variable
        // n.base = new EnumBase( 1, "TWELVE");  // ERROR : cannot assign to a final variable

    }

}
like image 43
Francesco Bochicchio Avatar answered Sep 19 '22 22:09

Francesco Bochicchio