在创建将在多个级别频繁引用的 java 对象时,使用 class 实例更好还是使 class 静态更好?

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?

我有一个正在编写的 java 游戏,我需要一个单位原型的主数据库。数据库只是一个 class,包含一个 HashMap,它存储了几十个 class 实例,其中包含各个单位类型的统计信息。当游戏生成一个新单位时,它会将该单位从数据库中复制出来,使用该单位的名称在 HashMap 中定位它。该数据库在程序启动时构建一次,并且不会更改。我也没有扩展或修改存储在 HashMap 中的任何 classes。它旨在成为供游戏系统使用的只读参考。

我还有其他几个 classes,基本上是军队,包含许多单位。当军队获得一个单位时,它会从数据库中复制该单位的信息。我有三种方法可以为军队 classes 提供读取主数据库的方法。对于创建简单、可读且不会产生奇怪错误的代码的最佳解决方案,我想听听您的意见。

我已经包含了一些简单的代码来说明不同的方法(抱歉,如果我漏掉了一个分号或其他东西,我很快就把我的例子放在一起,我还在学习 java)。

方法一) 我将数据库创建为 class 实例,每次我创建一支新军队或调用军队方法将一个单位添加到军队时,我都会将对数据库实例的引用作为参数传递给军队或方法。这应该很容易想象。

方法二) 我将数据库创建为 class 实例。在军队 class 中,我有一个对数据库实例的静态引用,它通过军队 class 中的静态方法设置一次。用数据填充数据库后,调用静态方法并将静态引用设置为指向数据库。此后,军队class将始终能够通过引用静态变量从数据库中提取信息。

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");
    }
}

方法三) 我在数据库 class 中将 HashMap 创建为静态,并使用静态方法用数据填充它。数据库 class 中的 getter 方法也设为静态。现在根本没有引用传递,因为每次军队 class 实例需要从数据库中提取时,它只运行 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");
    }
}

我已经尝试了所有三种方法,它们都有效,但代码仍处于初级阶段,我不想因为我的早期架构而在未来遇到棘手的错误。我知道它没有以传统的面向对象的方式封装,但实际上方法 3 对我来说似乎是最干净的,因为数据库是在后台创建的,从来没有通过一堆不同的方法调用来访问。该代码的行数也更少,并且没有像方法 2 中那样的技巧。

对类似的事情有什么建议或经验吗?我已经用 python 做了很多,但我最近才开始学习 java,并且仍在努力适应封装限制。

首先,考虑到 HashMap 不是线程安全的。当客户请求引用时,您不能在数据库中添加新单位。其次,我有一个数据库接口和一个 DatabaseImpl class。然后通过 Unit 构造函数或 setter 注入一个 DatabaseImpl 实例。这样做的主要原因是可测试性,您可以轻松地模拟数据库。在内存管理方面,静态对象将首先在 class 加载时自动实例化,但垃圾收集器会在几次后将数据库放入旧世代,所以我认为这在性能方面并不重要.

尽量遵循SOLID原则,从你的代码着重依赖倒置原则:尽量依赖抽象(例如接口数据库)而不是您当前的数据库实现。

我想你应该有一些 enums 作为单位的单位类型,我想你有不同种类的单位。我想 Unit 应该是抽象的,甚至是一个接口,因为你总是应该有一个特定的单位(士兵,坦克,...),每一种 Unit 都应该实现某种具有动作(方法)的接口,如移动,攻击。移动士兵和移动坦克是不一样的。

FileOfUnits.game 作为您将拥有的其他配置值我会使用 configuration.properties 文件

不要忘记测试TDD会让你的类更容易测试,你会在实施之前先了解你想做什么。

使用一些 Control Version 和 commit/push 加上有用的注释,会更容易看出你为什么做了一些改变,有时你会不记得你为什么写这些行。

推荐您按以下顺序阅读和查看书籍: Effective Java and Clean Code

方法一最好。

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

这对我来说似乎是问题的症结所在。如果您认为这是一个问题,您正在尝试解决 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

如果在您的第​​二个示例中,我创建了一支军队但忘记设置数据库怎么办?我有一个实际上处于半创建状态的对象。对象永远不应该处于这种状态。如果您需要将它传递给构造函数,那么您就可以避免这个潜在的错误。

您的第二个和第三个示例也存在维护问题,因为当您不知道您的需求可能会如何变化时,它们将您限制在一个实例上。您怎么知道两支不同的军队 总是 共享一个数据库?如果将来你想要一个 ModernUSArmy 和一个 AncientMacedonianArmy 怎么办 - 你确定他们会共享相同的原型吗?