7.1 章节概述

本章将深入学习两个重要的结构型设计模式:组合模式(Composite Pattern)和享元模式(Flyweight Pattern)。这两个模式分别解决了不同的设计问题:

  • 组合模式:处理树形结构,让客户端统一处理单个对象和对象组合
  • 享元模式:通过共享技术有效支持大量细粒度对象,优化内存使用

7.1.1 学习目标

  1. 理解组合模式的设计思想和适用场景
  2. 掌握组合模式的实现方式和最佳实践
  3. 理解享元模式的核心概念和优化原理
  4. 学会在实际项目中应用这两种模式
  5. 了解模式的优缺点和注意事项

7.1.2 应用场景预览

  • 组合模式:文件系统、UI组件树、组织架构、表达式解析
  • 享元模式:文本编辑器、游戏开发、图形渲染、缓存系统

7.2 组合模式(Composite Pattern)

7.2.1 模式定义与动机

定义: 组合模式将对象组合成树形结构以表示”部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

动机: - 需要表示对象的部分-整体层次结构 - 希望用户忽略组合对象与单个对象的不同 - 统一处理树形结构中的所有对象

7.2.2 模式结构

组合模式包含以下角色:

  1. Component(抽象构件):定义参与组合的对象的共同接口
  2. Leaf(叶子构件):表示组合中的叶子节点,没有子节点
  3. Composite(容器构件):表示有子节点的节点,实现子节点相关操作

7.2.3 Python实现示例:文件系统

from abc import ABC, abstractmethod
from typing import List, Optional
import os
from datetime import datetime

# 抽象构件
class FileSystemComponent(ABC):
    """文件系统组件抽象基类"""
    
    def __init__(self, name: str):
        self.name = name
        self.parent: Optional['FileSystemComponent'] = None
    
    @abstractmethod
    def get_size(self) -> int:
        """获取大小"""
        pass
    
    @abstractmethod
    def display(self, indent: int = 0) -> str:
        """显示结构"""
        pass
    
    def get_path(self) -> str:
        """获取完整路径"""
        if self.parent is None:
            return self.name
        return f"{self.parent.get_path()}/{self.name}"
    
    @abstractmethod
    def search(self, name: str) -> List['FileSystemComponent']:
        """搜索文件或目录"""
        pass

# 叶子构件:文件
class File(FileSystemComponent):
    """文件类"""
    
    def __init__(self, name: str, size: int, content: str = ""):
        super().__init__(name)
        self.size = size
        self.content = content
        self.created_time = datetime.now()
        self.modified_time = datetime.now()
    
    def get_size(self) -> int:
        return self.size
    
    def display(self, indent: int = 0) -> str:
        spaces = "  " * indent
        return f"{spaces}📄 {self.name} ({self.size} bytes)"
    
    def read(self) -> str:
        """读取文件内容"""
        return self.content
    
    def write(self, content: str) -> None:
        """写入文件内容"""
        self.content = content
        self.size = len(content.encode('utf-8'))
        self.modified_time = datetime.now()
    
    def search(self, name: str) -> List[FileSystemComponent]:
        return [self] if self.name == name else []

# 容器构件:目录
class Directory(FileSystemComponent):
    """目录类"""
    
    def __init__(self, name: str):
        super().__init__(name)
        self.children: List[FileSystemComponent] = []
        self.created_time = datetime.now()
    
    def add(self, component: FileSystemComponent) -> None:
        """添加子组件"""
        component.parent = self
        self.children.append(component)
    
    def remove(self, component: FileSystemComponent) -> None:
        """移除子组件"""
        if component in self.children:
            component.parent = None
            self.children.remove(component)
    
    def get_child(self, name: str) -> Optional[FileSystemComponent]:
        """获取子组件"""
        for child in self.children:
            if child.name == name:
                return child
        return None
    
    def get_size(self) -> int:
        """计算目录总大小"""
        return sum(child.get_size() for child in self.children)
    
    def display(self, indent: int = 0) -> str:
        """显示目录结构"""
        spaces = "  " * indent
        result = f"{spaces}📁 {self.name}/ ({len(self.children)} items, {self.get_size()} bytes)\n"
        
        for child in self.children:
            result += child.display(indent + 1) + "\n"
        
        return result.rstrip()
    
    def search(self, name: str) -> List[FileSystemComponent]:
        """递归搜索"""
        results = []
        
        # 检查当前目录
        if self.name == name:
            results.append(self)
        
        # 递归搜索子组件
        for child in self.children:
            results.extend(child.search(name))
        
        return results
    
    def get_files_by_extension(self, extension: str) -> List[File]:
        """按扩展名获取文件"""
        files = []
        for child in self.children:
            if isinstance(child, File) and child.name.endswith(extension):
                files.append(child)
            elif isinstance(child, Directory):
                files.extend(child.get_files_by_extension(extension))
        return files

