:::info 也许你会觉得享元模式比较陌生,但是相信在你的软件开发生涯中应该不知不觉的用了很多次,只是你没有总结。例例如你肯定用到过缓存,用到过对象池…

不知道作为IT猿的你听说过麦克斯韦这个人不,这个人可牛逼了,就是他提出了著名的麦克斯韦方程式。但是你知道吗,这个方程式的东西都没有一个是他研究出来的,但他很好的把前人的研究总结形成了一个体系,这一下就牛逼了,足见总结的重要性。 :::

定义

允许使用对象共享来有效地支持大量细粒度对象

使用场景

当你的程序中存在大量相似对象,每个对象之间只是根据不同的使用场景有些许变化时。

UML

享元模式 - 图1

角色结构

  • Flyweight:享元接口,定义所有对象共享的操作
  • ConcreteFlyweight:具体的要被共享的对象,其一般是一个不可变类,内部只保存需要共享的内部状态,它可能不止一个。
  • FlyweightFactory:负责给客户端提供共享对象


:::info 共享对象的状态分为内部状态外部状态,内部状态在各个对象间共享,而外部状态由客户端传入,这一点一定要牢记。 :::

优点

极大的降低了程序的内存占用

缺点

  • 提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
  • 对设计者的水平提出了更高的要求,要求其可以准确的设计出合理的共享对象。

    业务场景

    二狗设计五子棋的架构,在分析到棋子那一块的时候,他想到了享元模式。他是这么分析的:这里需要大量的棋子对象,它们除了颜色有黑白之分,摆放的位置不同其他都一样,非常适合使用享元模式。

    代码示例

    Chess 棋子接口

    我们的棋子对象有一个绘制自己的通用操作

    1. public interface Chess {
    2. /**
    3. * 绘制棋子
    4. */
    5. void draw(int x, int y);
    6. }

    BlackChess WhiteChess 棋子的各种实现

    我们的棋子对象分为黑白两类,所以此处我们将颜色设计为对象的内部状态来共享,而不是外部状态,所以就需要黑白两个对象类。如果你把颜色作为外部状态,那么只需要一个对象类即可。

    1. public class BlackChess implements Chess {
    2. //内部状态,共享
    3. private final Color color = Color.BLACK;
    4. private final String sharp = "圆形";
    5. public Color getColor() {
    6. return color;
    7. }
    8. @Override
    9. public void draw(int x, int y) {
    10. System.out.println(String.format("%s%s棋子置于(%d,%d)处", sharp, color.getAlias(), x, y));
    11. }
    12. }
    1. public class WhiteChess implements Chess {
    2. //内部状态,共享
    3. private final Color color = Color.WHITE;
    4. private final String sharp = "圆形";
    5. public Color getColor() {
    6. return color;
    7. }
    8. @Override
    9. public void draw(int x, int y) {
    10. System.out.println(String.format("%s%s棋子置于(%d,%d)处", sharp, color.getAlias(), x, y));
    11. }
    12. }

    ChessFactory 共享对象工厂

    其负责提供共享对象,客户端不应该直接实例化棋子对象,而应该使用此工厂来获取。因为我们分了黑白两类对象,所以这里使用Color为key的map来存储共享对象。

    1. public class ChessFactory {
    2. private static final Map<Color, Chess> chessMap = new HashMap<>();
    3. public static Chess getChess(Color color) {
    4. Chess chess = chessMap.get(color);
    5. if (chess == null) {
    6. chess = color == Color.WHITE ? new WhiteChess() : new BlackChess();
    7. chessMap.put(color, chess);
    8. }
    9. return chess;
    10. }
    11. }

    Client

    1. public class Client {
    2. public static void main(String[] args) {
    3. //下黑子
    4. Chess backChess1 = ChessFactory.getChess(Color.BLACK);
    5. backChess1.draw(2, 5);
    6. //下白子
    7. Chess whiteChess = ChessFactory.getChess(Color.WHITE);
    8. whiteChess.draw(3, 5);
    9. //下黑子
    10. Chess backChess2 = ChessFactory.getChess(Color.BLACK);
    11. backChess2.draw(2, 6);
    12. System.out.println(String.format("backChess1:%d | backChess2:%d | whiteChess:%d",
    13. backChess1.hashCode(), backChess2.hashCode(), whiteChess.hashCode()));
    14. }
    15. }

    其他辅助类

    1. public enum Color {
    2. /**
    3. * 黑色
    4. */
    5. BLACK("黑色"),
    6. /**
    7. * 白色
    8. */
    9. WHITE("白色");
    10. /**
    11. * 名字
    12. */
    13. private String alias;
    14. Color(String alias) {
    15. this.alias = alias;
    16. }
    17. public String getAlias() {
    18. return alias;
    19. }
    20. }

    输出

    1. 圆形黑色棋子置于(25)处
    2. 圆形白色棋子置于(35)处
    3. 圆形黑色棋子置于(26)处
    4. backChess1:617901222 | backChess2:617901222 | whiteChess:1159190947

    从输出可见,backChess1 与 backChess2 是同一个对象,而whiteChess是另一个不同的对象。所以不论棋盘上有多少颗棋子,程序中只会保持最多两个棋子对象,这就极大的节约了内存。

    技术要点总结

  • 首先一定要区分出内部状态与外部状态,共享对象只持有内部状态,内部状态不可以从客户端设置,而外部状态必须从客户端设置。

  • 合理设计共享对象分类,大部分情况下会设计成一组,而不是一个共享对象。
  • 共享对象要求是不可变对象,从FlyWeightFactory获取到的对象都是一个原始的对象,而不是一个状态不确定的对象。