享元模式 (Flyweight Pattern)

享元模式 (Flyweight Pattern)

概述

享元模式是一种结构型设计模式,它运用共享技术有效地支持大量细粒度的对象。享元模式通过共享技术避免大量拥有相同内容对象的开销,提高系统资源的利用率。

意图

  • 运用共享技术有效地支持大量细粒度的对象

适用场景

  • 一个系统有大量相同或相似的对象,造成内存的大量耗费
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
  • 使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式

结构

复制代码
┌─────────────┐          ┌─────────────┐
│   Client    │──────────>│ FlyweightFactory│
├─────────────┤          ├─────────────┤
│             │          │ - flyweights│
└─────────────┘          │ + getFlyweight()│
                         └─────────────┘
                                 ▲
                                 │
┌─────────────┐          ┌─────────────┐
│UnsharedConcreteFlyweight│   │  Flyweight  │
├─────────────┤          ├─────────────┤
│ - intrinsicState│      │ + operation()│
│ + operation()│          └─────────────┘
└─────────────┘                  ▲
                                 │
                         ┌─────────────┐
                         │ConcreteFlyweight│
                         ├─────────────┤
                         │ - intrinsicState│
                         │ + operation()│
                         └─────────────┘

参与者

  • Flyweight:描述一个接口,通过这个接口flyweight可以接受并作用于外部状态
  • ConcreteFlyweight:实现Flyweight接口,并为内部状态增加存储空间。ConcreteFlyweight对象必须是可共享的,它所存储的状态必须是内部的
  • UnsharedConcreteFlyweight:并非所有的Flyweight子类都需要被共享,Flyweight接口使共享成为可能,但它并不强制共享
  • FlyweightFactory:创建并管理flyweight对象,确保合理地共享flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个
  • Client:维持一个对flyweight的引用,计算或存储一个flyweight的外部状态

示例代码

下面是一个完整的享元模式示例,以围棋游戏为例:

java 复制代码
import java.util.HashMap;
import java.util.Map;

// Flyweight - 享元接口
public interface ChessPiece {
    void display(int x, int y);
}

// ConcreteFlyweight - 具体享元类
public class ConcreteChessPiece implements ChessPiece {
    private String color; // 内部状态
    private String name;  // 内部状态
    
    public ConcreteChessPiece(String color, String name) {
        this.color = color;
        this.name = name;
    }
    
    @Override
    public void display(int x, int y) {
        System.out.println(color + " " + name + " 在位置 (" + x + ", " + y + ")");
    }
}

// FlyweightFactory - 享元工厂
public class ChessPieceFactory {
    private static Map<String, ChessPiece> pieces = new HashMap<>();
    
    public static ChessPiece getChessPiece(String color, String name) {
        String key = color + "_" + name;
        ChessPiece piece = pieces.get(key);
        
        if (piece == null) {
            piece = new ConcreteChessPiece(color, name);
            pieces.put(key, piece);
            System.out.println("创建新的棋子: " + color + " " + name);
        } else {
            System.out.println("使用已存在的棋子: " + color + " " + name);
        }
        
        return piece;
    }
    
    public static int getTotalPieces() {
        return pieces.size();
    }
}

// Client - 客户端
public class Client {
    public static void main(String[] args) {
        // 创建棋子
        ChessPiece blackKing = ChessPieceFactory.getChessPiece("黑色", "国王");
        blackKing.display(1, 1);
        
        ChessPiece whiteKing = ChessPieceFactory.getChessPiece("白色", "国王");
        whiteKing.display(8, 8);
        
        ChessPiece blackQueen = ChessPieceFactory.getChessPiece("黑色", "皇后");
        blackQueen.display(1, 4);
        
        ChessPiece whiteQueen = ChessPieceFactory.getChessPiece("白色", "皇后");
        whiteQueen.display(8, 4);
        
        // 使用已存在的棋子
        ChessPiece blackKing2 = ChessPieceFactory.getChessPiece("黑色", "国王");
        blackKing2.display(2, 2);
        
        ChessPiece whiteQueen2 = ChessPieceFactory.getChessPiece("白色", "皇后");
        whiteQueen2.display(7, 4);
        
        System.out.println("总共创建了 " + ChessPieceFactory.getTotalPieces() + " 个不同的棋子对象");
    }
}

另一个示例 - 文本编辑器

java 复制代码
import java.util.HashMap;
import java.util.Map;

// Flyweight - 享元接口
public interface Character {
    void display(int fontSize, String color);
}

// ConcreteFlyweight - 具体享元类
public class ConcreteCharacter implements Character {
    private char symbol; // 内部状态
    
