背景:我正在进行一个相当大的游戏项目,以提高我的OO技能。我一直在使用SOLID原理,并进行了大量搜索(实际上比我编码的要多)。
问题:我一直在与资源(具体来说是精灵)作斗争。在第一个地方,我创建了一个类,它将加载游戏中的资源(基于一个字符串,该字符串指定了spritesheet在jar文件中的位置),并对其进行全局访问。它基于静态变量工作。然后我读到静态变量对OO设计是有害的,于是我转向了singleton。然后,我读到单身汉是邪恶的,从那以后,我没有找到任何其他选择。我知道全局状态会产生很多依赖关系(事实上,我已经在这个资源类中感受到了这一点)。但我找不到任何好方法来避免使用它。
问题:在问题的底部,您将看到我对资源类和精灵类的实现。每个需要图形解释的类(mobs、播放器、tiles、itens等等)都依赖于Sprite类。每个类都可以通过Resource类访问它(该类在示例中只加载了两个资源,但这与主题无关)。S*o,最好的解决方案是什么*(我更愿意你用概念回答,而不是给我完成的代码:)
OBS:我在保存tile类型的数据库时遇到了几乎相同的问题(请参阅下面的代码)。OBS2:在游戏运行期间,数据库和资源都不会改变。它们是"恒定的",我只会在编译时更改它们以添加新的瓦片/资源。OBS2:我有一个想法可能还可以,但我不确定:-为sprite/resources创建一个超类,然后为我需要的每种类型的资源创建子类。我不认为这个解决方案会解决耦合问题,而且它会将sprite实现拆分为不同的包。
public final class Resources {
private static HashMap<String, Sprite> sprites = new HashMap<String, Sprite>();
static {
loadSprites(new SpriteSheet("spritesheets/spritesheettest.png"));
}
private static void loadSprites(SpriteSheet s) {
sprites.put("Grass1", s.getRawSprite(0, 0).recolor(Color.GREEN));
sprites.put("Cave1", s.getRawSprite(0, 0).recolor(Color.GRAY));
}
public static Sprite getSprite (String name) {
return sprites.get(name);
}
}
public final class Sprite {
public static final int SIDE = 32;
private static SpriteFilter spriteFilter;
private static MySpriteRotator spriteRotator;
private BufferedImage image;
static {
spriteFilter = new MySpriteFilter();
spriteRotator = new MySpriteRotator();
}
public Sprite(BufferedImage img) {
image = img;
}
public Sprite rotate (double angle, BufferedImage sprite) {
return (spriteRotator.rotate(angle, this));
}
public Sprite recolor(Color c) {
MySpriteFilter sf = new MySpriteFilter();
return (spriteFilter.recolor(c, this));
}
public void render(Graphics2D g, int x, int y) {
g.drawImage(image, x, y, null);
}
public BufferedImage getImage() {
return image;
}
}
public final class TileDataBase {
private static HashMap<Integer, Tile> database = new HashMap<Integer, Tile>();
private static HashMap<Integer, Tile> rgbDatabase = new HashMap<Integer, Tile>();
private static final Tile grass = new MyTile(1, new Color(0, 255, 0), Resources.getSprite("Grass1"));
private static final Tile cave = new AnimatedTile(2, new Color(127, 127, 127), Resources.getSprite("Cave1"), new Animator(new Sprite[] {Resources.getSprite("Grass1"), Resources.getSprite("Cave1")}));
private TileDataBase() {
}
public static Tile getTileByID(int id, int x, int y) {
Tile t = database.get(id).cloneTile();
if (t == null) {
try {
throw new Exception("No tile for such id");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
t.setX(x);
t.setY(y);
return t;
}
static Tile getTileByRGB(int rgb, int x, int y) {
Tile t = rgbDatabase.get(rgb).cloneTile();
if (t == null) {
try {
throw new Exception("No tile for such rgb");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
t.setX(x * Sprite.SIDE);
t.setY(y * Sprite.SIDE);
return t;
}
static void putToDatabase(int id, Tile tile) {
database.put(id, tile);
}
static void putToRGBDatabase (Color c, Tile t) {
rgbDatabase.put(c.getRGB(), t);
}
}
您是否考虑过使用像spring或gui这样的依赖注入框架?它可以将资源访问器类注入到所有使用它的地方。在引擎盖下,资源类可以表现得像一个singleton,实际的资源数据被缓存。但你不会遭受singleton或全局状态的"邪恶"。您可以将资源类定义为一个接口,并在单元测试中轻松地模拟它。
快速回答,不要太深入地研究这个问题的细节。。。您可以使用Guava Supplier接口,尤其是Suppliers.ofInstance(T t
)。这可能比Guice还要轻。
普通的javase解决方案是传递对Resources类的引用。通常,您的类将分为两类中的一类:较大和较少的实例,较小和较多的实例。对于前者,您通常通过引用Resources类来实例化它们(或者,该类可能已经引用了另一个引用Resources类的类)。对于后者,您通常会在可能需要的方法上添加Resources类作为参数(这允许类很小,并且没有太多额外的引用)。
此外,我假设您将设置Resources类,使其具有所有资源的句柄(例如,您可以从中获取sprite和tile)。
最后,您将在程序的入口点(例如main方法)实例化一次Resources类,并将其传递给创建的其他顶级对象。