Bootstrap

设计模式之 享元模式

享元模式(Flyweight Pattern)是一种结构型设计模式,用于减少系统中对象的数量,从而节省内存和提升性能。它通过共享相同的对象来避免重复创建类似的对象。该模式尤其适用于对象数量庞大、且重复内容较多的场景。

核心思想:将对象的状态划分为内蕴状态(Intrinsic State)和外蕴状态(Extrinsic State),内蕴状态是可以共享的部分,而外蕴状态则由客户端管理并在需要时动态传递。

一、享元模式的特点

  1. 状态拆分

    • 内蕴状态:对象中可以共享的不变部分,由享元对象内部存储。
    • 外蕴状态:对象中可变的部分,不存储在享元对象中,由客户端动态提供。
  2. 对象共享

    系统只会为每种内蕴状态创建一个享元对象,并在不同上下文中重复使用这些对象。
  3. 性能优化

    通过共享对象,减少内存开销,提升系统性能。

二、享元模式的结构

享元模式主要包含以下角色:

  1. 抽象享元(Flyweight)

    定义享元对象的公共接口,外部通过该接口与具体享元对象交互。
  2. 具体享元(ConcreteFlyweight)

    实现抽象享元接口,并存储内蕴状态。具体享元对象需要支持可以共享的功能。
  3. 非共享具体享元(UnsharedConcreteFlyweight)

    并非所有的享元对象都需要共享,这些对象不能被多个客户端共享,但也实现了享元接口。
  4. 享元工厂(FlyweightFactory)

    管理享元对象的创建和共享,确保客户端获取的享元对象是唯一的,避免重复创建。
  5. 客户端(Client)

    负责维护外蕴状态,并与享元对象交互。

三、享元模式的工作原理

  1. 客户端通过享元工厂获取享元对象。
  2. 如果享元对象已存在,工厂返回共享对象;如果不存在,则工厂创建新的享元对象。
  3. 客户端将外蕴状态传递给享元对象,享元对象结合其内蕴状态执行操作。

四、享元模式的实现

下面通过一个实例展示享元模式的实现。

示例:棋盘上的棋子

在一个棋盘游戏中,棋子的位置是变化的(外蕴状态),而棋子的颜色(黑/白)是固定的(内蕴状态)。我们可以使用享元模式来优化棋子的管理。

  • 抽象享元类
    public interface ChessPiece {
        void display(int x, int y);
    }
  • 具体享元类
    public class ConcreteChessPiece implements ChessPiece{
    
        private String color;
        public ConcreteChessPiece(String color){
            this.color = color;
        }
        @Override
        public void display(int x,int y) {
            System.out.println("棋子颜色" + color + ",棋子位置:(" + x + "," + y + ")" );
        }
    }
  • 享元工厂
    public class ChessPieceFactory {
        private static Map<String,ChessPiece> map = new HashMap<>();
    
           public static ChessPiece getChessPiece(String color) {
            if (!map.containsKey(color)) {
                map.put(color, new ConcreteChessPiece(color));
            }
            return map.get(color);
        }
    }
    
  • 客户端
    public class Client {
        public static void main(String[] args) {
            // 获取共享的棋子
            ChessPiece black1 = ChessPieceFactory.getChessPiece("黑色");
            ChessPiece black2 = ChessPieceFactory.getChessPiece("黑色");
            ChessPiece white1 = ChessPieceFactory.getChessPiece("白色");
    
            // 设置外蕴状态(棋子的位置)
            black1.display(1, 1);
            black2.display(2, 3);
            white1.display(4, 5);
    
            // 验证共享对象
            System.out.println("黑色棋子是否相同: " + (black1 == black2));
        }
    }
  • 运行结果

五、享元模式的优缺点

优点:
  1. 减少内存占用

    通过共享对象,避免了重复的对象实例,大幅度降低系统的内存开销。
  2. 提升性能

    减少对象创建的次数和垃圾回收的频率,提升系统性能。
  3. 适用于大量小对象

    享元模式尤其适合那些数量多、内容重复的对象。
缺点:
  1. 实现复杂

    需要对对象的状态进行拆分,并精心设计共享机制,增加了系统的复杂度。
  2. 不适用所有场景

    如果共享对象的数量过少,或者内蕴状态和外蕴状态之间的界限不清晰,享元模式的优势难以体现。
  3. 增加了客户端的职责

    客户端需要管理外蕴状态,这可能导致客户端代码的复杂度提高。

六、享元模式的应用场景

享元模式适用于以下场景:

  1. 大量相似对象

    系统中存在大量内容重复的对象,且这些对象的部分状态是可以共享的。
  2. 内存优化需求强

    内存占用是系统性能的瓶颈,迫切需要通过共享对象来节省内存。
  3. 不变性场景

    对象的共享部分是不可变的,这样才能安全地在不同上下文中共享。
常见案例:
  • 文本处理:在文本编辑器中,每个字符的字体、颜色等属性可以共享,具体的位置由外部管理。
  • 图形界面:在绘图程序中,重复的图形对象(如线条、圆形)可以共享。
  • 游戏开发:在大型游戏中,地图上的树木、建筑等对象可以共享。
  • 数据库连接池:共享连接对象,避免重复创建连接。

七、享元模式的拓展

  1. 非共享享元

    并非所有对象都适合共享。如果某些对象需要独占使用,则可以通过非共享享元类实现。
  2. 复合享元模式

    组合模式和享元模式结合使用,可以将享元对象组成一个树形结构,从而支持更复杂的共享场景。
  3. 线程安全性

    享元对象通常是不可变的,这样可以在多线程环境中安全共享。
;