小易说IT 小易说IT

Java 享元模式 (Flyweight) 示例代码

享元模式:运用共享技术,有效支持大量细粒度对象。区分内部状态(可共享,不变)和外部状态(不可共享,由外部传入),减少对象创建数量,降低内存占用。

适用场景:系统需要创建大量同类对象,造成内存压力;对象大部分状态可以外部化。

UML 角色

  1. Flyweight(抽象享元):定义接口,接收外部状态

  2. ConcreteFlyweight(具体享元):保存内部状态,可共享

  3. UnsharedConcreteFlyweight(非共享享元):不需要共享的对象

  4. FlyweightFactory(享元工厂):管理享元池,复用对象

完整示范代码

import java.util.HashMap;
import java.util.Map;

/**
 * 抽象享元角色:棋子
 * 内部状态:棋子类型(黑棋/白棋,不变,可以共享)
 * 外部状态:坐标 x,y(位置会变,不存到对象内部,调用时传入)
 */
public interface ChessFlyweight {
    // 传入外部状态:坐标
    void display(int x, int y);
}

/**
 * 具体享元:围棋棋子,可共享
 */
class ConcreteChess implements ChessFlyweight {
    // 内部状态:棋子颜色,固定不变,可以共享
    private final String color;

    public ConcreteChess(String color) {
        this.color = color;
    }

    @Override
    public void display(int x, int y) {
        System.out.printf("棋子颜色:%s,位置:(x=%d, y=%d),对象哈希:%d%n",
                color, x, y, this.hashCode());
    }
}

/**
 * 享元工厂:负责创建、缓存棋子对象
 */
class ChessFlyweightFactory {
    // 享元池,缓存已经创建好的棋子
    private final Map<String, ChessFlyweight> flyweightPool = new HashMap<>();

    // 获取棋子,存在就复用,不存在新建
    public ChessFlyweight getChess(String color) {
        if (!flyweightPool.containsKey(color)) {
            flyweightPool.put(color, new ConcreteChess(color));
            System.out.println("新建棋子对象:" + color);
        } else {
            System.out.println("复用已有棋子对象:" + color);
        }
        return flyweightPool.get(color);
    }

    // 获取池内对象数量
    public int getPoolSize() {
        return flyweightPool.size();
    }
}

// 测试客户端
class FlyweightDemo {
    public static void main(String[] args) {
        ChessFlyweightFactory factory = new ChessFlyweightFactory();

        // 多次落子,大量棋子,实际只创建2个对象(黑、白)
        ChessFlyweight c1 = factory.getChess("黑色");
        c1.display(1,1);

        ChessFlyweight c2 = factory.getChess("黑色");
        c2.display(2,3);

        ChessFlyweight c3 = factory.getChess("白色");
        c3.display(5,5);

        ChessFlyweight c4 = factory.getChess("白色");
        c4.display(7,8);

        System.out.println("享元池对象总数:" + factory.getPoolSize());
        // c1 和 c2 hashcode 完全相同,是同一个对象;c3 c4 同一个对象
    }
}

输出结果

新建棋子对象:黑色
棋子颜色:黑色,位置:(x=1, y=1),对象哈希:123456
复用已有棋子对象:黑色
棋子颜色:黑色,位置:(x=2, y=3),对象哈希:123456
新建棋子对象:白色
棋子颜色:白色,位置:(x=5, y=5),对象哈希:789012
复用已有棋子对象:白色
棋子颜色:白色,位置:(x=7, y=8),对象哈希:789012
享元池对象总数:2

可以看到:虽然落子 4 次,但是只实例化 2 个对象,坐标 x,y 作为外部状态每次调用传入,对象本身被复用。

内部状态 vs 外部状态

表格

状态

特点

本例

内部状态

不变、可共享,保存在享元对象

棋子颜色

外部状态

变化、不可共享,由客户端传入

棋盘坐标 x,y

JDK 中享元模式典型例子

Integer.valueOf() 缓存 -128~127,就是享元模式:

Integer a = Integer.valueOf(10);
Integer b = Integer.valueOf(10);
// a == b ,复用缓存对象

优缺点

✅优点:

  1. 极大减少内存中对象数量,降低内存开销

  2. 对象复用,性能提升

❌缺点:

  1. 需要区分内外状态,增加代码复杂度

  2. 外部状态由客户端维护,容易出错

使用注意

不要把变化的数据放到享元对象内部;变化数据全部作为参数传入方法。


本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。

原文链接 https://www.yijunzhao.cc/archives/java-flyweight-pattern-example-code

欢迎访问 https://www.yijunzhao.cc/

https://www.yijunzhao.cc/