When creating a java object that will be referenced frequently at many levels, is it better to use a class instance or make the class static?

CMB :

I have a java game I'm writing where I need a master database of unit archetypes. The database is just a class containing a HashMap that stores a couple of dozen class instances containing individual unit types' stats. When the game spawns a new unit, it copies the unit out of the database, using the unit's name to locate it in the HashMap. This database gets built once, when the program starts, and doesn't change. I am also not extending or modifying any of the classes stored in the HashMap. It's meant to be a read-only reference for use by the game systems.

I have several other classes, basically armies, that contain a number of units. When an army gets a unit, it copies the unit's info out of the database. I have three ways of providing the army classes with a way to read the master database. I'd like your opinion as to which is the best solution for creating a simple, readable code that won't create weird bugs.

I've included some simple code to illustrate the different approaches (sorry if I missed a semicolon or something, I put my examples together quickly and I'm still learning java).

Method 1) I create the database as a class instance, and every time I create a new army or call an army method that adds a unit to the army, I pass the army or method a reference to the database instance as a parameter. This should be pretty easy to visualize.

Method 2) I create the database as a class instance. In the army class, I have a static reference to the database instance that gets set once by a static method in the army class. After the database is populated with data, the static method is called and sets the static reference to point to the database. Thereafter, the army class will always be able to pull information from the database simply by referencing the static variable.

class database
{
    // HashMap of unit archetypes
    private HashMap<String, Unit> unitLibrary = new HashMap<String, Unit>();

    // method for storing units in HashMap
    void storeUnits(String fileName)
    {
        // load unit stats from file
        // add new Unit instances to unitLibrary
    }

    // getter method
    Unit getUnit(String unitName)
    {
        return unitLibrary.get(unitName);
    }
}

class Army
{
    // variables
    private static Database masterDatabase;
    private static boolean databaseSet = false;
    ArrayList<Unit> armyUnits = new ArrayList<Unit>();

    // method for creating static database reference
    void setReference(Database d)
    {
        // set static reference to main database if not previously set
        if (!databaseSet)
        {
            masterDatabase = d;
            databaseSet = true;
        }
    }

    // add unit to army
    void addUnit(String unitName)
    {
        armyUnits.add(masterDatabase.getUnit(unitName);
    }   
}

public class CodeTest
{
    public static void main(String[] args)
    {
        // create master database
        Database masterDatabase = new Database();
        masterDatabase.storeUnits("FileOfUnits.game");

        // set static reference in army class to point to master database
        Army.setReference(masterDatabase);

        // Create army
        Army army1 = new Army();
        army1.addUnit("Soldier");
    }
}

Method 3) I create the HashMap in the database class as static, and use a static method to populate it with data. The getter methods in the database class are also made static. Now there is no passing of references at all, because every time an army class instance needs to pull from the database, it just runs Database.getUnit().

class database
{
    // HashMap of unit archetypes
    private static HashMap<String, Unit> unitLibrary = new HashMap<String, Unit>();

    // method for storing units in HashMap
    static void storeUnits(String fileName)
    {
        // load unit stats from file
        // add new Unit instances to unitLibrary
    }

    // getter method
    static Unit getUnit(String unitName)
    {
        return unitLibrary.get(unitName);
    }
}

class Army
{
    ArrayList<Unit> armyUnits = new ArrayList<Unit>();

    // add unit to army
    void addUnit(String unitName)
    {
        armyUnits.add(Database.getUnit(unitName);
    }   
}

public class CodeTest
{
    public static void main(String[] args)
    {
        // prepare master database
        Database.storeUnits();

        // create army
        Army army2 = new army2();
        army2.add("Soldier");
    }
}

I've tried all three methods, and they all work, but the code is still in its infancy and I don't want to encounter tricky bugs down the road because of my early architecture. I understand that it isn't encapsulated in the traditional object-oriented manner, but Method 3 actually seems the cleanest to me because the database gets created in the background and never has be piped through a bunch of different method calls to be accessed. The code also has fewer lines and no tricks like the one in method 2.

Any suggestions or experience with something like this? I've done a lot with python, but I've only recently started to learn java, and still trying to get used to the encapsulation restrictions.

Michael :

Method 1 is the best.

but Method 3 actually seems the cleanest to me because the database gets created in the background and never has be piped through a bunch of different method calls to be accessed

This is seems to be the crux of the issue to me. You're trying to solve dependency injection with static variables and it's a terrible idea. Frameworks like Spring can save you the trouble of passing objects down the chain with features such as autowiring if you think that that's a problem.

What if, in your second example, I create an Army and forget to set the database? I have an object that's effectively in a half-created state. Objects should never be in such a state. If you'd required it be passed to the constructor then you would have avoided this potential bug.

There's a maintenance problem with your second and third examples, as well, in that they restrict you to a single instance when you have no idea how your requirements might change. How do you know that two different armies will always share a database? What if in the future you want a ModernUSArmy and an AncientMacedonianArmy - do you know for certain that they will share the same archetypes?

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=90021&siteId=1