7.1 章节概述
本章将深入学习两个重要的结构型设计模式:组合模式(Composite Pattern)和享元模式(Flyweight Pattern)。这两个模式分别解决了不同的设计问题:
- 组合模式:处理树形结构,让客户端统一处理单个对象和对象组合
- 享元模式:通过共享技术有效支持大量细粒度对象,优化内存使用
7.1.1 学习目标
- 理解组合模式的设计思想和适用场景
- 掌握组合模式的实现方式和最佳实践
- 理解享元模式的核心概念和优化原理
- 学会在实际项目中应用这两种模式
- 了解模式的优缺点和注意事项
7.1.2 应用场景预览
- 组合模式:文件系统、UI组件树、组织架构、表达式解析
- 享元模式:文本编辑器、游戏开发、图形渲染、缓存系统
7.2 组合模式(Composite Pattern)
7.2.1 模式定义与动机
定义: 组合模式将对象组合成树形结构以表示”部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
动机: - 需要表示对象的部分-整体层次结构 - 希望用户忽略组合对象与单个对象的不同 - 统一处理树形结构中的所有对象
7.2.2 模式结构
组合模式包含以下角色:
- Component(抽象构件):定义参与组合的对象的共同接口
- Leaf(叶子构件):表示组合中的叶子节点,没有子节点
- 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 适用场景
- 树形结构:需要表示对象的部分-整体层次结构
- 统一处理:希望用户忽略组合对象与单个对象的不同
- 递归结构:结构中的对象具有相似的操作接口
7.3 享元模式(Flyweight Pattern)
7.3.1 模式定义与动机
定义: 享元模式运用共享技术有效地支持大量细粒度的对象。
动机: - 系统中存在大量相似对象 - 对象的创建和存储成本很高 - 对象的大部分状态可以外部化 - 需要优化内存使用和提高性能
7.3.2 核心概念
- 内部状态(Intrinsic State):存储在享元对象内部,可以共享
- 外部状态(Extrinsic State):依赖于环境,不可共享,由客户端维护
- 享元工厂:管理享元对象的创建和共享
7.3.3 模式结构
- Flyweight(抽象享元):定义享元对象的接口
- ConcreteFlyweight(具体享元):实现享元接口,存储内部状态
- FlyweightFactory(享元工厂):管理享元对象的创建和共享
- 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 适用场景
- 大量对象:系统中需要创建大量相似对象
- 内存敏感:对象创建和存储成本很高
- 状态分离:对象的大部分状态可以外部化
- 性能要求:需要优化内存使用和创建性能
7.4 模式对比与选择
7.4.1 组合模式 vs 享元模式
对比维度 | 组合模式 | 享元模式 |
---|---|---|
主要目的 | 处理树形结构,统一单个对象和组合对象 | 通过共享减少内存使用 |
结构特点 | 树形层次结构 | 扁平化共享结构 |
适用场景 | 部分-整体关系,递归结构 | 大量相似对象,内存优化 |
性能关注 | 操作的统一性和简化 | 内存使用和创建性能 |
实现复杂度 | 中等,主要是接口设计 | 较高,需要状态分离 |
7.4.2 选择指南
选择组合模式的情况: - 需要表示对象的部分-整体层次结构 - 希望客户端统一处理单个对象和组合对象 - 结构具有递归特性
选择享元模式的情况: - 系统中存在大量相似对象 - 内存使用是关键考虑因素 - 对象的状态可以分为内部和外部两部分
7.4.3 组合使用
两种模式可以结合使用: - 在组合模式的叶子节点中使用享元模式 - 享元对象可以作为组合结构的一部分
7.5 本章总结
7.5.1 核心概念回顾
组合模式:
- 统一处理单个对象和组合对象
- 适用于树形结构和递归操作
- 简化客户端代码,提高扩展性
享元模式:
- 通过共享技术优化内存使用
- 分离内部状态和外部状态
- 适用于大量相似对象的场景
7.5.2 最佳实践
组合模式最佳实践:
- 保持组件接口的一致性
- 合理设计叶子和容器的职责
- 考虑安全性和透明性的平衡
- 提供便捷的遍历和搜索功能
享元模式最佳实践:
- 正确识别和分离内部外部状态
- 使用工厂模式管理享元对象
- 考虑线程安全问题
- 权衡内存节省与复杂性
7.5.3 实际应用建议
1.2. 性能考虑: - 组合模式注意递归调用的性能影响 - 享元模式要权衡内存节省与运行时开销
- 设计决策:
- 根据具体需求选择合适的模式
- 考虑模式的组合使用
- 注意模式的适用边界
7.5.4 注意事项
组合模式注意事项:
- 避免过深的递归层次
- 合理处理循环引用
- 考虑组件的生命周期管理
享元模式注意事项:
- 确保内部状态真正不可变
- 避免过度优化导致复杂性增加
- 注意多线程环境下的安全性
7.6 练习题
7.6.1 基础练习
组合模式练习:
- 设计一个公司组织架构系统,包含部门和员工
- 实现一个数学表达式解析器,支持加减乘除和括号
享元模式练习:
- 实现一个棋类游戏的棋子系统
- 设计一个图标缓存系统,优化UI界面的内存使用
7.6.2 进阶练习
组合应用:
- 在组合模式的文件系统中集成享元模式优化文件类型
- 设计一个GUI框架,支持复杂的组件嵌套和样式共享
性能优化:
- 比较使用和不使用享元模式的内存使用差异
- 实现一个支持大量节点的树形结构,优化遍历性能
7.6.3 思考题
- 在什么情况下组合模式可能导致性能问题?如何解决?
- 享元模式中如何处理状态变化的问题?
- 如何在分布式系统中应用享元模式?
- 组合模式和装饰器模式有什么区别和联系?
下一章预告: 接下来我们将学习行为型设计模式的基础,包括策略模式和模板方法模式。