# 使用示例
def demonstrate_composite_pattern():
    """演示组合模式"""
    print("=== 组合模式演示:文件系统 ===")
    
    # 创建根目录
    root = Directory("root")
    
    # 创建子目录
    documents = Directory("Documents")
    pictures = Directory("Pictures")
    projects = Directory("Projects")
    
    # 创建文件
    readme = File("README.md", 1024, "# 项目说明\n这是一个示例项目")
    config = File("config.json", 512, '{"debug": true}')
    photo1 = File("vacation.jpg", 2048000)
    photo2 = File("family.png", 1536000)
    
    # 构建目录结构
    root.add(documents)
    root.add(pictures)
    root.add(projects)
    
    documents.add(readme)
    pictures.add(photo1)
    pictures.add(photo2)
    
    # 创建项目子目录
    web_project = Directory("WebProject")
    mobile_project = Directory("MobileProject")
    
    projects.add(web_project)
    projects.add(mobile_project)
    
    # 添加项目文件
    index_html = File("index.html", 2048, "<html><body>Hello World</body></html>")
    style_css = File("style.css", 1024, "body { margin: 0; }")
    app_js = File("app.js", 4096, "console.log('Hello World');")
    
    web_project.add(index_html)
    web_project.add(style_css)
    web_project.add(config)  # 共享配置文件
    
    mobile_project.add(app_js)
    
    # 显示文件系统结构
    print("\n文件系统结构:")
    print(root.display())
    
    # 搜索功能
    print("\n搜索 'config.json':")
    search_results = root.search("config.json")
    for result in search_results:
        print(f"找到: {result.get_path()}")
    
    # 按扩展名查找文件
    print("\n查找所有 .js 文件:")
    js_files = root.get_files_by_extension(".js")
    for js_file in js_files:
        print(f"JS文件: {js_file.get_path()} ({js_file.get_size()} bytes)")
    
    # 统计信息
    print(f"\n根目录总大小: {root.get_size()} bytes")
    print(f"Documents目录大小: {documents.get_size()} bytes")
    print(f"Pictures目录大小: {pictures.get_size()} bytes")
    print(f"Projects目录大小: {projects.get_size()} bytes")

if __name__ == "__main__":
    demonstrate_composite_pattern()

7.2.4 Java实现示例:UI组件树

import java.util.*;

// 抽象构件
abstract class UIComponent {
    protected String name;
    protected UIComponent parent;
    protected Map<String, Object> properties;
    
    public UIComponent(String name) {
        this.name = name;
        this.properties = new HashMap<>();
    }
    
    public abstract void render(int indent);
    public abstract int getComponentCount();
    public abstract void setProperty(String key, Object value);
    public abstract Object getProperty(String key);
    
    public String getName() {
        return name;
    }
    
    public void setParent(UIComponent parent) {
        this.parent = parent;
    }
    
    public UIComponent getParent() {
        return parent;
    }
    
    protected String getIndent(int level) {
        return "  ".repeat(level);
    }
}

// 叶子构件:按钮
class Button extends UIComponent {
    private String text;
    private String color;
    private boolean enabled;
    
    public Button(String name, String text) {
        super(name);
        this.text = text;
        this.color = "blue";
        this.enabled = true;
    }
    
    @Override
    public void render(int indent) {
        String indentStr = getIndent(indent);
        System.out.printf("%s🔘 Button[%s]: '%s' (color=%s, enabled=%s)%n", 
                         indentStr, name, text, color, enabled);
    }
    
    @Override
    public int getComponentCount() {
        return 1;
    }
    
    @Override
    public void setProperty(String key, Object value) {
        properties.put(key, value);
        switch (key) {
            case "text" -> this.text = (String) value;
            case "color" -> this.color = (String) value;
            case "enabled" -> this.enabled = (Boolean) value;
        }
    }
    
    @Override
    public Object getProperty(String key) {
        return switch (key) {
            case "text" -> text;
            case "color" -> color;
            case "enabled" -> enabled;
            default -> properties.get(key);
        };
    }
    
    public void click() {
        if (enabled) {
            System.out.println("Button '" + text + "' clicked!");
        } else {
            System.out.println("Button '" + text + "' is disabled!");
        }
    }
}

// 叶子构件:文本框
class TextBox extends UIComponent {
    private String value;
    private String placeholder;
    private boolean readonly;
    
    public TextBox(String name, String placeholder) {
        super(name);
        this.value = "";
        this.placeholder = placeholder;
        this.readonly = false;
    }
    
    @Override
    public void render(int indent) {
        String indentStr = getIndent(indent);
        System.out.printf("%s📝 TextBox[%s]: value='%s', placeholder='%s' (readonly=%s)%n", 
                         indentStr, name, value, placeholder, readonly);
    }
    
    @Override
    public int getComponentCount() {
        return 1;
    }
    
    @Override
    public void setProperty(String key, Object value) {
        properties.put(key, value);
        switch (key) {
            case "value" -> this.value = (String) value;
            case "placeholder" -> this.placeholder = (String) value;
            case "readonly" -> this.readonly = (Boolean) value;
        }
    }
    
    @Override
    public Object getProperty(String key) {
        return switch (key) {
            case "value" -> value;
            case "placeholder" -> placeholder;
            case "readonly" -> readonly;
            default -> properties.get(key);
        };
    }
}

// 容器构件:面板
class Panel extends UIComponent {
    private List<UIComponent> children;
    private String layout;
    private String backgroundColor;
    
