简体   繁体   中英

JavaSE: Using a singleton/static-only class to hold resources? Or what?

Background : I'm working on a reasonably big game project to improve my OO skills. I've been using SOLID principles and searching a lot (actually more than i code it).

Problem : I've been struggling with resources (sprites, to be specific). In fisrt place i made a class which would load the resources on the game(based on a string that specified where would the spritesheet be located in the jar file) and give global access to it. It worked based on static variables. Then I read that static variables are evil for OO designs, and went to singletons. Then, i read that singletons are evil, and since it I didn't find any other option. I understand that global state creates a lot of dependencies (in fact, I already felt it on my skin with this resource class). But i don't find any good way to avoid using it.

Question : On the bottom of the question you will see my implementation for the resource class and sprite class. Every single class that needs do have a graphical interpretation (mobs, the player, tiles, itens and so on) depends upon the Sprite class. And every class can get access to it by the Resource class (which only has two resources loaded in the sample, but thats offtopic). S* o, what would be the best solution for this? * (I would prefer if you answer with concepts, rather than give me the code done :))

OBS: I have almost the same problem with holding a database for tile types (see in the code below). OBS2: Neither the database nor the resources will change during the game runtime. They are "constant", and i would only change them to add new tiles/resources on compile time. OBS2: I have a idea that might be OK, but im not sure: -Creating a superclass for sprite/resources, and then create subclass for every type of resource i need. I don't think that this solution will solve coupling issues, and it would split the sprite implementation amongst diferent packages.

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

}

}

Have you considered using a dependency injection framework like spring or guice ? It could inject the resource accessor class into all the places where it is used. Under the hood the resource class could behave like a singleton with the actual resource data being cached or so. But you would not suffer from the 'evil-ness' of singletons or global state. You could ie define the resource class as an interface and easily mock it in unit tests.

Quickie answer without looking too deeply at the details of this question... You could use a Guava Supplier interface , and in particular, you could use the Suppliers.ofInstance(T t ) . That might be even more lightweight than Guice.

The plain vanilla javase solution is to pass around a reference to the Resources class. typically, your classes will fall into 1 of 2 categories: larger and fewer instances, smaller and many instances. for the former, you typically instantiate them with a reference to your Resources class (or, the class may already have a reference to another class which references the Resources class). for the latter, you would typically add the Resources class as a parameter on the methods that might need it (this allows to classes to be small and not have a lot of extra references).

also, i'm assuming you would setup your Resources class so it has handles to all the resources (eg you could get sprites and tiles from it).

finally, you would instantiate your Resources class once at your program's entrypoint (eg main method) and pass it along to the other top-level objects which get created.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM