在创建将在多个级别频繁引用的 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
怎么办 - 你确定他们会共享相同的原型吗?
我有一个正在编写的 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
怎么办 - 你确定他们会共享相同的原型吗?