学习设计模式——享元模式

今天我们来学习最后一个设计模式:享元模式

相对来说,享元模式的原理和实现也比较简单,并且在实际的项目开发中也不怎么常用。

概述

享元模式:(Flyweight Design Pattern)运用共享技术有效的支持大量细粒度的对象。

所谓享元,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。

当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样可以减少内存中对象的数量,起到节省内存的目的。

何时使用:

  • 系统中有大量对象。
  • 这些对象消耗大量内存。
  • 这些对象的状态大部分可以外部化。
  • 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。
  • 系统不依赖于这些对象身份,这些对象是不可分辨的。

UML 类图:

image.png
角色组成:

  1. 抽象享元(Flyweight)角色: 是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  2. 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
  3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  4. 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

实例解析

假设我们要开发一个象棋游戏。一个游戏厅中有成千上万个房间,每个房间对应一个棋局。棋局要保存每个棋子的数据:棋子类型棋子颜色棋子在棋局中的位置。利用这些数据,我们就能显示一个完整的棋局给玩家。

此时,我们就可以使用享元模式来实现。具体代码如下:

享元类 ChessPieceUnit.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
java复制代码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 int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public Color getColor() {
return color;
}

public void setColor(Color color) {
this.color = color;
}
}

棋子 ChessPiece.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
java复制代码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;
}

public ChessPieceUnit getChessPieceUnit() {
return chessPieceUnit;
}

public void setChessPieceUnit(ChessPieceUnit chessPieceUnit) {
this.chessPieceUnit = chessPieceUnit;
}

public int getPositionX() {
return positionX;
}

public void setPositionX(int positionX) {
this.positionX = positionX;
}

public int getPositionY() {
return positionY;
}

public void setPositionY(int positionY) {
this.positionY = positionY;
}
}

享元工厂 ChessPieceUnitFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
arduino复制代码public class ChessPieceUnitFactory {
private static final Map<Integer, ChessPieceUnit> pieces = new HashMap<>();

static {
pieces.put(1, new ChessPieceUnit(1,"車", ChessPieceUnit.Color.BLACK));
pieces.put(1, new ChessPieceUnit(2,"馬", ChessPieceUnit.Color.BLACK));
pieces.put(1, new ChessPieceUnit(3,"相", ChessPieceUnit.Color.BLACK));
// 省略其他棋子代码。。。
}

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

棋局 ChessBoard.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码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(2, new ChessPiece(ChessPieceUnitFactory.getChessPiece(2), 1, 0));
chessPieces.put(3, new ChessPiece(ChessPieceUnitFactory.getChessPiece(3), 2, 0));
// 省略其他棋子代码。。。
}

public void move(int chessPieceId, int toPositionX, int toPositionY) {
// 省略。。。
}
}

总结

优缺点

  • 优点: 大大减少对象的创建,降低系统的内存,使效率提高。
  • 缺点: 提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

应用

  • String 常量池、数据库连接池、缓冲池等等。
  • Java Integer 类中、String 类的 字符串常量池

实际上,享元模式对 JVM 的垃圾回收并不友好。因为享元工厂类一直保存了对享元对象的引用,这就导致享元对象在没有任何代码使用的情况下,也并不会被 JVM 垃圾回收机制自动回收掉。因此,在某些情况下,如果对象的生命周期很短,也不会被密集使用,利用享元模式反倒可能会浪费更多的内存。所以,除非经过线上验证,利用享元模式真的可以大大节省内存,否则,就不要过度使用这个模式,为了一点点内存的节省而引入一个复杂的设计模式,得不偿失。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%