Search code examples
javadatabaseenumsstatichardcode

How to hard-code static relational data with base class?


In my application I need to have access some "relational" data at runtime. If I had a lot of data or it changed often I'd store it in a DB (sqlite or something). But I only have a couple of entities with 5-50 objects that won't change (often) and will be changed by the developers alone. So I think it makes sense to simply hard-code the data in Java classes for simpler code maintanence.

All of the entities share a common abstract superclass (having an int id and a String name). After defining the data I'd like to have a reference to each entity object AND to a Collection/Array all of these objects being available outside of the classes. I want to make sure that the data is immutable.

The first idea was to do something like this:

public abstract class AbstractEntity{

    private final int id;
    private String name;

    protected AbstractEntity(int id, String name){
        this.id = id;
        this.name = name;
    }   

    private void someMethods(){
        ...
    }

    abstract protected void someAbstractMethods(){
        ...
    }
}

public final class MyEntity extends AbstractEntity{

    public static final ENTITY_1 = new MyEntity(1, "Entity 1", "foo");
    public static final ENTITY_2 = new MyEntity(2, "Entity 2", "bar");
    public static final ENTITY_3 = new MyEntity(3, "Entity 3", "baz");

    public static final Set<MyEntity> ALL = Collections.unmodifiableSet(new TreeSet<>(Arrays.asList(ENTITY_1, ENTITY_2, ENTITY_3)));

    private final String foo;

    private MyEntity(int id, String name, String foo){
        super(id, name);
        this.foo = foo;
    }
}

The flaw I see is that when adding a new entity during development I have to add it to the ALL Set as well. That annoys me. It's a kind of redundancy and source for bugs.

I then just thought of using enums as entity classes where I can define the entitiy objects and implicitely have alle of the objects references in the values() Array. But this doesn't work because of having a base class (AbstractEntity) and multiple inheritance not being possible in Java.

The next idea that instead of an abstract base class was to use an interface with default methods and as entities define an enum implementing this interface. But then I wouldn't be able to define int id and String name in the base class (interface) and would have to define it in each subclass.

Is there some syntactic sugar I'm missing? What's the best practice here?


Solution

  • As explained in the comments I would rather use an Enum to store the instances of each Entity. You can add parameters to the enum constructor so you can then instantiate and store the entity values.

    Next, I provide you a solution keeping your Set to store ALL the instances and with an Enum for entites of type A and another one for entities of type B (both inherit from the AbstractEntity which is used in the ALL set). The ALL set is filled on the static part retrieving the values from the ENUMS and thus, you only need to add entries on the enums to add new values.

    Entities.java

    public class Entities {
    
        // Entities of class A
        private enum EntitiesOfA {
            ENTITY_A_1(1, "EntityA 1", "foo"), // A1
            ENTITY_A_2(2, "EntityA 2", "bar"), // A2
            ENTITY_A_3(3, "EntityA 3", "baz"); // A3
    
            private MyEntityA entity;
    
    
            private EntitiesOfA(int id, String name, String foo) {
                this.entity = new MyEntityA(id, name, foo);
            }
    
            public MyEntityA getEntity() {
                return this.entity;
            }
        }
    
        // Entities of class B
        private enum EntitiesOfB {
            ENTITY_B_1(4, "EntityB 1", 10), // B1
            ENTITY_B_2(5, "EntityB 2", 11), // B2
            ENTITY_B_3(6, "EntityB 3", 12); // B3
    
            private MyEntityB entity;
    
    
            private EntitiesOfB(int id, String name, int value) {
                this.entity = new MyEntityB(id, name, value);
            }
    
            public MyEntityB getEntity() {
                return this.entity;
            }
        }
    
    
        // All Entities
        public static final Set<AbstractEntity> ALL;
    
        static {
            // I use HashSet instead of TreeSet because I have
            // not implemented the comparable interface
            Set<AbstractEntity> allEntities = new HashSet<>();
            for (EntitiesOfA entity : EntitiesOfA.values()) {
                allEntities.add(entity.getEntity());
            }
            for (EntitiesOfB entity : EntitiesOfB.values()) {
                allEntities.add(entity.getEntity());
            }
    
            ALL = Collections.unmodifiableSet(allEntities);
        }
    
    
        public static void main(String[] args) {
            for (AbstractEntity entity : ALL) {
                System.out.println("Entity ID = " + entity.getId() + " NAME = " + entity.getName());
                entity.someAbstractMethods();
    
                if (entity instanceof MyEntityA) {
                    MyEntityA a = (MyEntityA) entity;
                    System.out.println("Entity A with foo = " + a.getFoo());
                    a.someMethods();
                } else if (entity instanceof MyEntityB) {
                    MyEntityB b = (MyEntityB) entity;
                    System.out.println("Entity B with value = " + b.getValue());
                    b.someMethods();
                } else {
                    System.err.println("ERROR: Unrecognised subclass");
                }
            }
        }
    }
    

    AbstractEntity.java

    public abstract class AbstractEntity {
    
        private final int id;
        private String name;
    
    
        protected AbstractEntity(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        public int getId() {
            return this.id;
        }
    
        public String getName() {
            return this.name;
        }
    
        public void someMethods() {
            // A
        }
    
        protected abstract void someAbstractMethods();
    
    }
    

    MyEntityA.java public final class MyEntityA extends AbstractEntity {

    private final String foo;
    
    
    public MyEntityA(int id, String name, String foo) {
        super(id, name);
    
        this.foo = foo;
    }
    
    public String getFoo() {
        return this.foo;
    }
    
    @Override
    protected void someAbstractMethods() {
        // Some code
    }
    

    }

    MyEntityB.java public final class MyEntityB extends AbstractEntity {

    private final int value;
    
    
    public MyEntityB(int id, String name, int value) {
        super(id, name);
    
        this.value = value;
    }
    
    public int getValue() {
        return this.value;
    }
    
    @Override
    protected void someAbstractMethods() {
        // Some code
    }
    

    }

    Notice that:

    • The constructor in the ENUM can be substituted by a VAL(new MyEntityA(...)) if you prefer. In that case, you could merge EntitesOfA and EntitesOfB to have all the entities and use a constructor with the AbstractEntity. You could also remove the Set too.

      public class AllEntities {
      
      private enum Entities {
          ENTITY_A_1(new MyEntityA(1, "EntityA 1", "foo")), // A1
          ENTITY_A_2(new MyEntityA(2, "EntityA 2", "bar")), // A2
          ENTITY_A_3(new MyEntityA(3, "EntityA 3", "baz")), // A3
          ENTITY_B_1(new MyEntityB(4, "EntityB 1", 10)), // B1
          ENTITY_B_2(new MyEntityB(5, "EntityB 2", 11)), // B2
          ENTITY_B_3(new MyEntityB(6, "EntityB 3", 12)); // B3
      
          private AbstractEntity entity;
      
      
          private Entities(AbstractEntity entity) {
              this.entity = entity;
          }
      
          public AbstractEntity getEntity() {
              return this.entity;
          }
      }
      
      
      // All Entities
      public static final Set<AbstractEntity> ALL;
      
      static {
          // I use HashSet instead of TreeSet because I have
          // not implemented the comparable interface
          Set<AbstractEntity> allEntities = new HashSet<>();
          for (Entities entity : Entities.values()) {
              allEntities.add(entity.getEntity());
          }
          ALL = Collections.unmodifiableSet(allEntities);
      }
      
    • I have added a main method for testing but you can also remove it.