11-享元模式

11-享元模式

1.享元模式

  1. 传统的方式实现棋牌游戏,传统的方式可能会消耗较大的内存。
// 棋子类
public class ChessPiece {

    private int id;
    private String text;
    private Color color;
    private int positionX;
    private int positionY;

    public ChessPiece(int id, String text, Color color, int positionX, int positionY) {
        this.id = id;
        this.text = text;
        this.color = color;
        this.positionX = positionX;
        this.positionY = positionY;
    }

    public static enum Color {
        RED, BLACK;
    }
}

// 棋盘类。
// 传统的方式中,每个游戏房间都需要有一个棋盘类,
// 如果有上百万个游戏房间则需要有上百万个棋盘类,保存较多的对象会消耗大量的内存。
public class ChessBoard {

    private Map<Integer, ChessPiece> chessPieces = new HashMap<>();
    public ChessBoard () {
        init();
    }

    public void init() {
        chessPieces.put(1, new ChessPiece(1, "車", ChessPiece.Color.BLACK, 0, 0));
        chessPieces.put(2, new ChessPiece(2, "馬", ChessPiece.Color.BLACK, 1, 0));
        // ...省略其他棋子的创建。
    }
}
  1. 使用享元模式实现棋盘游戏。
// 享元类
public class ChessPieceUnit {

    private int id;
    private String text;
    private Color color;

    public ChessPieceUnit(int id, String text, Color color) {
        this.id = id;
        this.text = text;
        this.color = color;
    }

    public static enum Color {
        RED, BLACK;
    }
}

// 缓存享元类的工厂
public class ChessPieceUnitFactory {

    private static final Map<Integer, ChessPieceUnit> pieces = new ConcurrentHashMap<>();
    static {
        pieces.put(1, new ChessPieceUnit(1, "車", ChessPieceUnit.Color.BLACK));
        pieces.put(2, new ChessPieceUnit(2, "馬", ChessPieceUnit.Color.BLACK));
        // ... 省略其他棋子的创建
    }

    public static ChessPieceUnit getChessPiece(int chessPieceId) {
        return pieces.get(chessPieceId);
    }
}

// 棋子类
public class ChessPiece {

    private ChessPieceUnit chessPieceUnit;
    private int positionX;
    private int positionY;

    public ChessPiece(ChessPieceUnit chessPieceUnit, int positionX, int positionY) {
        this.chessPieceUnit = chessPieceUnit;
        this.positionX = positionX;
        this.positionY = positionY;
    }
}

// 棋盘类。
// 使用享元模式之后,如果有上百万个游戏空间,依然需要上百万个棋盘ChessBoard类和上百万个棋子ChessPiece类。
// 但是棋子ChessPiece类中只有positionX和positionY属性是新的,即没有被共享的,
// 而享元单元ChessPieceUnit类保存的棋子id、文本和颜色是被共享的,不管有多少个游戏房间都只有32个享元单元ChessPieceUnit类
public class ChessBoard {

    private Map<Integer, ChessPiece> chessPieces = new HashMap<>();
    public ChessBoard () {
        init();
    }

    public void init() {
        chessPieces.put(1, new ChessPiece(ChessPieceUnitFactory.getChessPiece(1), 0, 0));
        chessPieces.put(2, new ChessPiece(ChessPieceUnitFactory.getChessPiece(2), 1, 0));
        // ...省略其他棋子的创建过程。
    }
}

2.享元模式总结

  1. 享元模式通过复用不可变对象,节省内存。如果系统中存在大量的重复的不可变对象,就可以利用享元模式,将对象设计为享元,在内充中只保留一份实例,供多出代码使用。
  2. 在实际开发中,不仅仅相同的对象可以被设计为享元类,对于相似的对象,可以将这些对象中相同的部分提取出来设计为享元,如棋盘游戏中将棋子的id、文本和颜色抽取出来设计为享元类。
  3. 享元模式对JVM的垃圾回收不友好,因为工厂类会一直保存对享元类的引用,使享元类即使在不适用的情况下,也不会被垃圾回收。在某些情况下,如果对象的生命周期很短,也不会被密集使用,利用享元模式反而会浪费空间。
  4. 享元模式和单例模式的区别。在享元模式用,一个类可以创建多个对象,所以享元模式有点类似单例模式的变体:多例模式。但是从设计意图上看,享元模式是为了对象复用,节省内存;多例模式是为了限制对象的个数。
  5. 享元模式和缓存的区别。常见的缓存,如Redis缓存、数据库缓存,是为了提供访问速度;享元模式是为了对象复用,节省内存。
  6. 享元模式和对象池、线程池、连接池的区别。对象池、线程池、连接池可以称之为池化技术,池化技术中的复用是为了节省时间(如节省线程、连接的创建时间);享元模式是为了对象复用,节省内存。

3.享元模式在Java中的应用

  1. 享元模式在Integer中的应用。
// Integer利用享元模式缓存-128~127之间的整数。
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
  1. 享元模式在String中的应用。
// String利用享元模式复用相同的字符串常量。
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true
posted @ 2022-11-27 08:51  行稳致远方  阅读(37)  评论(0)    收藏  举报