    public Panel(String name, String layout) {
        super(name);
        this.children = new ArrayList<>();
        this.layout = layout;
        this.backgroundColor = "white";
    }
    
    public void add(UIComponent component) {
        component.setParent(this);
        children.add(component);
    }
    
    public void remove(UIComponent component) {
        component.setParent(null);
        children.remove(component);
    }
    
    public UIComponent getChild(String name) {
        return children.stream()
                .filter(child -> child.getName().equals(name))
                .findFirst()
                .orElse(null);
    }
    
    @Override
    public void render(int indent) {
        String indentStr = getIndent(indent);
        System.out.printf("%s📦 Panel[%s]: layout=%s, bg=%s (%d children)%n", 
                         indentStr, name, layout, backgroundColor, children.size());
        
        for (UIComponent child : children) {
            child.render(indent + 1);
        }
    }
    
    @Override
    public int getComponentCount() {
        return 1 + children.stream().mapToInt(UIComponent::getComponentCount).sum();
    }
    
    @Override
    public void setProperty(String key, Object value) {
        properties.put(key, value);
        switch (key) {
            case "layout" -> this.layout = (String) value;
            case "backgroundColor" -> this.backgroundColor = (String) value;
        }
    }
    
    @Override
    public Object getProperty(String key) {
        return switch (key) {
            case "layout" -> layout;
            case "backgroundColor" -> backgroundColor;
            default -> properties.get(key);
        };
    }
    
    public List<UIComponent> findComponentsByType(Class<?> type) {
        List<UIComponent> result = new ArrayList<>();
        
        for (UIComponent child : children) {
            if (type.isInstance(child)) {
                result.add(child);
            }
            if (child instanceof Panel) {
                result.addAll(((Panel) child).findComponentsByType(type));
            }
        }
        
        return result;
    }
}

// 使用示例
public class CompositePatternDemo {
    public static void main(String[] args) {
        System.out.println("=== 组合模式演示:UI组件树 ===");
        
        // 创建主窗口
        Panel mainWindow = new Panel("MainWindow", "BorderLayout");
        mainWindow.setProperty("backgroundColor", "lightgray");
        
        // 创建头部面板
        Panel headerPanel = new Panel("HeaderPanel", "FlowLayout");
        headerPanel.setProperty("backgroundColor", "darkblue");
        
        Button homeBtn = new Button("HomeButton", "首页");
        Button aboutBtn = new Button("AboutButton", "关于");
        Button contactBtn = new Button("ContactButton", "联系");
        
        headerPanel.add(homeBtn);
        headerPanel.add(aboutBtn);
        headerPanel.add(contactBtn);
        
        // 创建内容面板
        Panel contentPanel = new Panel("ContentPanel", "GridLayout");
        
        // 创建表单面板
        Panel formPanel = new Panel("FormPanel", "VerticalLayout");
        
        TextBox nameField = new TextBox("NameField", "请输入姓名");
        TextBox emailField = new TextBox("EmailField", "请输入邮箱");
        Button submitBtn = new Button("SubmitButton", "提交");
        Button resetBtn = new Button("ResetButton", "重置");
        
        formPanel.add(nameField);
        formPanel.add(emailField);
        formPanel.add(submitBtn);
        formPanel.add(resetBtn);
        
        contentPanel.add(formPanel);
        
        // 创建侧边栏
        Panel sidebarPanel = new Panel("SidebarPanel", "VerticalLayout");
        sidebarPanel.setProperty("backgroundColor", "lightblue");
        
        Button settingsBtn = new Button("SettingsButton", "设置");
        Button helpBtn = new Button("HelpButton", "帮助");
        
        sidebarPanel.add(settingsBtn);
        sidebarPanel.add(helpBtn);
        
        contentPanel.add(sidebarPanel);
        
        // 组装主窗口
        mainWindow.add(headerPanel);
        mainWindow.add(contentPanel);
        
        // 渲染UI
        System.out.println("\nUI组件树结构:");
        mainWindow.render(0);
        
        // 统计信息
        System.out.println("\n=== 统计信息 ===");
        System.out.println("总组件数: " + mainWindow.getComponentCount());
        
        // 查找特定类型的组件
        List<UIComponent> buttons = mainWindow.findComponentsByType(Button.class);
        System.out.println("按钮数量: " + buttons.size());
        
        List<UIComponent> textBoxes = mainWindow.findComponentsByType(TextBox.class);
        System.out.println("文本框数量: " + textBoxes.size());
        
        List<UIComponent> panels = mainWindow.findComponentsByType(Panel.class);
        System.out.println("面板数量: " + panels.size());
        
        // 模拟用户交互
        System.out.println("\n=== 用户交互演示 ===");
        homeBtn.click();
        submitBtn.click();
        
        // 动态修改属性
        System.out.println("\n=== 动态属性修改 ===");
        submitBtn.setProperty("enabled", false);
        submitBtn.click();
        
        nameField.setProperty("value", "张三");
        emailField.setProperty("value", "zhangsan@example.com");
        
        System.out.println("\n修改后的表单:");
        formPanel.render(0);
    }
}

7.2.5 组合模式的优缺点

