设计模式-享元模式

要理解设计模式中的享元模式,我们可以从其核心思想入手:通过共享技术复用大量相似对象,从而减少内存消耗和对象创建成本。它的本质是分离对象的“内部状态”(可共享、不变的部分)和“外部状态”(不可共享、随场景变化的部分),让相似对象共享内部状态,仅通过外部状态区分差异。

一、享元模式的核心角色

享元模式包含4个核心角色,各自职责如下:

角色 职责描述
抽象享元(Flyweight) 声明享元对象的接口,定义内部状态的访问方法,以及接收外部状态的操作方法。
具体享元(ConcreteFlyweight) 实现抽象享元接口,存储内部状态(可共享),并在操作中结合外部状态完成业务逻辑。
享元工厂(FlyweightFactory) 负责创建和管理享元对象,维护一个“享元池”(缓存),确保相同内部状态的对象只被创建一次。
客户端(Client) 负责创建或获取享元对象,为享元对象传递外部状态,并使用享元对象完成操作。

二、Java代码演示

我们以“围棋游戏”为例(围棋有大量棋子,仅黑白两色,颜色是可共享的内部状态,位置是随落子变化的外部状态),演示享元模式的实现。

1. 定义抽象享元(Flyweight)

声明棋子的核心操作(如渲染棋子),并定义内部状态(颜色)的访问方法:

// 抽象享元:棋子
public interface ChessPiece {
    // 获取内部状态(颜色)
    String getColor();
    // 渲染棋子(结合外部状态:位置)
    void render(int x, int y);
}

2. 定义具体享元(ConcreteFlyweight)

实现抽象享元,存储内部状态(颜色),并在渲染时结合外部状态(位置):

// 具体享元:黑色棋子
public class BlackChessPiece implements ChessPiece {
    // 内部状态(可共享:所有黑棋颜色相同)
    private final String color = "黑色";

    @Override
    public String getColor() {
        return color;
    }

    @Override
    public void render(int x, int y) {
        // 结合外部状态(位置x,y)执行渲染
        System.out.println("渲染" + color + "棋子,位置:(" + x + "," + y + ")");
    }
}

// 具体享元:白色棋子
public class WhiteChessPiece implements ChessPiece {
    // 内部状态(可共享:所有白棋颜色相同)
    private final String color = "白色";

    @Override
    public String getColor() {
        return color;
    }

    @Override
    public void render(int x, int y) {
        System.out.println("渲染" + color + "棋子,位置:(" + x + "," + y + ")");
    }
}

3. 定义享元工厂(FlyweightFactory)

创建并管理享元池,确保相同颜色的棋子只被实例化一次:

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

// 享元工厂:棋子工厂
public class ChessPieceFactory {
    // 享元池(缓存棋子对象,key为颜色)
    private static final Map<String, ChessPiece> chessPieces = new HashMap<>();

    // 获取棋子(如果存在则复用,不存在则创建并缓存)
    public static ChessPiece getChessPiece(String color) {
        // 从缓存中获取
        ChessPiece piece = chessPieces.get(color);
        // 缓存中没有则创建
        if (piece == null) {
            switch (color) {
                case "黑色":
                    piece = new BlackChessPiece();
                    break;
                case "白色":
                    piece = new WhiteChessPiece();
                    break;
                default:
                    throw new IllegalArgumentException("不支持的棋子颜色:" + color);
            }
            // 存入缓存
            chessPieces.put(color, piece);
            System.out.println("创建新的" + color + "棋子");
        } else {
            System.out.println("复用已有的" + color + "棋子");
        }
        return piece;
    }
}

4. 客户端(Client)测试

模拟围棋落子过程,通过工厂获取棋子并传入外部状态(位置):

public class Client {
    public static void main(String[] args) {
        // 第一次获取黑色棋子(创建新对象)
        ChessPiece black1 = ChessPieceFactory.getChessPiece("黑色");
        black1.render(1, 1); // 输出:渲染黑色棋子,位置:(1,1)

        // 第二次获取黑色棋子(复用已有对象)
        ChessPiece black2 = ChessPieceFactory.getChessPiece("黑色");
        black2.render(3, 3); // 输出:渲染黑色棋子,位置:(3,3)

        // 第一次获取白色棋子(创建新对象)
        ChessPiece white1 = ChessPieceFactory.getChessPiece("白色");
        white1.render(2, 2); // 输出:渲染白色棋子,位置:(2,2)

        // 第二次获取白色棋子(复用已有对象)
        ChessPiece white2 = ChessPieceFactory.getChessPiece("白色");
        white2.render(4, 4); // 输出:渲染白色棋子,位置:(4,4)

        // 验证是否为同一对象(通过内存地址哈希值)
        System.out.println("black1与black2是否为同一对象:" + (black1 == black2)); // true
        System.out.println("white1与white2是否为同一对象:" + (white1 == white2)); // true
    }
}

三、享元模式的优点

  1. 减少对象数量:通过共享相似对象,大幅降低系统中实例的数量,节省内存空间(如上例中,无论下多少黑棋/白棋,都只需要2个对象)。
  2. 提高性能:减少对象创建和垃圾回收的开销,尤其适合需要大量相似对象的场景(如游戏中的粒子、文本编辑器中的字符)。
  3. 分离状态:清晰区分内部状态(共享)和外部状态(独立),符合“单一职责原则”。

通过这个例子,我们可以清晰看到:享元模式通过共享可复用的内部状态,让大量相似对象只需要少量实例,从而高效解决了“对象过多导致的内存浪费”问题。它特别适合系统中存在大量细粒度对象,且这些对象的大部分状态可以共享的场景。

posted @ 2025-11-05 09:32  fishyy  阅读(5)  评论(0)    收藏  举报