顾名思义就是共享的单元,享元模式的意图就是复用对象,从而节省对应的内存
对于一个系统中,存在着的大量对象,如果对象有相同属性,并且这个对象是不可变的,那么我们可以将这个对象设计为享元的对象,在内存中只有一个对象,但可以提供给多处代码使用,从而节省内存,实际上,不仅仅是对象可以设计为享元的,对于相似的对象,我们可以将对象中的相同字段提取出来,设计为享元的
我们拿一个简单棋牌游戏来举例
我们在开发一个棋牌游戏的时候,一个大厅内有无数个房间,每个房间都有一个棋盘,每个棋盘上都有对应的棋子,我们需要利用这些数据,来展示出一个完整的棋盘
我们编写了ChessPiece类作为棋子,ChessBoard作为棋盘,那么这个完整的棋局的展现就是如下
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 = positionX; } public static enum Color { RED, BLACK } // …省略其他属性和getter/setter方法… } public class ChessBoard {//棋局 private Map<Integer, ChessPiece> chessPieces = new HashMap<>(); public ChessBoard() { init(); } private void init() { chessPieces.put(1, new ChessPiece(1, “車”, ChessPiece.Color.BLACK, 0, 0)); chessPieces.put(2, new ChessPiece(2,”馬”, ChessPiece.Color.BLACK, 0, 1)); //…省略摆放其他棋子的代码… } public void move(int chessPieceId, int toPositionX, int toPositionY) { //…省略… } } |
那么会出现一个问题,棋子有位置有千万万,但是棋子的个数和颜色还有名称不会变啊,我们没有必要创建如此之多的棋子,于是乎,我们将棋子的颜色名称 id提取出来,形成享元的类
// 享元类
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 } // …省略其他属性和getter方法… } public class ChessPieceUnitFactory { private static final Map<Integer, ChessPieceUnit> pieces = new HashMap<>(); 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 unit, int positionX, int positionY) { this.chessPieceUnit = unit; this.positionX = positionX; this.positionY = positionY; } // 省略getter、setter方法 } public class ChessBoard { private Map<Integer, ChessPiece> chessPieces = new HashMap<>(); public ChessBoard() { init(); } private void init() { chessPieces.put(1, new ChessPiece( ChessPieceUnitFactory.getChessPiece(1), 0,0)); chessPieces.put(1, new ChessPiece( ChessPieceUnitFactory.getChessPiece(2), 1,0)); //…省略摆放其他棋子的代码… } public void move(int chessPieceId, int toPositionX, int toPositionY) { //…省略… } } |
这样的话,我们所有的棋盘类ChessBoard共享这30个ChessPieceUnit类,在使用的时候,就大大的节省了内存
那么,享元模式的使用很简单了,就是通过工厂模式,在工厂类中,通过一个Map来缓存已经创建好的享元对象,达到复用的目的
那么我们再举一个文本编辑器的例子,如何使用文本编辑器来优化内存占用呢?
因为每一字都可能有着对应的格式大小,那么我们要定一个文字类,来存储字体,大小,颜色,文字信息等
public class Character {//文字
private char c; private Font font; private int size; private int colorRGB; public Character(char c, Font font, int size, int colorRGB) { this.c = c; this.font = font; this.size = size; this.colorRGB = colorRGB; } } public class Editor { private List<Character> chars = new ArrayList<>(); public void appendCharacter(char c, Font font, int size, int colorRGB) { Character character = new Character(c, font, size, colorRGB); chars.add(character); } } |
在文本编辑器中,我们每打一个字,就会创建一个新的Character对象,保存在chars数组中,如果一个文本有成千上万的字,那么就需要存储那么多的Charater对象
于是我们使用了享元模式,将其中的字体,大小,颜色抽取出来
然后只需要直接返回Style对象即可
public class CharacterStyle {
private Font font; private int size; private int colorRGB; public CharacterStyle(Font font, int size, int colorRGB) { this.font = font; this.size = size; this.colorRGB = colorRGB; } @Override public boolean equals(Object o) { CharacterStyle otherStyle = (CharacterStyle) o; return font.equals(otherStyle.font) && size == otherStyle.size && colorRGB == otherStyle.colorRGB; } } public class CharacterStyleFactory { private static final List<CharacterStyle> styles = new ArrayList<>(); public static CharacterStyle getStyle(Font font, int size, int colorRGB) { CharacterStyle newStyle = new CharacterStyle(font, size, colorRGB); for (CharacterStyle style : styles) { if (style.equals(newStyle)) { return style; } } styles.add(newStyle); return newStyle; } } public class Character { private char c; private CharacterStyle style; public Character(char c, CharacterStyle style) { this.c = c; this.style = style; } } public class Editor { private List<Character> chars = new ArrayList<>(); public void appendCharacter(char c, Font font, int size, int colorRGB) { Character character = new Character(c, CharacterStyleFactory.getStyle(font, size, colorRGB)); chars.add(character); } } |
那么,根据上面的讲解,这些和单例 缓存 对象池这些概念,有什么区别呢?
享元模式和单例的区别
单例是指,一个类只能创建一个对象,享元模式中,一个类可以创建多个对象,每个对象被多个代码引用共享
类似于多例
但是和多例有区别,多例是为了限制对象的个数,而享元是为了对象的复用
而且这个缓存是为了存储可以复用的对象,和数据库缓存没有啥太大关系
那么享元模式和对象池的区别呢?
对象池和享元模式,都是为了对象的复用,在享元模式中,对象是不可变的,是为了可以重复使用,也可以共享使用,像在对象池中,很多都是被独占的,上锁的,单一线程来使用