享元模式 (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. 享元模式通常与工厂模式一起使用,由工厂来管理享元对象的创建和共享
相关推荐
会员果汁9 小时前
22.设计模式-享元模式(Flyweight)
设计模式·哈希算法·享元模式
apolloyhl10 天前
FlyWeight 享元模式
享元模式
小码过河.11 天前
设计模式——享元模式
java·设计模式·享元模式
亲爱的非洲野猪14 天前
深入解析享元模式:用Java实现高性能对象复用
java·开发语言·享元模式
Geoking.15 天前
【设计模式】享元模式(Flyweight)详解:用共享对象对抗内存爆炸
java·设计模式·享元模式
Engineer邓祥浩15 天前
设计模式学习(13) 23-11 享元模式
学习·设计模式·享元模式
sxlishaobin21 天前
设计模式之享元模式
java·设计模式·享元模式
阿闽ooo1 个月前
深入浅出享元模式:从图形编辑器看对象复用的艺术
c++·设计模式·编辑器·享元模式
老朱佩琪!1 个月前
Unity享元模式
unity·游戏引擎·享元模式