优点: 1. 统一接口:客户端可以一致地处理单个对象和组合对象 2. 简化客户端:客户端不需要区分叶子和容器对象 3. 易于扩展:容易增加新的组件类型 4. 符合开闭原则:对扩展开放,对修改封闭

缺点: 1. 设计复杂:使设计变得更加抽象 2. 类型限制:难以限制组合中组件的类型 3. 性能开销:递归调用可能影响性能

7.2.6 适用场景

  1. 树形结构:需要表示对象的部分-整体层次结构
  2. 统一处理:希望用户忽略组合对象与单个对象的不同
  3. 递归结构:结构中的对象具有相似的操作接口

7.3 享元模式(Flyweight Pattern)

7.3.1 模式定义与动机

定义: 享元模式运用共享技术有效地支持大量细粒度的对象。

动机: - 系统中存在大量相似对象 - 对象的创建和存储成本很高 - 对象的大部分状态可以外部化 - 需要优化内存使用和提高性能

7.3.2 核心概念

  1. 内部状态(Intrinsic State):存储在享元对象内部,可以共享
  2. 外部状态(Extrinsic State):依赖于环境,不可共享,由客户端维护
  3. 享元工厂:管理享元对象的创建和共享

7.3.3 模式结构

  1. Flyweight(抽象享元):定义享元对象的接口
  2. ConcreteFlyweight(具体享元):实现享元接口,存储内部状态
  3. FlyweightFactory(享元工厂):管理享元对象的创建和共享
  4. Context(环境类):维护外部状态

7.3.4 Python实现示例:文本编辑器

from typing import Dict, List, Optional
from dataclasses import dataclass
from enum import Enum
import weakref

# 字符样式枚举
class FontStyle(Enum):
    NORMAL = "normal"
    BOLD = "bold"
    ITALIC = "italic"
    BOLD_ITALIC = "bold_italic"

# 抽象享元
class CharacterFlyweight:
    """字符享元抽象基类"""
    
    def render(self, position: int, size: int, color: str) -> str:
        """渲染字符,position, size, color是外部状态"""
        raise NotImplementedError

# 具体享元
class Character(CharacterFlyweight):
    """字符享元实现"""
    
    def __init__(self, char: str, font_family: str, style: FontStyle):
        # 内部状态:字符、字体族、样式
        self._char = char
        self._font_family = font_family
        self._style = style
    
    @property
    def char(self) -> str:
        return self._char
    
    @property
    def font_family(self) -> str:
        return self._font_family
    
    @property
    def style(self) -> FontStyle:
        return self._style
    
    def render(self, position: int, size: int, color: str) -> str:
        """渲染字符"""
        style_str = self._style.value.replace('_', ' ')
        return f"Char['{self._char}'] at pos={position}, font={self._font_family}, style={style_str}, size={size}px, color={color}"
    
    def __str__(self):
        return f"Character('{self._char}', {self._font_family}, {self._style.value})"
    
    def __repr__(self):
        return self.__str__()

# 享元工厂
class CharacterFactory:
    """字符享元工厂"""
    
    _instances: Dict[tuple, Character] = {}
    _instance_count = 0
    
    @classmethod
    def get_character(cls, char: str, font_family: str, style: FontStyle) -> Character:
        """获取字符享元对象"""
        key = (char, font_family, style)
        
        if key not in cls._instances:
            cls._instances[key] = Character(char, font_family, style)
            cls._instance_count += 1
            print(f"创建新的字符享元: {cls._instances[key]} (总数: {cls._instance_count})")
        
        return cls._instances[key]
    
    @classmethod
    def get_instance_count(cls) -> int:
        """获取享元实例数量"""
        return cls._instance_count
    
    @classmethod
    def get_all_instances(cls) -> Dict[tuple, Character]:
        """获取所有享元实例"""
        return cls._instances.copy()
    
    @classmethod
    def clear_cache(cls) -> None:
        """清空缓存"""
        cls._instances.clear()
        cls._instance_count = 0

# 环境类:文档字符
@dataclass
class DocumentCharacter:
    """文档中的字符(包含外部状态)"""
    flyweight: Character  # 享元对象
    position: int         # 位置(外部状态)
    size: int            # 字体大小(外部状态)
    color: str           # 颜色(外部状态)
    
    def render(self) -> str:
        return self.flyweight.render(self.position, self.size, self.color)

# 文档类
class Document:
    """文档类"""
    
    def __init__(self, name: str):
        self.name = name
        self.characters: List[DocumentCharacter] = []
        self.default_font = "Arial"
        self.default_style = FontStyle.NORMAL
        self.default_size = 12
        self.default_color = "black"
    
    def add_character(self, char: str, position: int, 
                     font_family: str = None, style: FontStyle = None,
                     size: int = None, color: str = None) -> None:
        """添加字符到文档"""
        # 使用默认值
        font_family = font_family or self.default_font
        style = style or self.default_style
        size = size or self.default_size
        color = color or self.default_color
        
        # 获取享元对象
        flyweight = CharacterFactory.get_character(char, font_family, style)
        
        # 创建文档字符
        doc_char = DocumentCharacter(flyweight, position, size, color)
        self.characters.append(doc_char)
    
    def add_text(self, text: str, start_position: int = 0,
                font_family: str = None, style: FontStyle = None,
                size: int = None, color: str = None) -> None:
        """添加文本到文档"""
        for i, char in enumerate(text):
            self.add_character(char, start_position + i, font_family, style, size, color)
    
    def render(self) -> str:
        """渲染整个文档"""
        result = f"Document: {self.name}\n"
        result += "=" * 50 + "\n"
        
        for doc_char in self.characters:
            result += doc_char.render() + "\n"
        
        return result
    
    def get_character_count(self) -> int:
        """获取字符总数"""
        return len(self.characters)
    
    def get_memory_usage_info(self) -> Dict[str, int]:
        """获取内存使用信息"""
        flyweight_count = CharacterFactory.get_instance_count()
        total_chars = self.get_character_count()
        
        return {
            "total_characters": total_chars,
            "flyweight_instances": flyweight_count,
            "memory_saved_ratio": round((1 - flyweight_count / max(total_chars, 1)) * 100, 2)
        }