    public ConcreteCharacter(char symbol) {
        this.symbol = symbol;
    }
    
    @Override
    public void display(int fontSize, String color) {
        System.out.println("字符 '" + symbol + "' - 字体大小: " + fontSize + ", 颜色: " + color);
    }
}

// FlyweightFactory - 享元工厂
public class CharacterFactory {
    private static Map<Character, Character> characters = new HashMap<>();
    
    public static Character getCharacter(char symbol) {
        Character character = characters.get(symbol);
        
        if (character == null) {
            character = new ConcreteCharacter(symbol);
            characters.put(symbol, character);
            System.out.println("创建新的字符对象: '" + symbol + "'");
        } else {
            System.out.println("使用已存在的字符对象: '" + symbol + "'");
        }
        
        return character;
    }
    
    public static int getTotalCharacters() {
        return characters.size();
    }
}

// UnsharedConcreteFlyweight - 非共享的具体享元类
public class Paragraph {
    private String text;
    private int fontSize;
    private String color;
    
    public Paragraph(String text, int fontSize, String color) {
        this.text = text;
        this.fontSize = fontSize;
        this.color = color;
    }
    
    public void display() {
        System.out.println("段落: " + text + " - 字体大小: " + fontSize + ", 颜色: " + color);
    }
}

// Client - 客户端
public class Client {
    public static void main(String[] args) {
        // 创建文档
        String document = "Hello World! This is a test document.";
        
        // 显示文档中的每个字符
        for (int i = 0; i < document.length(); i++) {
            char c = document.charAt(i);
            Character character = CharacterFactory.getCharacter(c);
            character.display(12, "black");
        }
        
        System.out.println("总共创建了 " + CharacterFactory.getTotalCharacters() + " 个不同的字符对象");
        
        // 创建非共享的段落对象
        Paragraph paragraph = new Paragraph("This is a paragraph.", 14, "blue");
        paragraph.display();
    }
}

优缺点

优点

  1. 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份
  2. 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享

缺点

  1. 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化
  2. 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间变长

相关模式

  • 组合模式:享元模式通常与组合模式一起使用,共享组合结构中的叶节点
  • 单例模式:享元工厂通常可以设计为单例
  • 策略模式:享元对象可以作为策略对象使用

实际应用

  • Java中的String常量池
  • Java中的Integer缓存
  • 数据库连接池
  • 线程池
  • 游戏中的大量相同对象(如子弹、树木等)
  • 文本编辑器中的字符对象

内部状态与外部状态

享元模式的核心是区分内部状态和外部状态:

  • 内部状态:存储在享元对象内部,不会随环境改变而改变,可以被共享
  • 外部状态:随环境改变而改变,不可以被共享,由客户端保存并在需要时传入享元对象

在实现享元模式时,需要仔细分析哪些状态是内部的,哪些是外部的,以便正确地实现共享。

享元模式的实现步骤

  1. 分析应用中哪些对象可以被共享
  2. 将这些对象的状态分为内部状态和外部状态
  3. 创建享元接口,定义操作外部状态的方法
  4. 创建具体享元类,实现享元接口,存储内部状态
  5. 创建享元工厂,管理享元对象的创建和共享
  6. 客户端通过享元工厂获取享元对象,并传入外部状态

注意事项

  1. 享元模式适用于大量相似对象的场景,如果对象数量不多,使用享元模式可能得不偿失
  2. 享元模式会增加系统的复杂性,需要仔细分析内部状态和外部状态
  3. 享元模式需要维护一个享元池,这会增加一定的内存开销
  4. 享元模式通常与工厂模式一起使用,由工厂来管理享元对象的创建和共享
相关推荐
YigAin1 天前
Unity23种设计模式之 享元模式
设计模式·享元模式
会员果汁11 天前
22.设计模式-享元模式(Flyweight)
设计模式·哈希算法·享元模式
apolloyhl20 天前
FlyWeight 享元模式
享元模式
小码过河.21 天前
设计模式——享元模式
java·设计模式·享元模式
亲爱的非洲野猪25 天前
深入解析享元模式:用Java实现高性能对象复用
java·开发语言·享元模式
Geoking.25 天前
【设计模式】享元模式(Flyweight)详解:用共享对象对抗内存爆炸
java·设计模式·享元模式
Engineer邓祥浩25 天前
设计模式学习(13) 23-11 享元模式
学习·设计模式·享元模式
sxlishaobin1 个月前
设计模式之享元模式
java·设计模式·享元模式
阿闽ooo1 个月前
深入浅出享元模式:从图形编辑器看对象复用的艺术
c++·设计模式·编辑器·享元模式