-1

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?

Morrandir
  • 595
  • 1
  • 4
  • 19
  • 1
    This doesn't sound simpler at all. I think a better idea is to keep them in a database and provide an in-memory caching solution for those objects that are most heavily used. – duffymo Aug 03 '17 at 14:19
  • 2
    If you really don't want to use a db solution for some limited set of data, I would consider a properties file instead. This approach seems like a lot of unnecessary work to mimic a database – andrewdleach Aug 03 '17 at 14:22
  • 1
    Don't create entities at all. Create ENUMs and add necessary fields to the enums and use the enums to store relations in real entities. – StanislavL Aug 03 '17 at 14:25
  • What about having an entities class that adds the required entries on the static block? I mean you can have a private static attribute Collection and methods to get and add entities inside. If you need them to be unmodifiable you can probably use a Factory Pattern too. – Cristian Ramon-Cortes Aug 03 '17 at 14:30
  • I'm not sure who said this but: "If you know all instances of your class at compile time, use an enum". – André Stannek Aug 03 '17 at 14:33
  • 2
    I'd like to point out that I rarely hard code data into a program. Even simple things like a list of email addresses of ppl who will receive a report I store on a sql server. Data separate from apps and apps separate from data. The only "data" I put in my apps are constants. – Rabbit Guy Aug 03 '17 at 14:47
  • You *could* use reflection to figure out all declared "entities". Yet I'm not sure if your approach of "data in code" is appropriate because I don't know your real use case. – JimmyB Aug 03 '17 at 14:54
  • @duffymo Ok, I should have been clearer concerning the "data". The "data" more or less are constants: The application will be something like a huge (multi-step) form/dialog where the user has to chose a lot of options having strong dependencies. The options could be something like countries, zip codes, colors, that sort of stuff (no user data!). There might indeed be additions, but generally the "data" ist static and thus more or less business logic. Furthermore not all of the dependecies could be modeled in a relational DB. So I definitely have to hard-code here. – Morrandir Aug 03 '17 at 18:35
  • @rabbitguy See my answer to duffymo. – Morrandir Aug 03 '17 at 18:35
  • @JimmyB See my answer to duffymo. – Morrandir Aug 03 '17 at 18:35
  • I call that "reference data", and I put it in a database. You don't need a class hierarchy to do something simple. My comment stands – duffymo Aug 03 '17 at 18:37
  • @andrewdleach Some of the entities have 5-6 fields, so a properties file had to support something like a table that had to be parsed. That doesn't seem like a good approach to me. – Morrandir Aug 03 '17 at 18:37
  • The commentary is going against you. You'd be wise to consider doing something besides an object hierarchy. – duffymo Aug 03 '17 at 18:37
  • @duffymo So you would have the id and name field in every class and also shared logic (methods)? To me that looks like too much duplicate code. – Morrandir Aug 03 '17 at 18:39
  • @StanislavL As said with enums I wouldn't be able to use a common base class. What would be the workaround? And what do you mean with " use the enums to store relations in real entities". – Morrandir Aug 03 '17 at 18:40
  • Sorry, I thought we were talking about data. ID and name sound like primary keys for querying to me. Your question and requirements aren't clear to me. I'll bet I could make this simpler by avoiding your pre-conceived notion. Voting to close. – duffymo Aug 03 '17 at 18:40
  • @duffymo Ok, how would you call it when it's not actually data? In fact I need the IDs for persistence, not querying. Names for display. – Morrandir Aug 03 '17 at 18:42
  • Sounds like an overly complex object-oriented centric hierarchy that's unnecessary. You have an object-oriented hammer; no surprise that your problems all call for hierarchies. – duffymo Aug 03 '17 at 18:43
  • @CristianRamon-Cortes I don't see how a factory pattern would help me here. Could you please elaborate? – Morrandir Aug 03 '17 at 18:51
  • @Morrandir Between the categories of code vs. data I tend to call your data "configuration". However, if you actually have non-trivial logic associated with that configuration data your approach may be justified. (If you don't want to use some kind of more complex engine which handles flow and validation for you.) – JimmyB Aug 04 '17 at 08:04

2 Answers2

1

Something like this could do the trick using reflection.

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;

    static {
       final Set<MyEntity> all = new TreeSet<>();

       // Find all static fields in this class which are instances of this class:
       final Field[] fields = MyEntity.class.getDeclaredFields();
       for ( Field f : fields ) {
          if ( f.getType() == MyEntity.class ) {
            if ( Modifier.isStatic( f.getModifiers() ) {
               all.add((MyEntity)f.get(null));
            }
          }
       }

       ALL = Collections.unmodifiableSet( all );
    }

    private final String foo;

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

Writing a generic utility function initializeEntitySet( Class<?> entityClass ) from that should be straight forward.

JimmyB
  • 12,101
  • 2
  • 28
  • 44
  • Thanks, this indeed works. I like @Cristian Ramon-Cortes' solution better as it uses enums which indeed seems to be the way it should be (knowing all of the instances at compile time). – Morrandir Aug 04 '17 at 19:34
1

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.

Cristian Ramon-Cortes
  • 1,838
  • 1
  • 19
  • 32
  • Thanks a lot. It's a nice trick to nest the entities in enums. I modified your solution by defining the MyEntity classes as static classes in the enums. This way I can keep the constructor private. – Morrandir Aug 04 '17 at 19:31