# 使用示例
def demonstrate_flyweight_pattern():
    """演示享元模式"""
    print("=== 享元模式演示:文本编辑器 ===")
    
    # 创建文档
    doc = Document("示例文档")
    
    # 添加标题
    doc.add_text("设计模式学习", 0, "Arial", FontStyle.BOLD, 18, "blue")
    
    # 添加正文
    doc.add_text("\n\n享元模式是一种结构型设计模式。", 10, "Times New Roman", FontStyle.NORMAL, 12, "black")
    doc.add_text("它通过共享技术有效支持大量细粒度对象。", 50, "Times New Roman", FontStyle.NORMAL, 12, "black")
    
    # 添加重点文字
    doc.add_text("\n\n重要提示:", 100, "Arial", FontStyle.BOLD, 14, "red")
    doc.add_text("享元模式可以显著减少内存使用。", 110, "Arial", FontStyle.ITALIC, 12, "red")
    
    # 添加更多重复字符
    doc.add_text("\n\nAAAAAAAAA", 150, "Arial", FontStyle.NORMAL, 12, "green")
    doc.add_text("BBBBBBBBB", 160, "Arial", FontStyle.NORMAL, 12, "green")
    doc.add_text("CCCCCCCCC", 170, "Arial", FontStyle.NORMAL, 12, "green")
    
    # 显示文档内容
    print("\n文档内容:")
    print(doc.render())
    
    # 显示内存使用信息
    memory_info = doc.get_memory_usage_info()
    print("\n=== 内存使用分析 ===")
    print(f"文档总字符数: {memory_info['total_characters']}")
    print(f"享元实例数: {memory_info['flyweight_instances']}")
    print(f"内存节省比例: {memory_info['memory_saved_ratio']}%")
    
    # 显示所有享元实例
    print("\n=== 享元实例详情 ===")
    instances = CharacterFactory.get_all_instances()
    for key, instance in instances.items():
        char, font, style = key
        print(f"Key: ('{char}', '{font}', '{style.value}') -> {instance}")
    
    # 性能对比演示
    print("\n=== 性能对比演示 ===")
    demonstrate_performance_comparison()

def demonstrate_performance_comparison():
    """演示性能对比"""
    import time
    import sys
    
    # 清空享元缓存
    CharacterFactory.clear_cache()
    
    # 创建大量重复字符的文档
    large_doc = Document("大文档")
    
    # 模拟大量重复内容
    text_patterns = [
        "Hello World! ",
        "Python Programming ",
        "Design Patterns ",
        "Flyweight Example "
    ]
    
    start_time = time.time()
    position = 0
    
    # 重复添加文本模式
    for _ in range(100):  # 重复100次
        for pattern in text_patterns:
            large_doc.add_text(pattern, position)
            position += len(pattern)
    
    end_time = time.time()
    
    # 获取内存使用信息
    memory_info = large_doc.get_memory_usage_info()
    
    print(f"创建时间: {end_time - start_time:.4f} 秒")
    print(f"总字符数: {memory_info['total_characters']}")
    print(f"享元实例数: {memory_info['flyweight_instances']}")
    print(f"内存节省: {memory_info['memory_saved_ratio']}%")
    
    # 估算内存节省
    if memory_info['total_characters'] > 0:
        without_flyweight = memory_info['total_characters'] * 100  # 假设每个字符对象100字节
        with_flyweight = memory_info['flyweight_instances'] * 100 + memory_info['total_characters'] * 20  # 享元对象100字节 + 外部状态20字节
        
        print(f"\n估算内存使用:")
        print(f"不使用享元模式: {without_flyweight} 字节")
        print(f"使用享元模式: {with_flyweight} 字节")
        print(f"节省内存: {without_flyweight - with_flyweight} 字节 ({((without_flyweight - with_flyweight) / without_flyweight * 100):.1f}%)")

if __name__ == "__main__":
    demonstrate_flyweight_pattern()

7.3.5 Go实现示例:游戏开发

package main

import (
	"fmt"
	"sync"
	"time"
)

// 粒子类型枚举
type ParticleType int

const (
	Fire ParticleType = iota
	Water
	Earth
	Air
	Magic
)

func (pt ParticleType) String() string {
	switch pt {
	case Fire:
		return "Fire"
	case Water:
		return "Water"
	case Earth:
		return "Earth"
	case Air:
		return "Air"
	case Magic:
		return "Magic"
	default:
		return "Unknown"
	}
}

// 抽象享元接口
type ParticleFlyweight interface {
	Render(x, y, z float64, velocity Vector3D, color string) string
	GetType() ParticleType
	GetTexture() string
}

// 3D向量
type Vector3D struct {
	X, Y, Z float64
}

func (v Vector3D) String() string {
	return fmt.Sprintf("(%.2f, %.2f, %.2f)", v.X, v.Y, v.Z)
}

// 具体享元:粒子
type Particle struct {
	// 内部状态
	particleType ParticleType
	texture      string
	baseSize     float64
	lifetime     time.Duration
}

func NewParticle(pType ParticleType, texture string, baseSize float64, lifetime time.Duration) *Particle {
	return &Particle{
		particleType: pType,
		texture:      texture,
		baseSize:     baseSize,
		lifetime:     lifetime,
	}
}

func (p *Particle) Render(x, y, z float64, velocity Vector3D, color string) string {
	return fmt.Sprintf("Particle[%s] at (%.2f, %.2f, %.2f), velocity=%s, color=%s, texture=%s, size=%.2f, lifetime=%v",
		p.particleType, x, y, z, velocity, color, p.texture, p.baseSize, p.lifetime)
}

func (p *Particle) GetType() ParticleType {
	return p.particleType
}

func (p *Particle) GetTexture() string {
	return p.texture
}

// 享元工厂
type ParticleFactory struct {
	mu        sync.RWMutex
	particles map[ParticleType]ParticleFlyweight
	created   int
}

var (
	factory     *ParticleFactory
	factoryOnce sync.Once
)

func GetParticleFactory() *ParticleFactory {
	factoryOnce.Do(func() {
		factory = &ParticleFactory{
			particles: make(map[ParticleType]ParticleFlyweight),
		}
	})
	return factory
}

func (pf *ParticleFactory) GetParticle(pType ParticleType) ParticleFlyweight {
	pf.mu.RLock()
	particle, exists := pf.particles[pType]
	pf.mu.RUnlock()
	
	if exists {
		return particle
	}
	
	pf.mu.Lock()
	defer pf.mu.Unlock()
	
	// 双重检查
	if particle, exists := pf.particles[pType]; exists {
		return particle
	}
	
	// 创建新的粒子享元
	switch pType {
	case Fire:
		particle = NewParticle(Fire, "fire_texture.png", 2.0, 3*time.Second)
	case Water:
		particle = NewParticle(Water, "water_texture.png", 1.5, 5*time.Second)
	case Earth:
		particle = NewParticle(Earth, "earth_texture.png", 3.0, 10*time.Second)
	case Air:
		particle = NewParticle(Air, "air_texture.png", 1.0, 2*time.Second)
	case Magic:
		particle = NewParticle(Magic, "magic_texture.png", 2.5, 4*time.Second)
	default:
		particle = NewParticle(Fire, "default_texture.png", 1.0, 1*time.Second)
	}
	
	pf.particles[pType] = particle
	pf.created++
	fmt.Printf("创建新的粒子享元: %s (总数: %d)\n", pType, pf.created)
	
	return particle
}

func (pf *ParticleFactory) GetCreatedCount() int {
	pf.mu.RLock()
	defer pf.mu.RUnlock()
	return pf.created
}

func (pf *ParticleFactory) GetAllParticles() map[ParticleType]ParticleFlyweight {
	pf.mu.RLock()
	defer pf.mu.RUnlock()
	
	result := make(map[ParticleType]ParticleFlyweight)
	for k, v := range pf.particles {
		result[k] = v
	}
	return result
}

// 环境类:游戏粒子
type GameParticle struct {
	// 享元对象
	flyweight ParticleFlyweight
	
	// 外部状态
	position Vector3D
	velocity Vector3D
	color    string
	scale    float64
	id       int
}

func NewGameParticle(id int, pType ParticleType, pos, vel Vector3D, color string, scale float64) *GameParticle {
	factory := GetParticleFactory()
	flyweight := factory.GetParticle(pType)
	
	return &GameParticle{
		flyweight: flyweight,
		position:  pos,
		velocity:  vel,
		color:     color,
		scale:     scale,
		id:        id,
	}
}

func (gp *GameParticle) Render() string {
	return fmt.Sprintf("GameParticle[ID:%d] %s",
		gp.id, gp.flyweight.Render(gp.position.X, gp.position.Y, gp.position.Z, gp.velocity, gp.color))
}

func (gp *GameParticle) Update(deltaTime float64) {
	// 更新位置
	gp.position.X += gp.velocity.X * deltaTime
	gp.position.Y += gp.velocity.Y * deltaTime
	gp.position.Z += gp.velocity.Z * deltaTime
}

func (gp *GameParticle) GetType() ParticleType {
	return gp.flyweight.GetType()
}

// 粒子系统
type ParticleSystem struct {
	particles []*GameParticle
	nextID    int
}

func NewParticleSystem() *ParticleSystem {
	return &ParticleSystem{
		particles: make([]*GameParticle, 0),
		nextID:    1,
	}
}

func (ps *ParticleSystem) AddParticle(pType ParticleType, pos, vel Vector3D, color string, scale float64) {
	particle := NewGameParticle(ps.nextID, pType, pos, vel, color, scale)
	ps.particles = append(ps.particles, particle)
	ps.nextID++
}

func (ps *ParticleSystem) CreateExplosion(center Vector3D, particleCount int) {
	fmt.Printf("\n创建爆炸效果,中心点: %s,粒子数: %d\n", center, particleCount)
	
	for i := 0; i < particleCount; i++ {
		// 随机选择粒子类型
		pTypes := []ParticleType{Fire, Water, Earth, Air, Magic}
		pType := pTypes[i%len(pTypes)]
		
		// 随机位置和速度
		pos := Vector3D{
			X: center.X + float64(i%10-5),
			Y: center.Y + float64(i%8-4),
			Z: center.Z + float64(i%6-3),
		}
		
		vel := Vector3D{
			X: float64(i%20-10) * 0.5,
			Y: float64(i%15-7) * 0.3,
			Z: float64(i%12-6) * 0.4,
		}
		
		// 随机颜色
		colors := []string{"red", "blue", "green", "yellow", "purple", "orange"}
		color := colors[i%len(colors)]
		
		ps.AddParticle(pType, pos, vel, color, 1.0)
	}
}

func (ps *ParticleSystem) Update(deltaTime float64) {
	for _, particle := range ps.particles {
		particle.Update(deltaTime)
	}
}

func (ps *ParticleSystem) Render() {
	fmt.Println("\n=== 粒子系统渲染 ===")
	for _, particle := range ps.particles {
		fmt.Println(particle.Render())
	}
}

func (ps *ParticleSystem) GetParticleCount() int {
	return len(ps.particles)
}

func (ps *ParticleSystem) GetStatistics() map[ParticleType]int {
	stats := make(map[ParticleType]int)
	for _, particle := range ps.particles {
		stats[particle.GetType()]++
	}
	return stats
}

func (ps *ParticleSystem) GetMemoryInfo() (int, int, float64) {
	totalParticles := ps.GetParticleCount()
	flyweightCount := GetParticleFactory().GetCreatedCount()
	savingRatio := 0.0
	
	if totalParticles > 0 {
		savingRatio = (1.0 - float64(flyweightCount)/float64(totalParticles)) * 100
	}
	
	return totalParticles, flyweightCount, savingRatio
}

// 演示函数
func demonstrateFlyweightPattern() {
	fmt.Println("=== 享元模式演示:游戏粒子系统 ===")
	
	// 创建粒子系统
	ps := NewParticleSystem()
	
	// 创建多个爆炸效果
	explosionCenters := []Vector3D{
		{X: 0, Y: 0, Z: 0},
		{X: 10, Y: 5, Z: -3},
		{X: -5, Y: 8, Z: 2},
	}
	
	for i, center := range explosionCenters {
		fmt.Printf("\n--- 爆炸 %d ---", i+1)
		ps.CreateExplosion(center, 20)
	}
	
	// 显示部分粒子(避免输出过多)
	fmt.Println("\n=== 前10个粒子渲染示例 ===")
	for i, particle := range ps.particles {
		if i >= 10 {
			break
		}
		fmt.Println(particle.Render())
	}
	
	// 显示统计信息
	fmt.Println("\n=== 粒子统计 ===")
	stats := ps.GetStatistics()
	for pType, count := range stats {
		fmt.Printf("%s 粒子: %d 个\n", pType, count)
	}
	
	// 显示内存使用信息
	totalParticles, flyweightCount, savingRatio := ps.GetMemoryInfo()
	fmt.Println("\n=== 内存使用分析 ===")
	fmt.Printf("总粒子数: %d\n", totalParticles)
	fmt.Printf("享元实例数: %d\n", flyweightCount)
	fmt.Printf("内存节省比例: %.2f%%\n", savingRatio)
	
	// 显示所有享元实例
	fmt.Println("\n=== 享元实例详情 ===")
	allParticles := GetParticleFactory().GetAllParticles()
	for pType, flyweight := range allParticles {
		fmt.Printf("%s: texture=%s\n", pType, flyweight.GetTexture())
	}
	
	// 性能测试
	fmt.Println("\n=== 性能测试 ===")
	performanceTest()
}

func performanceTest() {
	start := time.Now()
	
	// 创建大量粒子
	ps := NewParticleSystem()
	for i := 0; i < 1000; i++ {
		pTypes := []ParticleType{Fire, Water, Earth, Air, Magic}
		pType := pTypes[i%len(pTypes)]
		
		pos := Vector3D{X: float64(i % 100), Y: float64(i % 50), Z: float64(i % 25)}
		vel := Vector3D{X: 1.0, Y: 0.5, Z: 0.2}
		
		ps.AddParticle(pType, pos, vel, "white", 1.0)
	}
	
	creationTime := time.Since(start)
	
	// 更新粒子
	start = time.Now()
	ps.Update(0.016) // 60 FPS
	updateTime := time.Since(start)
	
	totalParticles, flyweightCount, savingRatio := ps.GetMemoryInfo()
	
	fmt.Printf("创建 %d 个粒子耗时: %v\n", totalParticles, creationTime)
	fmt.Printf("更新粒子耗时: %v\n", updateTime)
	fmt.Printf("享元实例数: %d\n", flyweightCount)
	fmt.Printf("内存节省: %.2f%%\n", savingRatio)
	
	// 估算内存节省
	withoutFlyweight := totalParticles * 200 // 假设每个粒子对象200字节
	withFlyweight := flyweightCount*200 + totalParticles*50 // 享元200字节 + 外部状态50字节
	
	fmt.Printf("\n估算内存使用:\n")
	fmt.Printf("不使用享元模式: %d 字节\n", withoutFlyweight)
	fmt.Printf("使用享元模式: %d 字节\n", withFlyweight)
	fmt.Printf("节省内存: %d 字节 (%.1f%%)\n", 
		withoutFlyweight-withFlyweight, 
		float64(withoutFlyweight-withFlyweight)/float64(withoutFlyweight)*100)
}

func main() {
	demonstrateFlyweightPattern()
}

7.3.6 享元模式的优缺点

优点: 1. 减少内存使用:通过共享相同的对象实例 2. 提高性能:减少对象创建的开销 3. 集中管理:享元工厂统一管理对象 4. 透明性:客户端使用享元对象与普通对象无差异

缺点: 1. 复杂性增加:需要分离内部状态和外部状态 2. 运行时开销:需要维护外部状态 3. 线程安全:多线程环境下需要考虑同步 4. 调试困难:共享对象使调试变得复杂

7.3.7 适用场景

  1. 大量对象:系统中需要创建大量相似对象
  2. 内存敏感:对象创建和存储成本很高
  3. 状态分离:对象的大部分状态可以外部化
  4. 性能要求:需要优化内存使用和创建性能

7.4 模式对比与选择

7.4.1 组合模式 vs 享元模式

对比维度 组合模式 享元模式
主要目的 处理树形结构,统一单个对象和组合对象 通过共享减少内存使用
结构特点 树形层次结构 扁平化共享结构
适用场景 部分-整体关系,递归结构 大量相似对象,内存优化
性能关注 操作的统一性和简化 内存使用和创建性能
实现复杂度 中等,主要是接口设计 较高,需要状态分离

7.4.2 选择指南

选择组合模式的情况: - 需要表示对象的部分-整体层次结构 - 希望客户端统一处理单个对象和组合对象 - 结构具有递归特性

选择享元模式的情况: - 系统中存在大量相似对象 - 内存使用是关键考虑因素 - 对象的状态可以分为内部和外部两部分

7.4.3 组合使用

两种模式可以结合使用: - 在组合模式的叶子节点中使用享元模式 - 享元对象可以作为组合结构的一部分


7.5 本章总结

7.5.1 核心概念回顾

  1. 组合模式

    • 统一处理单个对象和组合对象
    • 适用于树形结构和递归操作
    • 简化客户端代码,提高扩展性
  2. 享元模式

    • 通过共享技术优化内存使用
    • 分离内部状态和外部状态
    • 适用于大量相似对象的场景

7.5.2 最佳实践

  1. 组合模式最佳实践

    • 保持组件接口的一致性
    • 合理设计叶子和容器的职责
    • 考虑安全性和透明性的平衡
    • 提供便捷的遍历和搜索功能
  2. 享元模式最佳实践

    • 正确识别和分离内部外部状态
    • 使用工厂模式管理享元对象
    • 考虑线程安全问题
    • 权衡内存节省与复杂性

7.5.3 实际应用建议

1.2. 性能考虑: - 组合模式注意递归调用的性能影响 - 享元模式要权衡内存节省与运行时开销

  1. 设计决策
    • 根据具体需求选择合适的模式
    • 考虑模式的组合使用
    • 注意模式的适用边界

7.5.4 注意事项

  1. 组合模式注意事项

    • 避免过深的递归层次
    • 合理处理循环引用
    • 考虑组件的生命周期管理
  2. 享元模式注意事项

    • 确保内部状态真正不可变
    • 避免过度优化导致复杂性增加
    • 注意多线程环境下的安全性

7.6 练习题

7.6.1 基础练习

  1. 组合模式练习

    • 设计一个公司组织架构系统,包含部门和员工
    • 实现一个数学表达式解析器,支持加减乘除和括号
  2. 享元模式练习

    • 实现一个棋类游戏的棋子系统
    • 设计一个图标缓存系统,优化UI界面的内存使用

7.6.2 进阶练习

  1. 组合应用

    • 在组合模式的文件系统中集成享元模式优化文件类型
    • 设计一个GUI框架,支持复杂的组件嵌套和样式共享
  2. 性能优化

    • 比较使用和不使用享元模式的内存使用差异
    • 实现一个支持大量节点的树形结构,优化遍历性能

7.6.3 思考题

  1. 在什么情况下组合模式可能导致性能问题?如何解决?
  2. 享元模式中如何处理状态变化的问题?
  3. 如何在分布式系统中应用享元模式?
  4. 组合模式和装饰器模式有什么区别和联系?

下一章预告: 接下来我们将学习行为型设计模式的基础,包括策略模式和模板方法模式。