软件工程结构型设计模式
- 引言
- 适配器设计模式
-
- 通俗解释
- 关键特点
- 适配器模式类型
- 现实生活例子
- 代码示例
-
- 示例1:类适配器(通过继承变通实现)
- 示例2:对象适配器(通过组合实现,推荐)
- 示例3:接口适配器(缺省适配器)
- [示例4:实际应用 - 第三方支付接口适配](#示例4:实际应用 - 第三方支付接口适配)
- 适配器模式的优缺点
- 使用场景
- [适配器模式 vs 其他模式](#适配器模式 vs 其他模式)
- Java中的适配器模式
- 桥接设计模式
- 组合设计模式
-
- 通俗解释
- 现实生活中的例子
- 关键特点
- 代码示例
-
- 示例1:文件系统(经典例子)
- 示例2:电商产品目录(更实用的例子)
- [示例3:更简化的版本(透明方式 vs 安全方式)](#示例3:更简化的版本(透明方式 vs 安全方式))
- 组合模式的优缺点
- 使用场景
- [组合模式 vs 其他模式](#组合模式 vs 其他模式)
- 装饰器设计模式
-
- 通俗解释
- 关键特点
- 现实生活例子
- 代码示例
-
- 示例1:咖啡订单系统(经典例子)
- 示例2:游戏角色装备系统
- [示例3:文本处理系统(Java I/O风格的例子)](#示例3:文本处理系统(Java I/O风格的例子))
- [装饰器模式 vs 其他模式](#装饰器模式 vs 其他模式)
- 装饰器模式的优缺点
- 实际应用场景
- 外观设计模式
- 享元设计模式
- 代理设计模式
软件工程中,设计模式提供了相关问题的解决方案,使得工程人员可以更加简单方便的复用成功的设计和体系结构。设计模式分为创建型,结构型,和行为型三大类。本文主要讲结构性设计模式。
引言
结构设型设计模式涉及如何组合类和对象以获得更大的结构。结构型设计模式分为适配器(Adapter),桥接(Bridge),组合(Composite),装饰(Decorator),外观(Facade),享元(Flyweight),代理(Proxy)。这里可以按照ABCD FFP。来进行简单顺序记忆。以下进行详细讲述。
适配器设计模式
通俗解释
想象需要给手机充电,但的充电器插头(两脚扁插)和插座(三脚圆孔)不匹配。这时候有两个选择:
- 更换的手机充电器(修改自己的代码)
- 使用一个转换插头(适配器)
适配器模式就是这个转换插头:它连接的充电器和酒店的插座,让它们能够正常工作,而不需要改变任何一方。
核心思想 :将一个类的接口转换成客户端期望的另一种接口,让原本接口不兼容的类可以一起工作。
关键特点
- 接口转换:让不兼容的接口能够协同工作
- 重用现有代码:无需修改现有类就能集成新功能
- 解耦客户端和被适配者:客户端通过适配器与被适配者交互
- 符合开闭原则:新增适配器而不修改现有代码
适配器模式类型
- 类适配器:通过继承实现(多重继承,Java中不可用,但可通过接口变通)
- 对象适配器:通过组合实现(推荐,更灵活)
- 接口适配器:缺省适配器(提供接口的默认实现)
现实生活例子
- 电源适配器:220V转5V给手机充电
- 读卡器:SD卡转USB接口
- 翻译官:不同语言之间的沟通桥梁
- USB转Type-C转换头:新旧设备连接
- 老式游戏手柄转USB:让旧设备在新系统上使用
代码示例
示例1:类适配器(通过继承变通实现)
java
// 1. 目标接口:客户端期望的接口(Type-C接口)
interface TypeCCharger {
void chargeWithTypeC();
int getPower();
}
// 2. 被适配者:现有的Micro USB充电器
class MicroUSBCharger {
public void chargeWithMicroUSB() {
System.out.println("使用Micro USB接口充电中...");
}
public int outputPower() {
return 10; // 10W功率
}
}
// 3. 适配器:将Micro USB转换为Type-C
class USBAdapter extends MicroUSBCharger implements TypeCCharger {
@Override
public void chargeWithTypeC() {
System.out.println("适配器:将Type-C转换为Micro USB");
super.chargeWithMicroUSB(); // 调用被适配者的方法
}
@Override
public int getPower() {
// 适配器还可以增强功能
int originalPower = super.outputPower();
System.out.println("原始功率: " + originalPower + "W");
return originalPower;
}
// 适配器特有的方法
public void showAdapterInfo() {
System.out.println("这是一个Micro USB转Type-C适配器");
}
}
// 4. 客户端:新款手机(只支持Type-C)
class NewPhone {
private String model;
public NewPhone(String model) {
this.model = model;
}
public void charge(TypeCCharger charger) {
System.out.println(model + " 开始充电...");
charger.chargeWithTypeC();
System.out.println("充电功率: " + charger.getPower() + "W");
System.out.println("充电完成!\n");
}
}
// 主程序
public class ClassAdapterDemo {
public static void main(String[] args) {
System.out.println("=== 适配器模式示例:类适配器(充电器转换) ===\n");
// 创建新款手机(只支持Type-C)
NewPhone phone = new NewPhone("iPhone 15");
System.out.println("情景1:使用原装Type-C充电器");
TypeCCharger typeCCharger = new TypeCCharger() {
@Override
public void chargeWithTypeC() {
System.out.println("直接使用Type-C接口充电");
}
@Override
public int getPower() {
return 20; // 20W快充
}
};
phone.charge(typeCCharger);
System.out.println("情景2:只有旧的Micro USB充电器,使用适配器");
USBAdapter adapter = new USBAdapter();
adapter.showAdapterInfo();
phone.charge(adapter); // 通过适配器使用旧充电器
// 显示适配器工作方式
System.out.println("=== 适配器工作原理 ===");
System.out.println("手机 -> TypeC接口 -> 适配器 -> Micro USB接口 -> 充电器");
System.out.println("客户端(手机)只认识TypeC接口,不知道Micro USB的存在");
}
}
示例2:对象适配器(通过组合实现,推荐)
java
import java.util.*;
// 1. 目标接口:现代媒体播放器
interface ModernMediaPlayer {
void play(String audioType, String fileName);
void stop();
void pause();
List<String> getSupportedFormats();
}
// 2. 被适配者:老式播放器(只能播放MP3)
class LegacyMP3Player {
public void playMP3(String fileName) {
System.out.println("播放MP3文件: " + fileName);
System.out.println("🎵 正在播放音乐...");
}
public void stopMP3() {
System.out.println("停止播放MP3");
}
public void pauseMP3() {
System.out.println("暂停播放MP3");
}
public String getPlayerInfo() {
return "老式MP3播放器 (仅支持MP3格式)";
}
}
// 3. 另一个被适配者:老式视频播放器
class LegacyVideoPlayer {
public void playAVI(String fileName) {
System.out.println("播放AVI视频: " + fileName);
System.out.println("🎬 正在播放视频...");
}
public void playWMV(String fileName) {
System.out.println("播放WMV视频: " + fileName);
System.out.println("📹 正在播放视频...");
}
public String getInfo() {
return "老式视频播放器 (支持AVI, WMV格式)";
}
}
// 4. 对象适配器:组合多个被适配者
class MediaPlayerAdapter implements ModernMediaPlayer {
private LegacyMP3Player mp3Player;
private LegacyVideoPlayer videoPlayer;
private Map<String, Runnable> playCommands;
public MediaPlayerAdapter() {
this.mp3Player = new LegacyMP3Player();
this.videoPlayer = new LegacyVideoPlayer();
initPlayCommands();
}
private void initPlayCommands() {
playCommands = new HashMap<>();
playCommands.put("mp3", () -> mp3Player.playMP3(""));
playCommands.put("avi", () -> videoPlayer.playAVI(""));
playCommands.put("wmv", () -> videoPlayer.playWMV(""));
}
@Override
public void play(String audioType, String fileName) {
System.out.println("适配器:尝试播放 " + audioType.toUpperCase() + " 文件: " + fileName);
switch (audioType.toLowerCase()) {
case "mp3":
mp3Player.playMP3(fileName);
break;
case "avi":
videoPlayer.playAVI(fileName);
break;
case "wmv":
videoPlayer.playWMV(fileName);
break;
default:
System.out.println("不支持的文件格式: " + audioType);
System.out.println("尝试使用默认播放器...");
// 可以在这里添加更多适配逻辑
if (audioType.endsWith("mp4") || audioType.endsWith("mkv")) {
System.out.println("提示:请安装新版播放器以支持 " + audioType + " 格式");
}
}
}
@Override
public void stop() {
System.out.println("适配器:停止播放");
mp3Player.stopMP3();
// 视频播放器没有stop方法,但适配器提供了统一接口
}
@Override
public void pause() {
System.out.println("适配器:暂停播放");
mp3Player.pauseMP3();
// 视频播放器没有pause方法,但适配器提供了统一接口
}
@Override
public List<String> getSupportedFormats() {
return Arrays.asList("mp3", "avi", "wmv");
}
// 适配器特有的方法
public void showLegacyPlayersInfo() {
System.out.println("\n适配器内部使用的播放器:");
System.out.println("1. " + mp3Player.getPlayerInfo());
System.out.println("2. " + videoPlayer.getInfo());
}
// 增强功能:格式转换
public void convertFormat(String sourceFile, String targetFormat) {
System.out.println("格式转换: " + sourceFile + " -> " + targetFormat);
// 模拟格式转换
if (targetFormat.equals("mp3")) {
System.out.println("转换为MP3成功!");
}
}
}
// 5. 另一个适配器:仅适配MP3播放器
class SimpleMP3Adapter implements ModernMediaPlayer {
private LegacyMP3Player mp3Player;
public SimpleMP3Adapter(LegacyMP3Player mp3Player) {
this.mp3Player = mp3Player;
}
@Override
public void play(String audioType, String fileName) {
if (!audioType.equalsIgnoreCase("mp3")) {
System.out.println("错误:此适配器仅支持MP3格式");
return;
}
mp3Player.playMP3(fileName);
}
@Override
public void stop() {
mp3Player.stopMP3();
}
@Override
public void pause() {
mp3Player.pauseMP3();
}
@Override
public List<String> getSupportedFormats() {
return Arrays.asList("mp3");
}
}
// 6. 客户端:现代媒体播放应用
class MediaPlayerApp {
private ModernMediaPlayer player;
public void setPlayer(ModernMediaPlayer player) {
this.player = player;
System.out.println("设置播放器: " + player.getClass().getSimpleName());
System.out.println("支持的格式: " + player.getSupportedFormats());
}
public void playFile(String fileName) {
if (player == null) {
System.out.println("请先设置播放器");
return;
}
String extension = getFileExtension(fileName);
System.out.println("\n播放文件: " + fileName);
player.play(extension, fileName);
}
public void playMultipleFiles(List<String> files) {
System.out.println("\n=== 播放多个文件 ===");
for (String file : files) {
playFile(file);
}
}
private String getFileExtension(String fileName) {
int dotIndex = fileName.lastIndexOf('.');
return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
}
}
// 主程序
public class ObjectAdapterDemo {
public static void main(String[] args) {
System.out.println("=== 适配器模式示例:对象适配器(媒体播放器) ===\n");
// 创建客户端应用
MediaPlayerApp app = new MediaPlayerApp();
System.out.println("情景1:使用多功能适配器(支持多种格式)");
MediaPlayerAdapter multiAdapter = new MediaPlayerAdapter();
app.setPlayer(multiAdapter);
// 播放多种格式文件
List<String> files = Arrays.asList(
"song.mp3",
"movie.avi",
"video.wmv",
"new_video.mp4" // 不支持格式
);
app.playMultipleFiles(files);
// 显示适配器内部信息
multiAdapter.showLegacyPlayersInfo();
System.out.println("\n" + "=".repeat(50) + "\n");
System.out.println("情景2:使用简单适配器(仅MP3)");
LegacyMP3Player oldPlayer = new LegacyMP3Player();
SimpleMP3Adapter simpleAdapter = new SimpleMP3Adapter(oldPlayer);
app.setPlayer(simpleAdapter);
app.playFile("music.mp3");
app.playFile("video.mp4"); // 这个会失败
System.out.println("\n" + "=".repeat(50) + "\n");
System.out.println("情景3:直接使用现代播放器(不需要适配器)");
ModernMediaPlayer modernPlayer = new ModernMediaPlayer() {
@Override
public void play(String audioType, String fileName) {
System.out.println("现代播放器直接播放: " + fileName);
}
@Override
public void stop() {
System.out.println("现代播放器停止");
}
@Override
public void pause() {
System.out.println("现代播放器暂停");
}
@Override
public List<String> getSupportedFormats() {
return Arrays.asList("mp3", "mp4", "avi", "mkv", "flac");
}
};
app.setPlayer(modernPlayer);
app.playFile("high_quality.flac");
// 适配器模式的优势展示
System.out.println("\n" + "=".repeat(50));
System.out.println("=== 适配器模式的优势 ===");
System.out.println("1. 重用现有代码: 无需重写LegacyMP3Player和LegacyVideoPlayer");
System.out.println("2. 接口统一: 所有播放器都有相同的ModernMediaPlayer接口");
System.out.println("3. 灵活扩展: 可以轻松添加对新格式的支持");
System.out.println("4. 符合开闭原则: 添加新适配器不需要修改现有代码");
}
}
示例3:接口适配器(缺省适配器)
java
import java.util.*;
// 1. 复杂接口:包含很多方法
interface FileOperations {
void createFile(String path);
void deleteFile(String path);
void renameFile(String oldPath, String newPath);
void copyFile(String source, String destination);
void moveFile(String source, String destination);
void readFile(String path);
void writeFile(String path, String content);
void appendToFile(String path, String content);
void setPermissions(String path, String permissions);
void getFileInfo(String path);
List<String> listFiles(String directory);
boolean fileExists(String path);
long getFileSize(String path);
Date getLastModified(String path);
}
// 2. 缺省适配器(抽象类):提供所有方法的默认实现(空实现或抛异常)
abstract class FileOperationsAdapter implements FileOperations {
@Override
public void createFile(String path) {
throw new UnsupportedOperationException("createFile not implemented");
}
@Override
public void deleteFile(String path) {
throw new UnsupportedOperationException("deleteFile not implemented");
}
@Override
public void renameFile(String oldPath, String newPath) {
throw new UnsupportedOperationException("renameFile not implemented");
}
@Override
public void copyFile(String source, String destination) {
throw new UnsupportedOperationException("copyFile not implemented");
}
@Override
public void moveFile(String source, String destination) {
throw new UnsupportedOperationException("moveFile not implemented");
}
@Override
public void readFile(String path) {
throw new UnsupportedOperationException("readFile not implemented");
}
@Override
public void writeFile(String path, String content) {
throw new UnsupportedOperationException("writeFile not implemented");
}
@Override
public void appendToFile(String path, String content) {
throw new UnsupportedOperationException("appendToFile not implemented");
}
@Override
public void setPermissions(String path, String permissions) {
throw new UnsupportedOperationException("setPermissions not implemented");
}
@Override
public void getFileInfo(String path) {
throw new UnsupportedOperationException("getFileInfo not implemented");
}
@Override
public List<String> listFiles(String directory) {
throw new UnsupportedOperationException("listFiles not implemented");
}
@Override
public boolean fileExists(String path) {
throw new UnsupportedOperationException("fileExists not implemented");
}
@Override
public long getFileSize(String path) {
throw new UnsupportedOperationException("getFileSize not implemented");
}
@Override
public Date getLastModified(String path) {
throw new UnsupportedOperationException("getLastModified not implemented");
}
}
// 3. 具体实现:只实现需要的几个方法
class SimpleFileReader extends FileOperationsAdapter {
@Override
public void readFile(String path) {
System.out.println("读取文件: " + path);
// 实际读取文件的逻辑
}
@Override
public boolean fileExists(String path) {
System.out.println("检查文件是否存在: " + path);
return true; // 简化实现
}
@Override
public void getFileInfo(String path) {
System.out.println("获取文件信息: " + path);
System.out.println("大小: 1024 bytes");
System.out.println("类型: txt");
}
}
// 4. 另一个具体实现:实现更多方法
class FileManager extends FileOperationsAdapter {
private Map<String, String> fileSystem = new HashMap<>();
@Override
public void createFile(String path) {
System.out.println("创建文件: " + path);
fileSystem.put(path, "");
}
@Override
public void deleteFile(String path) {
System.out.println("删除文件: " + path);
fileSystem.remove(path);
}
@Override
public void writeFile(String path, String content) {
System.out.println("写入文件: " + path);
fileSystem.put(path, content);
}
@Override
public void readFile(String path) {
System.out.println("读取文件: " + path);
String content = fileSystem.get(path);
if (content != null) {
System.out.println("内容: " + content);
} else {
System.out.println("文件不存在");
}
}
@Override
public List<String> listFiles(String directory) {
System.out.println("列出目录: " + directory);
List<String> files = new ArrayList<>();
for (String path : fileSystem.keySet()) {
if (path.startsWith(directory)) {
files.add(path);
}
}
return files;
}
@Override
public boolean fileExists(String path) {
return fileSystem.containsKey(path);
}
}
// 5. 客户端
class ClientApp {
public void useFileOperations(FileOperations fileOps) {
System.out.println("\n使用文件操作接口:");
try {
fileOps.createFile("/test.txt");
} catch (UnsupportedOperationException e) {
System.out.println("功能不支持: createFile");
}
try {
fileOps.readFile("/test.txt");
} catch (UnsupportedOperationException e) {
System.out.println("功能不支持: readFile");
}
try {
fileOps.deleteFile("/test.txt");
} catch (UnsupportedOperationException e) {
System.out.println("功能不支持: deleteFile");
}
try {
List<String> files = fileOps.listFiles("/");
System.out.println("文件列表: " + files);
} catch (UnsupportedOperationException e) {
System.out.println("功能不支持: listFiles");
}
}
}
// 主程序
public class InterfaceAdapterDemo {
public static void main(String[] args) {
System.out.println("=== 适配器模式示例:接口适配器(缺省适配器) ===\n");
ClientApp app = new ClientApp();
System.out.println("情景1:使用简单文件阅读器");
SimpleFileReader reader = new SimpleFileReader();
app.useFileOperations(reader);
System.out.println("\n" + "=".repeat(50) + "\n");
System.out.println("情景2:使用完整文件管理器");
FileManager manager = new FileManager();
app.useFileOperations(manager);
System.out.println("\n" + "=".repeat(50) + "\n");
System.out.println("=== 接口适配器的优势 ===");
System.out.println("1. 避免实现不需要的方法");
System.out.println("2. 简化接口实现");
System.out.println("3. 提供默认实现,子类只需覆盖需要的方法");
System.out.println("4. 常用于事件监听器等有很多方法的接口");
// 实际应用示例:Java中的WindowAdapter
System.out.println("\n=== Java中的实际应用 ===");
System.out.println("java.awt.event.WindowAdapter 就是一个接口适配器");
System.out.println("它实现了WindowListener接口的所有方法(空实现)");
System.out.println("用户只需要继承WindowAdapter并覆盖需要的方法");
// 模拟WindowAdapter
System.out.println("\n模拟代码:");
System.out.println("// 原本需要实现7个方法");
System.out.println("class MyWindowListener implements WindowListener {");
System.out.println(" // 必须实现所有7个方法,即使大部分是空的");
System.out.println("}");
System.out.println("\n// 使用适配器后");
System.out.println("class MyWindowListener extends WindowAdapter {");
System.out.println(" // 只需要覆盖需要的方法");
System.out.println(" public void windowClosing(WindowEvent e) {");
System.out.println(" System.exit(0);");
System.out.println(" }");
System.out.println("}");
}
}
示例4:实际应用 - 第三方支付接口适配
java
import java.text.SimpleDateFormat;
import java.util.Date;
// 1. 目标接口:公司统一的支付接口
interface CompanyPaymentGateway {
boolean makePayment(double amount, String currency, String customerId);
String getPaymentStatus(String transactionId);
boolean refund(String transactionId, double amount);
String generateInvoice(double amount, String customerEmail);
}
// 2. 被适配者1:支付宝接口(第三方)
class AlipayService {
// 支付宝的接口与我们的接口不同
public boolean alipayPayment(double rmbAmount, String alipayAccount) {
System.out.println("调用支付宝接口: 向 " + alipayAccount + " 支付 " + rmbAmount + " 元");
// 实际调用支付宝API
return Math.random() > 0.1; // 90%成功率
}
public String checkAlipayOrder(String alipayOrderId) {
System.out.println("查询支付宝订单: " + alipayOrderId);
return "SUCCESS";
}
public boolean alipayRefund(String alipayOrderId, double rmbAmount) {
System.out.println("支付宝退款: 订单 " + alipayOrderId + " 退款 " + rmbAmount + " 元");
return true;
}
public String getAlipayServiceInfo() {
return "支付宝支付服务";
}
}
// 3. 被适配者2:PayPal接口(第三方,国外)
class PayPalService {
// PayPal的接口也不同
public boolean processPayPalPayment(double usdAmount, String paypalEmail) {
System.out.println("调用PayPal接口: 向 " + paypalEmail + " 支付 $" + usdAmount);
// 实际调用PayPal API
return Math.random() > 0.05; // 95%成功率
}
public String getPayPalTransactionStatus(String paypalTransactionId) {
System.out.println("查询PayPal交易状态: " + paypalTransactionId);
return "COMPLETED";
}
public boolean issuePayPalRefund(String paypalTransactionId, double usdAmount) {
System.out.println("PayPal退款: 交易 " + paypalTransactionId + " 退款 $" + usdAmount);
return true;
}
public String createPayPalInvoice(double amount, String email) {
return "PayPal Invoice #" + System.currentTimeMillis() + " for $" + amount + " to " + email;
}
}
// 4. 被适配者3:银行转账接口
class BankTransferService {
public boolean bankTransfer(double amount, String currency, String bankAccount, String beneficiary) {
System.out.println("银行转账: 向 " + beneficiary + " 的账户 " + bankAccount + " 转账 " + amount + " " + currency);
// 实际调用银行API
return Math.random() > 0.2; // 80%成功率
}
public String checkBankTransfer(String referenceNumber) {
System.out.println("查询银行转账状态: " + referenceNumber);
return "PROCESSED";
}
}
// 5. 适配器1:支付宝适配器
class AlipayAdapter implements CompanyPaymentGateway {
private AlipayService alipayService;
private SimpleDateFormat dateFormat;
public AlipayAdapter() {
this.alipayService = new AlipayService();
this.dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
}
@Override
public boolean makePayment(double amount, String currency, String customerId) {
System.out.println("支付宝适配器: 开始处理支付");
// 货币转换(假设业务逻辑)
double rmbAmount = convertCurrency(amount, currency, "CNY");
// 生成支付宝订单ID
String alipayOrderId = "ALIPAY" + dateFormat.format(new Date());
// 调用支付宝服务
boolean success = alipayService.alipayPayment(rmbAmount, customerId);
if (success) {
System.out.println("支付宝支付成功,订单号: " + alipayOrderId);
}
return success;
}
@Override
public String getPaymentStatus(String transactionId) {
System.out.println("支付宝适配器: 查询支付状态");
// 转换交易ID格式(如果需要)
String alipayOrderId = convertTransactionId(transactionId);
return alipayService.checkAlipayOrder(alipayOrderId);
}
@Override
public boolean refund(String transactionId, double amount) {
System.out.println("支付宝适配器: 处理退款");
String alipayOrderId = convertTransactionId(transactionId);
double rmbAmount = convertCurrency(amount, "USD", "CNY"); // 假设原始是USD
return alipayService.alipayRefund(alipayOrderId, rmbAmount);
}
@Override
public String generateInvoice(double amount, String customerEmail) {
System.out.println("支付宝适配器: 生成发票");
return "支付宝电子发票 - 金额: " + amount + " - 客户: " + customerEmail;
}
private double convertCurrency(double amount, String fromCurrency, String toCurrency) {
// 简化汇率转换
if (fromCurrency.equals("USD") && toCurrency.equals("CNY")) {
return amount * 7.0;
} else if (fromCurrency.equals("CNY") && toCurrency.equals("USD")) {
return amount / 7.0;
}
return amount; // 其他情况不转换
}
private String convertTransactionId(String transactionId) {
// 转换交易ID格式
if (transactionId.startsWith("COMPANY")) {
return "ALIPAY" + transactionId.substring(7);
}
return transactionId;
}
}
// 6. 适配器2:PayPal适配器
class PayPalAdapter implements CompanyPaymentGateway {
private PayPalService paypalService;
public PayPalAdapter() {
this.paypalService = new PayPalService();
}
@Override
public boolean makePayment(double amount, String currency, String customerId) {
System.out.println("PayPal适配器: 开始处理支付");
// 货币转换
double usdAmount = convertCurrency(amount, currency, "USD");
// 调用PayPal服务
boolean success = paypalService.processPayPalPayment(usdAmount, customerId);
if (success) {
System.out.println("PayPal支付成功");
}
return success;
}
@Override
public String getPaymentStatus(String transactionId) {
System.out.println("PayPal适配器: 查询支付状态");
return paypalService.getPayPalTransactionStatus(transactionId);
}
@Override
public boolean refund(String transactionId, double amount) {
System.out.println("PayPal适配器: 处理退款");
double usdAmount = convertCurrency(amount, "CNY", "USD"); // 假设原始是CNY
return paypalService.issuePayPalRefund(transactionId, usdAmount);
}
@Override
public String generateInvoice(double amount, String customerEmail) {
System.out.println("PayPal适配器: 生成发票");
return paypalService.createPayPalInvoice(amount, customerEmail);
}
private double convertCurrency(double amount, String fromCurrency, String toCurrency) {
// 简化汇率转换
if (fromCurrency.equals("CNY") && toCurrency.equals("USD")) {
return amount / 7.0;
} else if (fromCurrency.equals("USD") && toCurrency.equals("CNY")) {
return amount * 7.0;
}
return amount;
}
}
// 7. 适配器工厂
class PaymentAdapterFactory {
public static CompanyPaymentGateway getPaymentGateway(String gatewayType) {
switch (gatewayType.toLowerCase()) {
case "alipay":
return new AlipayAdapter();
case "paypal":
return new PayPalAdapter();
default:
throw new IllegalArgumentException("不支持的支付网关: " + gatewayType);
}
}
}
// 8. 客户端:电商系统
class ECommerceSystem {
public void processOrder(double amount, String currency, String customerEmail,
String paymentMethod, String customerId) {
System.out.println("\n=== 处理订单 ===");
System.out.println("金额: " + amount + " " + currency);
System.out.println("客户: " + customerEmail);
System.out.println("支付方式: " + paymentMethod);
try {
// 通过工厂获取适配器
CompanyPaymentGateway gateway = PaymentAdapterFactory.getPaymentGateway(paymentMethod);
// 使用统一的接口进行支付
boolean paymentSuccess = gateway.makePayment(amount, currency, customerId);
if (paymentSuccess) {
System.out.println("✅ 支付成功!");
// 生成发票
String invoice = gateway.generateInvoice(amount, customerEmail);
System.out.println("发票: " + invoice);
// 发货等后续操作
shipOrder(customerEmail);
} else {
System.out.println("❌ 支付失败,请重试");
}
} catch (Exception e) {
System.out.println("支付处理错误: " + e.getMessage());
}
}
private void shipOrder(String customerEmail) {
System.out.println("📦 订单已发货给: " + customerEmail);
}
public void refundOrder(String transactionId, double amount, String paymentMethod) {
System.out.println("\n=== 处理退款 ===");
System.out.println("交易ID: " + transactionId);
System.out.println("退款金额: " + amount);
try {
CompanyPaymentGateway gateway = PaymentAdapterFactory.getPaymentGateway(paymentMethod);
boolean refundSuccess = gateway.refund(transactionId, amount);
if (refundSuccess) {
System.out.println("✅ 退款成功");
} else {
System.out.println("❌ 退款失败");
}
} catch (Exception e) {
System.out.println("退款处理错误: " + e.getMessage());
}
}
}
// 主程序
public class RealWorldAdapterDemo {
public static void main(String[] args) {
System.out.println("=== 适配器模式示例:实际应用(第三方支付集成) ===\n");
ECommerceSystem system = new ECommerceSystem();
System.out.println("情景1:中国客户使用支付宝");
system.processOrder(100.0, "CNY", "zhangsan@example.com", "alipay", "zhangsan123");
System.out.println("\n" + "=".repeat(60));
System.out.println("情景2:美国客户使用PayPal");
system.processOrder(50.0, "USD", "john@example.com", "paypal", "john456");
System.out.println("\n" + "=".repeat(60));
System.out.println("情景3:处理退款");
system.refundOrder("TXN123456", 30.0, "alipay");
System.out.println("\n" + "=".repeat(60));
System.out.println("=== 适配器模式带来的好处 ===");
System.out.println("1. 统一接口: 所有支付方式都使用CompanyPaymentGateway接口");
System.out.println("2. 易于扩展: 添加新支付方式只需新增适配器,不修改现有代码");
System.out.println("3. 隔离变化: 第三方API变化时,只需修改适配器,不影响业务逻辑");
System.out.println("4. 代码复用: 多个系统可以使用相同的适配器");
System.out.println("\n=== 系统架构图 ===");
System.out.println("电商系统 -> CompanyPaymentGateway接口 -> 适配器 -> 第三方支付API");
System.out.println("电商系统不知道支付宝、PayPal的具体实现,只认识统一接口");
// 演示添加新的支付方式
System.out.println("\n" + "=".repeat(60));
System.out.println("演示:添加新的支付方式(微信支付)");
// 假设新增微信支付
class WeChatPayService {
public boolean wechatPayment(double amount, String openId) {
System.out.println("微信支付: 向OpenID " + openId + " 收取 " + amount + " 元");
return true;
}
}
// 创建微信支付适配器
class WeChatPayAdapter implements CompanyPaymentGateway {
private WeChatPayService wechatPay = new WeChatPayService();
@Override
public boolean makePayment(double amount, String currency, String customerId) {
System.out.println("微信支付适配器: 处理支付");
double cnyAmount = amount; // 简化,假设已经是CNY
return wechatPay.wechatPayment(cnyAmount, customerId);
}
@Override
public String getPaymentStatus(String transactionId) {
return "SUCCESS";
}
@Override
public boolean refund(String transactionId, double amount) {
System.out.println("微信支付退款");
return true;
}
@Override
public String generateInvoice(double amount, String customerEmail) {
return "微信支付发票";
}
}
// 扩展工厂
System.out.println("\n只需:");
System.out.println("1. 创建WeChatPayAdapter实现CompanyPaymentGateway");
System.out.println("2. 在PaymentAdapterFactory中添加wechat分支");
System.out.println("3. 电商系统无需任何修改即可支持微信支付");
}
}
适配器模式的优缺点
优点:
- 提高类的复用性:可以重用现有的类,无需修改
- 提高类的透明度:客户端通过目标接口与被适配者交互
- 灵活性好:可以随时增加新的适配器
- 符合开闭原则:可以在不修改现有代码的情况下引入新适配器
缺点:
- 增加复杂性:增加了很多适配器类,使系统变得复杂
- 可能降低性能:由于多了一层调用,可能会稍微降低性能
- 过度使用问题:如果系统设计合理,可能不需要适配器
使用场景
- 系统需要复用现有类:但这些类的接口不符合系统需求
- 统一多个类的接口:多个类完成相似功能但接口不同
- 旧系统升级:新系统需要与旧系统兼容
- 第三方库集成:需要将第三方库集成到自己的系统中
- 接口版本兼容:新接口需要向后兼容旧接口
适配器模式 vs 其他模式
| 模式 | 区别 |
|---|---|
| 适配器 vs 装饰器 | 适配器改变接口,装饰器增强功能但不改变接口 |
| 适配器 vs 外观 | 适配器转换一个接口,外观简化多个接口 |
| 适配器 vs 代理 | 适配器改变接口,代理控制访问但不改变接口 |
| 适配器 vs 桥接 | 适配器使不兼容接口兼容,桥接分离抽象和实现 |
Java中的适配器模式
- InputStreamReader/OutputStreamWriter:字节流到字符流的适配器
- Arrays.asList():数组到List的适配器
- Spring的HandlerAdapter:适配不同类型的处理器
- Java GUI中的适配器类:WindowAdapter、MouseAdapter等
适配器模式的核心价值在于:让不兼容的接口能够一起工作,它是系统集成和代码复用的重要工具。通过适配器,我们可以将已有的类集成到新系统中,而无需修改它们的源代码,这大大提高了代码的复用性和系统的可扩展性。
桥接设计模式
简单理解
想象有一个遥控器(抽象)和一台电视(实现)。遥控器可以控制电视,但不希望遥控器只能控制特定型号的电视。桥接模式就像在遥控器和电视之间建立一个桥梁,让它们可以独立变化:可以更换不同的遥控器(比如学习型遥控器、手机APP遥控器),也可以更换不同的电视(索尼、三星、小米),而它们之间都能正常工作。
核心思想
将抽象(做什么)与实现(怎么做)分离,使它们可以独立变化,而不互相影响。
实际场景类比
- 电商系统:支付方式(微信、支付宝、银行卡)和支付类型(即时支付、分期支付)可以独立扩展
- 绘图程序:形状(圆形、方形)和颜色(红色、蓝色)可以自由组合
- 汽车制造:发动机类型(汽油、电动)和车型(SUV、轿车)可以分别开发
代码示例
场景:跨平台消息推送
我们要实现一个消息推送系统,支持不同类型的消息(文本、图片)和不同的推送平台(iOS、Android),并且可以灵活组合。
java
// 1. 实现部分接口:推送平台
interface PushPlatform {
void push(String title, String content);
}
// 2. 具体实现:iOS推送平台
class IOSPlatform implements PushPlatform {
@Override
public void push(String title, String content) {
System.out.println("[iOS推送]");
System.out.println("标题: " + title);
System.out.println("内容: " + content);
System.out.println("使用APNs服务推送...");
}
}
// 3. 具体实现:Android推送平台
class AndroidPlatform implements PushPlatform {
@Override
public void push(String title, String content) {
System.out.println("[Android推送]");
System.out.println("标题: " + title);
System.out.println("内容: " + content);
System.out.println("使用FCM服务推送...");
}
}
// 4. 抽象部分:消息类型
abstract class Message {
protected PushPlatform platform;
public Message(PushPlatform platform) {
this.platform = platform;
}
public abstract void send(String title, String content);
}
// 5. 扩展抽象:文本消息
class TextMessage extends Message {
public TextMessage(PushPlatform platform) {
super(platform);
}
@Override
public void send(String title, String content) {
System.out.println("准备发送文本消息...");
// 可以在发送前做一些文本消息特有的处理
String processedContent = "📝 " + content;
platform.push(title, processedContent);
}
}
// 6. 扩展抽象:图片消息
class ImageMessage extends Message {
public ImageMessage(PushPlatform platform) {
super(platform);
}
@Override
public void send(String title, String content) {
System.out.println("准备发送图片消息...");
// 可以在发送前做一些图片消息特有的处理
String processedContent = "🖼️ [图片消息] " + content;
platform.push(title, processedContent);
}
}
// 7. 客户端使用
public class BridgePatternDemo {
public static void main(String[] args) {
System.out.println("=== 桥接模式示例:消息推送系统 ===\n");
// 创建不同的推送平台
PushPlatform ios = new IOSPlatform();
PushPlatform android = new AndroidPlatform();
// 组合:文本消息 + iOS平台
System.out.println("1. 发送文本消息到iOS设备:");
Message textToIOS = new TextMessage(ios);
textToIOS.send("系统通知", "您有新消息,请查收");
System.out.println("\n---\n");
// 组合:文本消息 + Android平台
System.out.println("2. 发送文本消息到Android设备:");
Message textToAndroid = new TextMessage(android);
textToAndroid.send("系统通知", "您有新消息,请查收");
System.out.println("\n---\n");
// 组合:图片消息 + iOS平台
System.out.println("3. 发送图片消息到iOS设备:");
Message imageToIOS = new ImageMessage(ios);
imageToIOS.send("图片分享", "查看好友分享的图片");
System.out.println("\n---\n");
// 组合:图片消息 + Android平台
System.out.println("4. 发送图片消息到Android设备:");
Message imageToAndroid = new ImageMessage(android);
imageToAndroid.send("图片分享", "查看好友分享的图片");
System.out.println("\n=== 添加新功能演示 ===");
// 添加新的推送平台(比如Web平台)非常容易
PushPlatform webPlatform = new PushPlatform() {
@Override
public void push(String title, String content) {
System.out.println("[Web推送] 使用WebSocket推送: " + title + " - " + content);
}
};
// 添加新的消息类型(比如语音消息)也非常容易
Message voiceMessage = new Message(webPlatform) {
@Override
public void send(String title, String content) {
System.out.println("发送语音消息...");
platform.push(title, "🎵 " + content);
}
};
System.out.println("\n5. 发送语音消息到Web平台:");
voiceMessage.send("语音消息", "您收到一条语音留言");
}
}
更简洁的示例:形状和颜色
java
// 实现部分:颜色
interface Color {
String fill();
}
class Red implements Color {
public String fill() { return "红色"; }
}
class Blue implements Color {
public String fill() { return "蓝色"; }
}
// 抽象部分:形状
abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
abstract String draw();
}
class Circle extends Shape {
public Circle(Color color) {
super(color);
}
public String draw() {
return "绘制一个" + color.fill() + "的圆形";
}
}
class Square extends Shape {
public Square(Color color) {
super(color);
}
public String draw() {
return "绘制一个" + color.fill() + "的正方形";
}
}
// 使用
public class SimpleBridgeDemo {
public static void main(String[] args) {
Shape redCircle = new Circle(new Red());
Shape blueSquare = new Square(new Blue());
System.out.println(redCircle.draw()); // 绘制一个红色的圆形
System.out.println(blueSquare.draw()); // 绘制一个蓝色的正方形
// 添加新颜色或新形状都非常容易
}
}
桥接模式的优势
- 解耦抽象和实现:可以独立扩展消息类型和推送平台
- 提高扩展性:添加新平台或新消息类型不需要修改现有代码
- 符合开闭原则:对扩展开放,对修改关闭
- 减少子类数量 :如果没有桥接模式,可能需要
iOS文本消息、Android文本消息、iOS图片消息、Android图片消息等多个类
使用场景
- 当一个类有两个独立变化的维度,且这两个维度都需要扩展时
- 避免在多个对象间使用继承导致类爆炸
- 需要在运行时切换不同的实现
桥接模式的关键在于识别出系统中可以独立变化的两个维度,然后通过组合(而不是继承)的方式将它们连接起来。
组合设计模式
通俗解释
想象一下公司组织架构图:公司有多个部门,每个部门有多个小组,每个小组有多名员工。当想计算整个公司的工资总额时,不需要分别计算每个部门、每个小组、每个员工的工资然后相加,而是可以统一对待:无论是公司、部门、小组还是员工,都有统一的"计算工资"方法。这就是组合模式。
核心思想:用树形结构表示"部分-整体"的层次结构,让单个对象(叶子节点)和组合对象(容器节点)能够被一致对待。
现实生活中的例子
- 计算机文件系统:文件夹(容器)和文件(叶子)都有"打开"、"删除"、"重命名"等操作
- 公司组织架构:部门(容器)和员工(叶子)都有"计算成本"的方法
- 菜单系统:菜单(容器)和菜单项(叶子)都有"显示"和"执行"的方法
- 图形编辑器:图形组(容器)和基本图形(叶子)都有"绘制"、"移动"等方法
关键特点
- 统一的接口:所有对象使用同一套接口
- 透明性:客户端不需要区分处理的是叶子节点还是容器节点
- 递归结构:容器可以包含其他容器或叶子
代码示例
示例1:文件系统(经典例子)
java
import java.util.ArrayList;
import java.util.List;
// 1. 组件接口:定义文件和文件夹的共同操作
interface FileSystemComponent {
void showDetails(String indent);
long getSize();
void add(FileSystemComponent component); // 注意:叶子节点也需要实现此方法(可选抛出异常)
void remove(FileSystemComponent component);
}
// 2. 叶子节点:文件
class File implements FileSystemComponent {
private String name;
private long size;
public File(String name, long size) {
this.name = name;
this.size = size;
}
@Override
public void showDetails(String indent) {
System.out.println(indent + "📄 " + name + " (" + size + " bytes)");
}
@Override
public long getSize() {
return size;
}
@Override
public void add(FileSystemComponent component) {
// 文件是叶子节点,不能添加子组件
throw new UnsupportedOperationException("不能向文件添加组件");
}
@Override
public void remove(FileSystemComponent component) {
throw new UnsupportedOperationException("文件没有子组件");
}
}
// 3. 容器节点:文件夹
class Directory implements FileSystemComponent {
private String name;
private List<FileSystemComponent> components = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
@Override
public void showDetails(String indent) {
System.out.println(indent + "📁 " + name + " [目录]");
// 递归显示所有子组件
for (FileSystemComponent component : components) {
component.showDetails(indent + " ");
}
}
@Override
public long getSize() {
long totalSize = 0;
// 递归计算所有子组件的大小
for (FileSystemComponent component : components) {
totalSize += component.getSize();
}
return totalSize;
}
@Override
public void add(FileSystemComponent component) {
components.add(component);
}
@Override
public void remove(FileSystemComponent component) {
components.remove(component);
}
// 可选:添加一些文件夹特有的方法
public int getComponentCount() {
return components.size();
}
}
// 4. 客户端使用
public class CompositePatternDemo {
public static void main(String[] args) {
System.out.println("=== 组合模式示例:文件系统 ===\n");
// 创建文件(叶子节点)
File file1 = new File("简历.pdf", 2048);
File file2 = new File("报告.docx", 4096);
File file3 = new File("照片.jpg", 8192);
File file4 = new File("代码.java", 1024);
File file5 = new File("笔记.txt", 512);
// 创建文件夹(容器节点)
Directory documents = new Directory("文档");
Directory pictures = new Directory("图片");
Directory work = new Directory("工作");
Directory root = new Directory("我的电脑");
// 构建树形结构
documents.add(file1);
documents.add(file2);
documents.add(file5);
pictures.add(file3);
work.add(file4);
work.add(documents); // 文件夹可以包含文件夹
root.add(work);
root.add(pictures);
// 统一操作:显示所有内容
System.out.println("文件系统结构:");
root.showDetails("");
System.out.println("\n--- 统计信息 ---");
System.out.println("根目录总大小: " + root.getSize() + " bytes");
System.out.println("文档文件夹大小: " + documents.getSize() + " bytes");
System.out.println("工作文件夹组件数: " + work.getComponentCount());
// 演示透明性:统一处理文件和文件夹
System.out.println("\n--- 统一处理示例 ---");
FileSystemComponent[] components = {file1, documents, file3};
for (FileSystemComponent component : components) {
System.out.print("组件: ");
component.showDetails("");
System.out.println("大小: " + component.getSize() + " bytes");
System.out.println();
}
// 尝试向文件添加组件(会抛出异常)
try {
file1.add(file2);
} catch (UnsupportedOperationException e) {
System.out.println("预期异常: " + e.getMessage());
}
}
}
示例2:电商产品目录(更实用的例子)
java
import java.util.ArrayList;
import java.util.List;
// 1. 组件接口:产品项
interface ProductComponent {
String getName();
double getPrice();
void display(String indent);
default void add(ProductComponent component) {
throw new UnsupportedOperationException("不支持添加操作");
}
default void remove(ProductComponent component) {
throw new UnsupportedOperationException("不支持移除操作");
}
}
// 2. 叶子节点:单个产品
class Product implements ProductComponent {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String getName() {
return name;
}
@Override
public double getPrice() {
return price;
}
@Override
public void display(String indent) {
System.out.println(indent + "🛒 " + name + " - ¥" + price);
}
}
// 3. 叶子节点:打折产品(特殊类型的叶子)
class DiscountedProduct implements ProductComponent {
private Product product;
private double discount; // 折扣率,如0.8表示8折
public DiscountedProduct(String name, double price, double discount) {
this.product = new Product(name, price);
this.discount = discount;
}
@Override
public String getName() {
return product.getName() + " (打折)";
}
@Override
public double getPrice() {
return product.getPrice() * discount;
}
@Override
public void display(String indent) {
System.out.println(indent + "🎁 " + product.getName() +
" - 原价:¥" + product.getPrice() +
", 折后价:¥" + getPrice());
}
}
// 4. 容器节点:产品包
class ProductBundle implements ProductComponent {
private String name;
private List<ProductComponent> components = new ArrayList<>();
private double bundleDiscount; // 套装折扣
public ProductBundle(String name, double bundleDiscount) {
this.name = name;
this.bundleDiscount = bundleDiscount;
}
@Override
public String getName() {
return name;
}
@Override
public double getPrice() {
double total = 0;
for (ProductComponent component : components) {
total += component.getPrice();
}
return total * bundleDiscount;
}
@Override
public void display(String indent) {
System.out.println(indent + "📦 " + name + " - 套装价: ¥" + getPrice());
for (ProductComponent component : components) {
component.display(indent + " ");
}
}
@Override
public void add(ProductComponent component) {
components.add(component);
}
@Override
public void remove(ProductComponent component) {
components.remove(component);
}
// 计算套装原价(无折扣)
public double getOriginalPrice() {
double total = 0;
for (ProductComponent component : components) {
total += component.getPrice();
}
return total;
}
}
// 5. 客户端:购物车
public class ShoppingCartDemo {
public static void main(String[] args) {
System.out.println("=== 组合模式示例:电商产品目录 ===\n");
// 创建单个产品
ProductComponent laptop = new Product("笔记本电脑", 6999);
ProductComponent mouse = new Product("无线鼠标", 199);
ProductComponent keyboard = new DiscountedProduct("机械键盘", 499, 0.8); // 8折
ProductComponent headphone = new Product("蓝牙耳机", 399);
// 创建产品套装
ProductBundle officeBundle = new ProductBundle("办公套装", 0.9); // 9折
officeBundle.add(laptop);
officeBundle.add(mouse);
officeBundle.add(keyboard);
ProductBundle gamingBundle = new ProductBundle("游戏套装", 0.85); // 85折
gamingBundle.add(laptop);
gamingBundle.add(new Product("游戏鼠标", 299));
gamingBundle.add(headphone);
// 创建顶级套餐(套装中可以包含套装)
ProductBundle ultimateBundle = new ProductBundle("终极套餐", 0.8);
ultimateBundle.add(officeBundle);
ultimateBundle.add(gamingBundle);
ultimateBundle.add(new Product("显示器", 1999));
// 显示所有产品
System.out.println("=== 产品目录 ===");
ProductComponent[] products = {
laptop, mouse, keyboard, headphone,
officeBundle, gamingBundle, ultimateBundle
};
for (ProductComponent product : products) {
product.display("");
System.out.println();
}
// 计算购物车总价
System.out.println("=== 购物车结算 ===");
List<ProductComponent> cart = new ArrayList<>();
cart.add(laptop);
cart.add(mouse);
cart.add(officeBundle);
double total = 0;
for (ProductComponent item : cart) {
System.out.println(item.getName() + ": ¥" + item.getPrice());
total += item.getPrice();
}
System.out.println("-------------------");
System.out.println("购物车总价: ¥" + total);
// 统一操作演示
System.out.println("\n=== 统一操作演示 ===");
System.out.println("所有产品名称和价格:");
for (ProductComponent product : products) {
System.out.println("- " + product.getName() + ": ¥" + product.getPrice());
}
}
}
示例3:更简化的版本(透明方式 vs 安全方式)
java
import java.util.ArrayList;
import java.util.List;
/**
* 组合模式的两种实现方式:
* 1. 透明方式(推荐):在Component接口中声明所有方法(包括管理子组件的方法)
* 2. 安全方式:只在Composite中声明管理子组件的方法,Leaf不需要实现这些方法
*/
// 透明方式实现
abstract class UIComponent {
protected String name;
public UIComponent(String name) {
this.name = name;
}
// 所有组件都有的操作
public abstract void render();
public abstract void add(UIComponent component);
public abstract void remove(UIComponent component);
public abstract UIComponent getChild(int index);
}
// 叶子节点:按钮
class Button extends UIComponent {
public Button(String name) {
super(name);
}
@Override
public void render() {
System.out.println("渲染按钮: " + name);
}
@Override
public void add(UIComponent component) {
throw new UnsupportedOperationException("按钮不能添加子组件");
}
@Override
public void remove(UIComponent component) {
throw new UnsupportedOperationException("按钮没有子组件");
}
@Override
public UIComponent getChild(int index) {
throw new UnsupportedOperationException("按钮没有子组件");
}
}
// 容器节点:面板
class Panel extends UIComponent {
private List<UIComponent> children = new ArrayList<>();
public Panel(String name) {
super(name);
}
@Override
public void render() {
System.out.println("渲染面板: " + name);
for (UIComponent child : children) {
child.render();
}
}
@Override
public void add(UIComponent component) {
children.add(component);
}
@Override
public void remove(UIComponent component) {
children.remove(component);
}
@Override
public UIComponent getChild(int index) {
return children.get(index);
}
public int getChildCount() {
return children.size();
}
}
public class CompositeSimpleDemo {
public static void main(String[] args) {
System.out.println("=== 组合模式简单示例:UI组件 ===\n");
// 创建UI组件
UIComponent button1 = new Button("确定");
UIComponent button2 = new Button("取消");
UIComponent textField = new Button("输入框"); // 简化,实际应该是不同类
// 创建容器
UIComponent panel1 = new Panel("主面板");
UIComponent panel2 = new Panel("按钮面板");
// 构建UI树
panel2.add(button1);
panel2.add(button2);
panel1.add(textField);
panel1.add(panel2);
// 统一渲染
System.out.println("渲染整个UI:");
panel1.render();
// 统一操作
System.out.println("\n--- 统一操作演示 ---");
UIComponent[] components = {button1, panel1, panel2};
for (UIComponent component : components) {
System.out.println("处理组件: " + component.name);
component.render();
System.out.println();
}
}
}
组合模式的优缺点
优点:
- 简化客户端代码:客户端可以一致地处理单个对象和组合对象
- 易于添加新组件类型:新增叶子或容器都很容易,符合开闭原则
- 可以方便地构建复杂的树形结构
- 提供了清晰的分层结构
缺点:
- 设计较复杂:需要仔细设计接口,确保所有组件都支持所有操作
- 类型安全问题:在透明方式中,叶子节点需要实现一些无意义的方法(如add/remove)
- 难以限制容器中的组件类型:如果不加约束,容器可能包含不合适的组件
使用场景
- 需要表示部分-整体层次结构:如文件系统、组织架构、UI组件树
- 希望用户忽略组合对象与单个对象的不同:用户可以一致地使用它们
- 需要遍历整个结构或部分结构:如计算总价、搜索特定项
组合模式 vs 其他模式
- 与装饰器模式:装饰器模式增强了单个对象的功能,而组合模式处理对象集合
- 与迭代器模式:组合模式常与迭代器模式结合使用来遍历组合结构
- 与访问者模式:访问者模式可以对组合结构中的元素执行操作
组合模式的关键在于一致性:让客户端不用关心处理的是单个对象还是组合对象,从而简化了复杂树形结构的操作。
装饰器设计模式
通俗解释
想象一下去买一杯基础咖啡(原对象),然后可以:
- 加牛奶(装饰器1)
- 加糖(装饰器2)
- 加奶油(装饰器3)
- 加巧克力(装饰器4)
每个添加物都会增强 或改变 咖啡的口味和价格,但核心还是那杯咖啡。装饰器模式就像这些"添加物",它们可以动态地给对象添加新功能,而不改变对象本身的结构。
核心思想:像洋葱一样一层层包裹对象,每一层都添加新功能,但保持接口一致。
关键特点
- 动态添加功能:运行时添加,而不是编译时
- 符合开闭原则:对扩展开放,对修改关闭
- 组合优于继承:避免了通过继承导致的类爆炸
- 保持接口一致:装饰后的对象和原对象使用相同接口
现实生活例子
- Java I/O流 :
BufferedReader(FileReader)就是装饰器 - 咖啡订单系统:基础饮料 + 各种配料
- 游戏角色装备:基础角色 + 武器/防具/饰品
- Web开发中间件:HTTP请求处理链
- GUI组件:带滚动条、边框的文本框
代码示例
示例1:咖啡订单系统(经典例子)
java
// 1. 组件接口:饮料
interface Beverage {
String getDescription();
double cost();
}
// 2. 具体组件:基础饮料
class Espresso implements Beverage {
@Override
public String getDescription() {
return "浓缩咖啡";
}
@Override
public double cost() {
return 25.0;
}
}
class HouseBlend implements Beverage {
@Override
public String getDescription() {
return "家常混合咖啡";
}
@Override
public double cost() {
return 20.0;
}
}
class Decaf implements Beverage {
@Override
public String getDescription() {
return "低咖啡因咖啡";
}
@Override
public double cost() {
return 22.0;
}
}
// 3. 装饰器抽象类
abstract class CondimentDecorator implements Beverage {
protected Beverage beverage; // 被装饰的对象
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public abstract String getDescription();
}
// 4. 具体装饰器:各种配料
class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ",加牛奶";
}
@Override
public double cost() {
return beverage.cost() + 5.0;
}
}
class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ",加摩卡";
}
@Override
public double cost() {
return beverage.cost() + 7.0;
}
}
class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ",加奶油";
}
@Override
public double cost() {
return beverage.cost() + 4.0;
}
}
class Soy extends CondimentDecorator {
public Soy(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ",加豆浆";
}
@Override
public double cost() {
return beverage.cost() + 6.0;
}
}
// 5. 客户端使用
public class DecoratorPatternDemo {
public static void main(String[] args) {
System.out.println("=== 装饰器模式示例:咖啡订单系统 ===\n");
// 1. 单纯一杯浓缩咖啡
Beverage espresso = new Espresso();
System.out.println("订单1: " + espresso.getDescription());
System.out.println("价格: ¥" + espresso.cost());
System.out.println("\n---\n");
// 2. 浓缩咖啡 + 双份摩卡 + 奶油
Beverage beverage2 = new Espresso();
beverage2 = new Mocha(beverage2); // 第一次装饰
beverage2 = new Mocha(beverage2); // 第二次装饰
beverage2 = new Whip(beverage2); // 第三次装饰
System.out.println("订单2: " + beverage2.getDescription());
System.out.println("价格: ¥" + beverage2.cost());
System.out.println("\n---\n");
// 3. 低咖啡因咖啡 + 豆浆 + 牛奶 + 奶油
Beverage beverage3 = new Decaf();
beverage3 = new Soy(beverage3);
beverage3 = new Milk(beverage3);
beverage3 = new Whip(beverage3);
System.out.println("订单3: " + beverage3.getDescription());
System.out.println("价格: ¥" + beverage3.cost());
System.out.println("\n---\n");
// 4. 家常混合咖啡 + 所有配料!
Beverage beverage4 = new HouseBlend();
beverage4 = new Milk(beverage4);
beverage4 = new Mocha(beverage4);
beverage4 = new Soy(beverage4);
beverage4 = new Whip(beverage4);
System.out.println("订单4: " + beverage4.getDescription());
System.out.println("价格: ¥" + beverage4.cost());
// 演示动态添加功能
System.out.println("\n=== 动态添加功能演示 ===");
Beverage simpleCoffee = new HouseBlend();
System.out.println("初始: " + simpleCoffee.getDescription() + " - ¥" + simpleCoffee.cost());
// 顾客想加牛奶
simpleCoffee = new Milk(simpleCoffee);
System.out.println("加牛奶后: " + simpleCoffee.getDescription() + " - ¥" + simpleCoffee.cost());
// 顾客改变主意,想加摩卡而不是牛奶
// 注意:装饰器是不可逆的,我们需要重新创建
Beverage newCoffee = new HouseBlend();
newCoffee = new Mocha(newCoffee);
System.out.println("改为加摩卡: " + newCoffee.getDescription() + " - ¥" + newCoffee.cost());
}
}
示例2:游戏角色装备系统
java
import java.util.ArrayList;
import java.util.List;
// 1. 组件接口:游戏角色
interface GameCharacter {
String getName();
int getAttack();
int getDefense();
int getHealth();
List<String> getSkills();
void showStatus();
}
// 2. 具体组件:基础角色
class Warrior implements GameCharacter {
@Override
public String getName() {
return "战士";
}
@Override
public int getAttack() {
return 10;
}
@Override
public int getDefense() {
return 5;
}
@Override
public int getHealth() {
return 100;
}
@Override
public List<String> getSkills() {
return new ArrayList<>(List.of("基础攻击"));
}
@Override
public void showStatus() {
System.out.println("=== 角色状态 ===");
System.out.println("职业: " + getName());
System.out.println("攻击力: " + getAttack());
System.out.println("防御力: " + getDefense());
System.out.println("生命值: " + getHealth());
System.out.println("技能: " + String.join(", ", getSkills()));
}
}
class Mage implements GameCharacter {
@Override
public String getName() {
return "法师";
}
@Override
public int getAttack() {
return 15;
}
@Override
public int getDefense() {
return 2;
}
@Override
public int getHealth() {
return 80;
}
@Override
public List<String> getSkills() {
return new ArrayList<>(List.of("火球术"));
}
@Override
public void showStatus() {
System.out.println("=== 角色状态 ===");
System.out.println("职业: " + getName());
System.out.println("攻击力: " + getAttack());
System.out.println("防御力: " + getDefense());
System.out.println("生命值: " + getHealth());
System.out.println("技能: " + String.join(", ", getSkills()));
}
}
// 3. 装饰器抽象类:装备
abstract class EquipmentDecorator implements GameCharacter {
protected GameCharacter character;
public EquipmentDecorator(GameCharacter character) {
this.character = character;
}
@Override
public String getName() {
return character.getName();
}
@Override
public void showStatus() {
character.showStatus();
}
}
// 4. 具体装饰器:各种装备
class Sword extends EquipmentDecorator {
public Sword(GameCharacter character) {
super(character);
}
@Override
public int getAttack() {
return character.getAttack() + 20;
}
@Override
public int getDefense() {
return character.getDefense() + 5;
}
@Override
public int getHealth() {
return character.getHealth();
}
@Override
public List<String> getSkills() {
List<String> skills = new ArrayList<>(character.getSkills());
skills.add("剑术精通");
return skills;
}
@Override
public void showStatus() {
super.showStatus();
System.out.println("装备: 长剑 (+20攻击, +5防御, 获得剑术精通)");
}
}
class Shield extends EquipmentDecorator {
public Shield(GameCharacter character) {
super(character);
}
@Override
public int getAttack() {
return character.getAttack();
}
@Override
public int getDefense() {
return character.getDefense() + 15;
}
@Override
public int getHealth() {
return character.getHealth() + 20;
}
@Override
public List<String> getSkills() {
List<String> skills = new ArrayList<>(character.getSkills());
skills.add("盾牌格挡");
return skills;
}
@Override
public void showStatus() {
super.showStatus();
System.out.println("装备: 盾牌 (+15防御, +20生命, 获得盾牌格挡)");
}
}
class MagicWand extends EquipmentDecorator {
public MagicWand(GameCharacter character) {
super(character);
}
@Override
public int getAttack() {
return character.getAttack() + 25;
}
@Override
public int getDefense() {
return character.getDefense();
}
@Override
public int getHealth() {
return character.getHealth();
}
@Override
public List<String> getSkills() {
List<String> skills = new ArrayList<>(character.getSkills());
skills.add("魔法增强");
skills.add("闪电链");
return skills;
}
@Override
public void showStatus() {
super.showStatus();
System.out.println("装备: 魔法杖 (+25攻击, 获得魔法增强和闪电链)");
}
}
class Armor extends EquipmentDecorator {
public Armor(GameCharacter character) {
super(character);
}
@Override
public int getAttack() {
return character.getAttack();
}
@Override
public int getDefense() {
return character.getDefense() + 25;
}
@Override
public int getHealth() {
return character.getHealth() + 50;
}
@Override
public List<String> getSkills() {
List<String> skills = new ArrayList<>(character.getSkills());
skills.add("伤害减免");
return skills;
}
@Override
public void showStatus() {
super.showStatus();
System.out.println("装备: 盔甲 (+25防御, +50生命, 获得伤害减免)");
}
}
// 5. 客户端:游戏系统
public class GameEquipmentDemo {
public static void main(String[] args) {
System.out.println("=== 装饰器模式示例:游戏装备系统 ===\n");
// 创建基础角色
System.out.println("1. 基础战士:");
GameCharacter warrior = new Warrior();
warrior.showStatus();
System.out.println("\n2. 基础法师:");
GameCharacter mage = new Mage();
mage.showStatus();
System.out.println("\n=== 装备装备 ===\n");
// 给战士装备武器和防具
System.out.println("3. 战士装备长剑:");
GameCharacter warriorWithSword = new Sword(warrior);
warriorWithSword.showStatus();
System.out.println("\n4. 战士装备长剑和盾牌:");
GameCharacter warriorWithSwordAndShield = new Shield(warriorWithSword);
warriorWithSwordAndShield.showStatus();
System.out.println("\n5. 战士全副武装:");
GameCharacter fullWarrior = new Armor(warriorWithSwordAndShield);
fullWarrior.showStatus();
System.out.println("\n=== 法师装备 ===\n");
// 给法师装备
System.out.println("6. 法师装备魔法杖:");
GameCharacter mageWithWand = new MagicWand(mage);
mageWithWand.showStatus();
System.out.println("\n7. 法师装备魔法杖和盔甲:");
GameCharacter mageWithWandAndArmor = new Armor(mageWithWand);
mageWithWandAndArmor.showStatus();
// 动态更换装备演示
System.out.println("\n=== 动态更换装备演示 ===\n");
// 创建新战士
GameCharacter newWarrior = new Warrior();
System.out.println("新战士创建:");
newWarrior.showStatus();
// 先装备盾牌
newWarrior = new Shield(newWarrior);
System.out.println("\n装备盾牌后:");
newWarrior.showStatus();
// 再装备长剑(注意顺序)
newWarrior = new Sword(newWarrior);
System.out.println("\n再装备长剑后:");
newWarrior.showStatus();
// 计算总属性
System.out.println("\n=== 最终属性统计 ===");
System.out.println("战士最终属性:");
System.out.println("攻击力: " + fullWarrior.getAttack());
System.out.println("防御力: " + fullWarrior.getDefense());
System.out.println("生命值: " + fullWarrior.getHealth());
System.out.println("技能数: " + fullWarrior.getSkills().size());
System.out.println("\n法师最终属性:");
System.out.println("攻击力: " + mageWithWandAndArmor.getAttack());
System.out.println("防御力: " + mageWithWandAndArmor.getDefense());
System.out.println("生命值: " + mageWithWandAndArmor.getHealth());
System.out.println("技能数: " + mageWithWandAndArmor.getSkills().size());
}
}
示例3:文本处理系统(Java I/O风格的例子)
java
import java.io.*;
// 1. 组件接口:文本处理器
interface TextProcessor {
String process(String text);
void writeToFile(String text, String filename) throws IOException;
}
// 2. 具体组件:基础文本处理器
class BasicTextProcessor implements TextProcessor {
@Override
public String process(String text) {
return text;
}
@Override
public void writeToFile(String text, String filename) throws IOException {
try (FileWriter writer = new FileWriter(filename)) {
writer.write(text);
}
}
}
// 3. 装饰器抽象类
abstract class TextProcessorDecorator implements TextProcessor {
protected TextProcessor textProcessor;
public TextProcessorDecorator(TextProcessor textProcessor) {
this.textProcessor = textProcessor;
}
@Override
public String process(String text) {
return textProcessor.process(text);
}
@Override
public void writeToFile(String text, String filename) throws IOException {
textProcessor.writeToFile(text, filename);
}
}
// 4. 具体装饰器:各种文本处理功能
class UpperCaseDecorator extends TextProcessorDecorator {
public UpperCaseDecorator(TextProcessor textProcessor) {
super(textProcessor);
}
@Override
public String process(String text) {
return textProcessor.process(text).toUpperCase();
}
}
class LowerCaseDecorator extends TextProcessorDecorator {
public LowerCaseDecorator(TextProcessor textProcessor) {
super(textProcessor);
}
@Override
public String process(String text) {
return textProcessor.process(text).toLowerCase();
}
}
class TrimDecorator extends TextProcessorDecorator {
public TrimDecorator(TextProcessor textProcessor) {
super(textProcessor);
}
@Override
public String process(String text) {
return textProcessor.process(text).trim();
}
}
class ReverseDecorator extends TextProcessorDecorator {
public ReverseDecorator(TextProcessor textProcessor) {
super(textProcessor);
}
@Override
public String process(String text) {
String processed = textProcessor.process(text);
return new StringBuilder(processed).reverse().toString();
}
}
class ReplaceSpaceDecorator extends TextProcessorDecorator {
private String replacement;
public ReplaceSpaceDecorator(TextProcessor textProcessor, String replacement) {
super(textProcessor);
this.replacement = replacement;
}
@Override
public String process(String text) {
return textProcessor.process(text).replace(" ", replacement);
}
}
class WordCountDecorator extends TextProcessorDecorator {
public WordCountDecorator(TextProcessor textProcessor) {
super(textProcessor);
}
@Override
public String process(String text) {
String processed = textProcessor.process(text);
int wordCount = processed.trim().isEmpty() ? 0 : processed.trim().split("\\s+").length;
return processed + "\n\n[单词数: " + wordCount + "]";
}
}
// 5. 客户端使用
public class TextProcessingDemo {
public static void main(String[] args) {
System.out.println("=== 装饰器模式示例:文本处理系统 ===\n");
String originalText = " Hello, World! This is a Decorator Pattern Example. ";
System.out.println("原始文本: \"" + originalText + "\"");
System.out.println("长度: " + originalText.length());
System.out.println("\n=== 各种文本处理组合 ===\n");
// 1. 基础处理
TextProcessor basic = new BasicTextProcessor();
System.out.println("1. 基础处理: " + basic.process(originalText));
// 2. 修剪 + 大写
TextProcessor trimAndUpper = new UpperCaseDecorator(new TrimDecorator(basic));
System.out.println("\n2. 修剪 + 大写: " + trimAndUpper.process(originalText));
// 3. 修剪 + 小写 + 替换空格
TextProcessor trimLowerReplace = new ReplaceSpaceDecorator(
new LowerCaseDecorator(
new TrimDecorator(basic)
), "_"
);
System.out.println("\n3. 修剪 + 小写 + 替换空格为_: " + trimLowerReplace.process(originalText));
// 4. 修剪 + 反转
TextProcessor trimAndReverse = new ReverseDecorator(new TrimDecorator(basic));
System.out.println("\n4. 修剪 + 反转: " + trimAndReverse.process(originalText));
// 5. 完整处理链
TextProcessor fullProcessing = new WordCountDecorator(
new ReplaceSpaceDecorator(
new UpperCaseDecorator(
new TrimDecorator(basic)
), "---"
)
);
System.out.println("\n5. 完整处理链 (修剪+大写+替换空格+单词统计):");
System.out.println(fullProcessing.process(originalText));
// 动态组合演示
System.out.println("\n=== 动态组合演示 ===\n");
TextProcessor processor = new BasicTextProcessor();
System.out.println("初始: " + processor.process(" Dynamic Processing "));
// 动态添加功能
processor = new TrimDecorator(processor);
System.out.println("加修剪: " + processor.process(" Dynamic Processing "));
processor = new UpperCaseDecorator(processor);
System.out.println("加大写: " + processor.process(" Dynamic Processing "));
processor = new ReverseDecorator(processor);
System.out.println("加反转: " + processor.process(" Dynamic Processing "));
// 写入文件示例
System.out.println("\n=== 文件操作演示 ===\n");
try {
TextProcessor fileProcessor = new WordCountDecorator(
new UpperCaseDecorator(
new BasicTextProcessor()
)
);
String testText = "This text will be written to a file with uppercase and word count.";
String processedText = fileProcessor.process(testText);
fileProcessor.writeToFile(processedText, "output.txt");
System.out.println("文件已生成: output.txt");
System.out.println("内容预览:\n" + processedText);
// 清理文件
new File("output.txt").delete();
} catch (IOException e) {
e.printStackTrace();
}
}
}
装饰器模式 vs 其他模式
| 模式 | 区别 |
|---|---|
| 装饰器 vs 继承 | 装饰器在运行时动态添加功能,继承在编译时静态扩展 |
| 装饰器 vs 组合 | 装饰器增强单个对象功能,组合处理对象集合 |
| 装饰器 vs 适配器 | 装饰器不改变接口,适配器改变接口 |
| 装饰器 vs 策略 | 装饰器添加功能,策略改变算法 |
装饰器模式的优缺点
优点:
- 灵活扩展:比继承更灵活,可以动态添加/移除功能
- 避免类爆炸:避免通过继承产生大量子类
- 符合开闭原则:新增装饰器无需修改现有代码
- 可以多层装饰:通过嵌套实现复杂功能组合
缺点:
- 增加复杂性:多层装饰使调试困难
- 顺序敏感性:装饰顺序可能影响结果
- 对象标识问题:装饰后的对象不等于原对象
实际应用场景
- Java I/O库 :
BufferedReader、ZipInputStream等 - Java Servlet :
HttpServletRequestWrapper - Spring框架 :
HandlerInterceptor - 图形界面:带滚动条、边框的组件
- Web中间件:身份验证、日志、压缩等处理链
装饰器模式的本质是:用组合替代继承,实现功能的动态扩展。它让代码更加灵活,更易于维护和扩展。
外观设计模式
通俗解释
想象要开一家公司,需要处理很多事情:注册工商、办理税务、开设银行账户、租赁办公室、招聘员工等。如果没有中介,需要分别跑工商局、税务局、银行、房产中介、招聘网站......
外观模式就像一个公司注册代办服务:只需要联系这个代办服务(外观),它就会帮处理所有繁琐的事情。不需要知道每个部门的具体流程,只需要告诉代办服务的需求。
核心思想 :为一组复杂的子系统提供一个统一的、简化的接口,隐藏子系统的复杂性,让客户端更容易使用。
关键特点
- 简化接口:将多个复杂接口转换为一个简单接口
- 解耦客户端与子系统:客户端不需要了解子系统内部细节
- 提高可维护性:子系统变化时,只需要修改外观类
- 易于使用:降低学习成本,让系统更易理解
现实生活例子
- 电脑启动按钮:按一下,电脑内部执行BIOS自检、加载操作系统、启动服务等复杂过程
- 汽车驾驶:只需要操作方向盘、油门、刹车,不需要知道发动机、变速箱如何工作
- 在线购物:点击"购买",系统自动处理库存检查、支付处理、物流安排等
- 酒店接待:告诉前台需求,他们帮处理房间、餐饮、清洁等
代码示例
示例1:家庭影院系统(经典例子)
java
import java.util.Arrays;
import java.util.List;
// 子系统类:各种设备
class DVDPlayer {
private String movie;
public void on() {
System.out.println("DVD播放器: 打开");
}
public void off() {
System.out.println("DVD播放器: 关闭");
}
public void play(String movie) {
this.movie = movie;
System.out.println("DVD播放器: 播放电影《" + movie + "》");
}
public void stop() {
System.out.println("DVD播放器: 停止播放《" + movie + "》");
}
public void eject() {
System.out.println("DVD播放器: 弹出光盘");
}
}
class Projector {
public void on() {
System.out.println("投影仪: 打开");
}
public void off() {
System.out.println("投影仪: 关闭");
}
public void wideScreenMode() {
System.out.println("投影仪: 设置为宽屏模式");
}
public void tvMode() {
System.out.println("投影仪: 设置为电视模式");
}
}
class SoundSystem {
private int volume = 5;
public void on() {
System.out.println("音响系统: 打开");
}
public void off() {
System.out.println("音响系统: 关闭");
}
public void setVolume(int level) {
volume = level;
System.out.println("音响系统: 设置音量为 " + level);
}
public void setSurroundSound() {
System.out.println("音响系统: 设置为环绕声模式");
}
public void setStereoSound() {
System.out.println("音响系统: 设置为立体声模式");
}
}
class Lights {
public void dim(int level) {
System.out.println("灯光: 调暗到 " + level + "%");
}
public void on() {
System.out.println("灯光: 打开");
}
public void off() {
System.out.println("灯光: 关闭");
}
}
class Screen {
public void down() {
System.out.println("幕布: 降下");
}
public void up() {
System.out.println("幕布: 升起");
}
}
class PopcornMachine {
public void on() {
System.out.println("爆米花机: 打开");
}
public void off() {
System.out.println("爆米花机: 关闭");
}
public void pop() {
System.out.println("爆米花机: 开始制作爆米花");
}
}
// 外观类:家庭影院外观
class HomeTheaterFacade {
private DVDPlayer dvdPlayer;
private Projector projector;
private SoundSystem soundSystem;
private Lights lights;
private Screen screen;
private PopcornMachine popcornMachine;
public HomeTheaterFacade(DVDPlayer dvdPlayer,
Projector projector,
SoundSystem soundSystem,
Lights lights,
Screen screen,
PopcornMachine popcornMachine) {
this.dvdPlayer = dvdPlayer;
this.projector = projector;
this.soundSystem = soundSystem;
this.lights = lights;
this.screen = screen;
this.popcornMachine = popcornMachine;
}
// 看电影的简化接口
public void watchMovie(String movie) {
System.out.println("🎬 准备看电影《" + movie + "》...");
// 以下步骤原本需要用户分别操作每个设备
popcornMachine.on();
popcornMachine.pop();
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
soundSystem.on();
soundSystem.setSurroundSound();
soundSystem.setVolume(8);
dvdPlayer.on();
dvdPlayer.play(movie);
System.out.println("✅ 电影《" + movie + "》开始播放,享受观影时光!\n");
}
// 结束电影的简化接口
public void endMovie() {
System.out.println("🛑 结束观影...");
popcornMachine.off();
dvdPlayer.stop();
dvdPlayer.eject();
dvdPlayer.off();
soundSystem.off();
projector.off();
screen.up();
lights.on();
System.out.println("✅ 家庭影院已关闭\n");
}
// 听音乐的简化接口
public void listenToMusic(String artist, String album) {
System.out.println("🎵 准备听音乐:" + artist + " - " + album);
lights.dim(30);
soundSystem.on();
soundSystem.setStereoSound();
soundSystem.setVolume(6);
System.out.println("✅ 开始播放音乐,享受休闲时光!\n");
}
// 停止音乐的简化接口
public void stopMusic() {
System.out.println("⏹️ 停止音乐播放...");
soundSystem.off();
lights.on();
System.out.println("✅ 音乐已停止\n");
}
// 游戏模式的简化接口
public void playGame(String game) {
System.out.println("🎮 准备玩游戏:" + game);
lights.dim(50);
projector.on();
projector.tvMode();
soundSystem.on();
soundSystem.setSurroundSound();
soundSystem.setVolume(7);
System.out.println("✅ 游戏模式已就绪,开始游戏吧!\n");
}
}
// 客户端使用
public class HomeTheaterDemo {
public static void main(String[] args) {
System.out.println("=== 外观模式示例:家庭影院系统 ===\n");
// 创建子系统组件
DVDPlayer dvdPlayer = new DVDPlayer();
Projector projector = new Projector();
SoundSystem soundSystem = new SoundSystem();
Lights lights = new Lights();
Screen screen = new Screen();
PopcornMachine popcornMachine = new PopcornMachine();
// 创建外观
HomeTheaterFacade homeTheater = new HomeTheaterFacade(
dvdPlayer, projector, soundSystem, lights, screen, popcornMachine
);
// 使用简化的外观接口
System.out.println("场景1: 看电影");
homeTheater.watchMovie("阿凡达");
homeTheater.endMovie();
System.out.println("场景2: 听音乐");
homeTheater.listenToMusic("周杰伦", "七里香");
homeTheater.stopMusic();
System.out.println("场景3: 玩游戏");
homeTheater.playGame("塞尔达传说");
// 对比:没有外观模式的复杂操作
System.out.println("=== 对比:没有外观模式的复杂操作 ===");
System.out.println("如果需要手动操作所有设备看电影,需要执行:");
System.out.println("1. popcornMachine.on()");
System.out.println("2. popcornMachine.pop()");
System.out.println("3. lights.dim(10)");
System.out.println("4. screen.down()");
System.out.println("5. projector.on()");
System.out.println("6. projector.wideScreenMode()");
System.out.println("7. soundSystem.on()");
System.out.println("8. soundSystem.setSurroundSound()");
System.out.println("9. soundSystem.setVolume(8)");
System.out.println("10. dvdPlayer.on()");
System.out.println("11. dvdPlayer.play(\"阿凡达\")");
System.out.println("\n而使用外观模式只需要:homeTheater.watchMovie(\"阿凡达\")");
}
}
示例2:在线购物系统
java
import java.util.*;
// 子系统类:库存管理
class InventoryService {
private Map<String, Integer> inventory = new HashMap<>();
public InventoryService() {
// 初始化库存
inventory.put("iPhone14", 10);
inventory.put("MacBook Pro", 5);
inventory.put("AirPods", 20);
inventory.put("iPad", 8);
}
public boolean checkAvailability(String productId, int quantity) {
Integer stock = inventory.get(productId);
if (stock == null) {
System.out.println("库存服务: 商品 " + productId + " 不存在");
return false;
}
if (stock >= quantity) {
System.out.println("库存服务: 商品 " + productId + " 库存充足 (" + stock + "件)");
return true;
} else {
System.out.println("库存服务: 商品 " + productId + " 库存不足,仅剩 " + stock + "件");
return false;
}
}
public void updateInventory(String productId, int quantity) {
Integer currentStock = inventory.get(productId);
if (currentStock != null) {
inventory.put(productId, currentStock - quantity);
System.out.println("库存服务: 更新库存," + productId + " 减少 " + quantity + "件,剩余 " + inventory.get(productId) + "件");
}
}
}
// 子系统类:支付处理
class PaymentService {
public boolean processPayment(String userId, double amount, String paymentMethod) {
System.out.println("支付服务: 处理支付 - 用户: " + userId +
", 金额: ¥" + amount +
", 支付方式: " + paymentMethod);
// 模拟支付处理
boolean success = Math.random() > 0.1; // 90%成功率
if (success) {
System.out.println("支付服务: 支付成功");
return true;
} else {
System.out.println("支付服务: 支付失败");
return false;
}
}
public void refund(String orderId, double amount) {
System.out.println("支付服务: 退款处理 - 订单: " + orderId + ", 金额: ¥" + amount);
}
}
// 子系统类:物流服务
class ShippingService {
public String createShipment(String orderId, String address, List<String> products) {
System.out.println("物流服务: 创建发货单 - 订单: " + orderId +
", 地址: " + address +
", 商品: " + products);
String trackingNumber = "TRK" + System.currentTimeMillis();
System.out.println("物流服务: 发货单已创建,运单号: " + trackingNumber);
return trackingNumber;
}
public void cancelShipment(String trackingNumber) {
System.out.println("物流服务: 取消发货 - 运单号: " + trackingNumber);
}
public String trackShipment(String trackingNumber) {
System.out.println("物流服务: 查询物流信息 - 运单号: " + trackingNumber);
// 模拟物流状态
String[] statuses = {"已揽收", "运输中", "派送中", "已签收"};
String status = statuses[(int)(Math.random() * statuses.length)];
return "物流状态: " + status;
}
}
// 子系统类:通知服务
class NotificationService {
public void sendEmail(String email, String subject, String content) {
System.out.println("通知服务: 发送邮件 - 收件人: " + email);
System.out.println(" 主题: " + subject);
System.out.println(" 内容: " + content);
}
public void sendSMS(String phone, String message) {
System.out.println("通知服务: 发送短信 - 手机号: " + phone);
System.out.println(" 内容: " + message);
}
public void sendAppNotification(String userId, String message) {
System.out.println("通知服务: 发送App通知 - 用户: " + userId);
System.out.println(" 内容: " + message);
}
}
// 子系统类:订单管理
class OrderService {
private int orderCounter = 1000;
public String createOrder(String userId, Map<String, Integer> items) {
String orderId = "ORD" + (orderCounter++);
System.out.println("订单服务: 创建订单 - 订单号: " + orderId +
", 用户: " + userId +
", 商品: " + items);
return orderId;
}
public void cancelOrder(String orderId) {
System.out.println("订单服务: 取消订单 - 订单号: " + orderId);
}
public String getOrderStatus(String orderId) {
System.out.println("订单服务: 查询订单状态 - 订单号: " + orderId);
// 模拟订单状态
String[] statuses = {"待支付", "已支付", "发货中", "已完成", "已取消"};
String status = statuses[(int)(Math.random() * statuses.length)];
return "订单状态: " + status;
}
}
// 外观类:购物系统外观
class ShoppingFacade {
private InventoryService inventoryService;
private PaymentService paymentService;
private ShippingService shippingService;
private NotificationService notificationService;
private OrderService orderService;
public ShoppingFacade() {
this.inventoryService = new InventoryService();
this.paymentService = new PaymentService();
this.shippingService = new ShippingService();
this.notificationService = new NotificationService();
this.orderService = new OrderService();
}
// 简化的购物接口
public ShoppingResult placeOrder(String userId,
Map<String, Integer> items,
String address,
String paymentMethod,
String email,
String phone) {
System.out.println("🛒 用户 " + userId + " 开始下单...");
System.out.println("商品: " + items);
// 1. 检查库存
System.out.println("\n步骤1: 检查库存");
for (Map.Entry<String, Integer> entry : items.entrySet()) {
if (!inventoryService.checkAvailability(entry.getKey(), entry.getValue())) {
return new ShoppingResult(false, "库存不足: " + entry.getKey(), null, null);
}
}
// 2. 创建订单
System.out.println("\n步骤2: 创建订单");
String orderId = orderService.createOrder(userId, items);
// 3. 处理支付
System.out.println("\n步骤3: 处理支付");
double totalAmount = calculateTotalAmount(items);
boolean paymentSuccess = paymentService.processPayment(userId, totalAmount, paymentMethod);
if (!paymentSuccess) {
// 支付失败,取消订单
orderService.cancelOrder(orderId);
return new ShoppingResult(false, "支付失败", orderId, null);
}
// 4. 更新库存
System.out.println("\n步骤4: 更新库存");
for (Map.Entry<String, Integer> entry : items.entrySet()) {
inventoryService.updateInventory(entry.getKey(), entry.getValue());
}
// 5. 创建发货单
System.out.println("\n步骤5: 安排发货");
List<String> productList = new ArrayList<>(items.keySet());
String trackingNumber = shippingService.createShipment(orderId, address, productList);
// 6. 发送通知
System.out.println("\n步骤6: 发送通知");
notificationService.sendEmail(email,
"订单确认 - " + orderId,
"您的订单已确认,运单号: " + trackingNumber + ",商品: " + items);
notificationService.sendSMS(phone,
"您的订单" + orderId + "已确认,运单号: " + trackingNumber);
notificationService.sendAppNotification(userId,
"订单" + orderId + "支付成功,已安排发货");
System.out.println("\n✅ 下单成功!");
return new ShoppingResult(true, "下单成功", orderId, trackingNumber);
}
// 简化的订单查询接口
public void trackOrder(String orderId, String email, String phone) {
System.out.println("📦 查询订单状态: " + orderId);
String orderStatus = orderService.getOrderStatus(orderId);
System.out.println(" " + orderStatus);
// 如果有运单号,查询物流
String trackingNumber = "TRK" + orderId.substring(3); // 模拟生成运单号
String shippingStatus = shippingService.trackShipment(trackingNumber);
System.out.println(" " + shippingStatus);
// 发送状态通知
notificationService.sendEmail(email,
"订单状态更新 - " + orderId,
orderStatus + "\n" + shippingStatus);
}
// 简化的取消订单接口
public void cancelOrder(String orderId, String reason, String email, String phone) {
System.out.println("❌ 取消订单: " + orderId + ",原因: " + reason);
// 1. 取消订单
orderService.cancelOrder(orderId);
// 2. 退款处理
paymentService.refund(orderId, 999.99); // 模拟金额
// 3. 取消发货
String trackingNumber = "TRK" + orderId.substring(3);
shippingService.cancelShipment(trackingNumber);
// 4. 发送通知
notificationService.sendEmail(email,
"订单取消通知 - " + orderId,
"您的订单已取消,原因: " + reason + ",退款将在3-7个工作日内处理。");
notificationService.sendSMS(phone,
"订单" + orderId + "已取消,退款处理中");
System.out.println("✅ 订单取消完成");
}
// 简化的退货接口
public void returnOrder(String orderId, String reason, String email) {
System.out.println("🔄 处理退货: " + orderId + ",原因: " + reason);
// 1. 退款处理
paymentService.refund(orderId, 999.99); // 模拟金额
// 2. 发送通知
notificationService.sendEmail(email,
"退货处理完成 - " + orderId,
"您的退货申请已处理,退款将在3-7个工作日内到账。");
System.out.println("✅ 退货处理完成");
}
private double calculateTotalAmount(Map<String, Integer> items) {
// 模拟商品价格
Map<String, Double> prices = new HashMap<>();
prices.put("iPhone14", 6999.0);
prices.put("MacBook Pro", 12999.0);
prices.put("AirPods", 1299.0);
prices.put("iPad", 3999.0);
double total = 0;
for (Map.Entry<String, Integer> entry : items.entrySet()) {
Double price = prices.get(entry.getKey());
if (price != null) {
total += price * entry.getValue();
}
}
return total;
}
}
// 封装购物结果
class ShoppingResult {
private boolean success;
private String message;
private String orderId;
private String trackingNumber;
public ShoppingResult(boolean success, String message, String orderId, String trackingNumber) {
this.success = success;
this.message = message;
this.orderId = orderId;
this.trackingNumber = trackingNumber;
}
// getters
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public String getOrderId() { return orderId; }
public String getTrackingNumber() { return trackingNumber; }
}
// 客户端使用
public class ShoppingSystemDemo {
public static void main(String[] args) {
System.out.println("=== 外观模式示例:在线购物系统 ===\n");
// 创建购物外观
ShoppingFacade shoppingFacade = new ShoppingFacade();
// 用户下单
System.out.println("场景1: 用户下单\n");
Map<String, Integer> order1 = new HashMap<>();
order1.put("iPhone14", 1);
order1.put("AirPods", 1);
ShoppingResult result = shoppingFacade.placeOrder(
"user123",
order1,
"北京市海淀区中关村大街1号",
"支付宝",
"user123@example.com",
"13800138000"
);
if (result.isSuccess()) {
System.out.println("\n下单成功!");
System.out.println("订单号: " + result.getOrderId());
System.out.println("运单号: " + result.getTrackingNumber());
}
System.out.println("\n" + "=".repeat(50) + "\n");
// 查询订单状态
System.out.println("场景2: 查询订单状态\n");
shoppingFacade.trackOrder(
"ORD1000",
"user123@example.com",
"13800138000"
);
System.out.println("\n" + "=".repeat(50) + "\n");
// 取消订单
System.out.println("场景3: 取消订单\n");
shoppingFacade.cancelOrder(
"ORD1001",
"改变主意",
"user456@example.com",
"13900139000"
);
System.out.println("\n" + "=".repeat(50) + "\n");
// 对比:没有外观模式的复杂操作
System.out.println("=== 对比:没有外观模式的复杂操作 ===");
System.out.println("如果用户需要手动完成购物流程,需要:");
System.out.println("1. 调用 inventoryService.checkAvailability() 检查每个商品库存");
System.out.println("2. 调用 orderService.createOrder() 创建订单");
System.out.println("3. 调用 paymentService.processPayment() 处理支付");
System.out.println("4. 调用 inventoryService.updateInventory() 更新库存");
System.out.println("5. 调用 shippingService.createShipment() 创建发货单");
System.out.println("6. 调用 notificationService.sendEmail() 发送邮件通知");
System.out.println("7. 调用 notificationService.sendSMS() 发送短信通知");
System.out.println("8. 调用 notificationService.sendAppNotification() 发送App通知");
System.out.println("\n而使用外观模式只需要:shoppingFacade.placeOrder(...)");
System.out.println("\n" + "=".repeat(50) + "\n");
// 测试退货流程
System.out.println("场景4: 退货处理\n");
shoppingFacade.returnOrder(
"ORD1002",
"商品有瑕疵",
"user789@example.com"
);
}
}
示例3:更简化的电脑启动系统
java
// 子系统类
class CPU {
public void start() {
System.out.println("CPU: 启动...");
System.out.println("CPU: 初始化寄存器...");
System.out.println("CPU: 启动完成");
}
}
class Memory {
public void load() {
System.out.println("内存: 加载数据...");
System.out.println("内存: 自检完成");
}
}
class HardDrive {
public void read() {
System.out.println("硬盘: 读取引导扇区...");
System.out.println("硬盘: 加载操作系统文件...");
}
}
class BIOS {
public void selfTest() {
System.out.println("BIOS: 执行开机自检(POST)...");
System.out.println("BIOS: 检查硬件设备...");
System.out.println("BIOS: 自检完成,一切正常");
}
}
class OperatingSystem {
public void boot() {
System.out.println("操作系统: 启动内核...");
System.out.println("操作系统: 初始化设备驱动...");
System.out.println("操作系统: 启动系统服务...");
System.out.println("操作系统: 启动完成,欢迎使用!");
}
}
// 外观类:电脑启动外观
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
private BIOS bios;
private OperatingSystem os;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
this.bios = new BIOS();
this.os = new OperatingSystem();
}
// 简化的启动接口
public void startComputer() {
System.out.println("💻 正在启动电脑...\n");
bios.selfTest();
System.out.println();
cpu.start();
System.out.println();
memory.load();
System.out.println();
hardDrive.read();
System.out.println();
os.boot();
System.out.println("\n✅ 电脑启动完成,可以开始使用了!");
}
// 简化的关机接口
public void shutdownComputer() {
System.out.println("🛑 正在关闭电脑...\n");
System.out.println("操作系统: 保存所有工作...");
System.out.println("操作系统: 关闭所有应用程序...");
System.out.println("操作系统: 停止系统服务...");
System.out.println("硬件: 关闭电源...");
System.out.println("\n✅ 电脑已关闭");
}
// 简化的重启接口
public void restartComputer() {
System.out.println("🔄 正在重启电脑...\n");
shutdownComputer();
System.out.println("\n--- 重启中 ---\n");
startComputer();
}
}
// 客户端使用
public class ComputerSystemDemo {
public static void main(String[] args) {
System.out.println("=== 外观模式示例:电脑启动系统 ===\n");
// 创建电脑外观
ComputerFacade computer = new ComputerFacade();
// 用户只需要按一个按钮
System.out.println("用户按下电源按钮:");
computer.startComputer();
System.out.println("\n" + "=".repeat(50) + "\n");
System.out.println("用户选择重启:");
computer.restartComputer();
System.out.println("\n" + "=".repeat(50) + "\n");
// 对比:没有外观模式的复杂操作
System.out.println("=== 对比:没有外观模式的复杂操作 ===");
System.out.println("如果没有外观模式,用户需要:");
System.out.println("1. 调用 bios.selfTest()");
System.out.println("2. 调用 cpu.start()");
System.out.println("3. 调用 memory.load()");
System.out.println("4. 调用 hardDrive.read()");
System.out.println("5. 调用 os.boot()");
System.out.println("\n而使用外观模式只需要:computer.startComputer()");
System.out.println("\n" + "=".repeat(50) + "\n");
System.out.println("用户选择关机:");
computer.shutdownComputer();
}
}
外观模式的优缺点
优点:
- 简化客户端代码:客户端不需要了解复杂子系统
- 降低耦合度:子系统变化不会直接影响客户端
- 提高安全性:可以限制客户端对子系统的直接访问
- 易于使用:提供了一个清晰的、高级的接口
- 符合单一职责原则:外观类专门负责简化接口
缺点:
- 可能成为上帝对象:如果外观类过于庞大,会承担太多职责
- 增加了一层抽象:可能稍微影响性能
- 可能隐藏了有用的功能:过于简化的接口可能无法使用子系统的所有功能
使用场景
- 复杂子系统需要简化接口:如框架、库的API设计
- 需要解耦客户端和子系统:如多层架构中的层间通信
- 需要为子系统提供统一入口:如微服务网关
- 遗留系统集成:为新系统提供统一接口访问老系统
外观模式 vs 其他模式
| 模式 | 区别 |
|---|---|
| 外观 vs 适配器 | 外观简化接口,适配器转换接口 |
| 外观 vs 中介者 | 外观单向简化,中介者多向协调 |
| 外观 vs 单例 | 外观可以有多个实例,单例只有一个 |
| 外观 vs 代理 | 外观简化接口,代理控制访问 |
实际应用
- Spring框架 :
JdbcTemplate封装了JDBC的复杂操作 - SLF4J日志门面:统一了不同日志框架的接口
- Servlet API :
HttpServletRequest和HttpServletResponse封装了HTTP细节 - JDBC驱动管理器 :
DriverManager简化了数据库连接 - Android Context:封装了应用环境信息访问
外观模式的核心价值在于:为复杂系统提供一个简单、统一的入口,让用户能够更容易地使用系统功能,而无需了解内部复杂实现。
享元设计模式
通俗解释
想象一下正在开发一个大型多人在线游戏,游戏中有成千上万棵树。如果每棵树都创建一个独立的对象(包含位置、颜色、纹理等所有属性),内存很快就会耗尽。
享元模式就像"共享单车"系统:与其为每个用户制造一辆新车,不如共享已有的自行车。同样,享元模式通过共享相似对象来减少内存使用。
核心思想 :共享大量细粒度对象,减少内存占用。将对象的"固有状态"(不变的部分)与"外部状态"(变化的部分)分离,只存储一份固有状态,通过参数传递外部状态。
关键特点
- 减少内存使用:共享相同部分,避免重复存储
- 分离内部/外部状态 :
- 内部状态:对象共享的不变部分(如树的纹理、颜色)
- 外部状态:对象特有的变化部分(如树的位置、大小)
- 工厂管理共享:使用工厂创建和管理共享对象
- 适合大量对象场景:当系统中存在大量相似对象时特别有效
现实生活例子
- 文字处理器:每个字符共享字体、颜色等属性,只存储一次
- 游戏开发:树木、云朵、子弹等大量重复对象
- 数据库连接池:共享数据库连接对象
- 浏览器标签页:共享浏览器内核实例
- 棋类游戏:棋盘上的棋子共享形状、颜色
代码示例
示例1:文字编辑器中的字符处理(经典例子)
java
import java.util.*;
// 1. 享元接口:字符
interface Character {
void display(int positionX, int positionY, String color);
}
// 2. 具体享元:具体字符
class ConcreteCharacter implements Character {
private char symbol; // 内部状态:字符本身(不变)
private String font; // 内部状态:字体(不变)
private int size; // 内部状态:字号(不变)
public ConcreteCharacter(char symbol, String font, int size) {
this.symbol = symbol;
this.font = font;
this.size = size;
System.out.println("创建字符对象: " + symbol + " (字体: " + font + ", 字号: " + size + ")");
}
@Override
public void display(int positionX, int positionY, String color) {
// 外部状态:位置和颜色由客户端传入
System.out.println("显示字符 '" + symbol +
"' 在位置(" + positionX + "," + positionY +
"), 颜色: " + color +
", 字体: " + font +
", 字号: " + size);
}
// 内部状态getters(用于比较)
public char getSymbol() { return symbol; }
public String getFont() { return font; }
public int getSize() { return size; }
}
// 3. 享元工厂:管理共享的字符对象
class CharacterFactory {
private static Map<String, Character> characterPool = new HashMap<>();
public static Character getCharacter(char symbol, String font, int size) {
// 创建唯一标识符
String key = symbol + "_" + font + "_" + size;
// 如果池中已有,直接返回
if (characterPool.containsKey(key)) {
System.out.println("从池中获取字符: " + symbol);
return characterPool.get(key);
}
// 否则创建新对象并放入池中
Character character = new ConcreteCharacter(symbol, font, size);
characterPool.put(key, character);
return character;
}
public static int getPoolSize() {
return characterPool.size();
}
public static void showPool() {
System.out.println("\n字符池内容 (" + characterPool.size() + "个唯一字符):");
for (String key : characterPool.keySet()) {
System.out.println(" " + key);
}
}
}
// 4. 客户端:文档类,存储字符及其外部状态
class Document {
private List<CharacterEntry> characters = new ArrayList<>();
// 内部类,存储字符引用和外部状态
static class CharacterEntry {
Character character;
int positionX;
int positionY;
String color;
CharacterEntry(Character character, int positionX, int positionY, String color) {
this.character = character;
this.positionX = positionX;
this.positionY = positionY;
this.color = color;
}
}
public void addCharacter(char symbol, String font, int size,
int positionX, int positionY, String color) {
Character character = CharacterFactory.getCharacter(symbol, font, size);
characters.add(new CharacterEntry(character, positionX, positionY, color));
}
public void display() {
System.out.println("\n=== 文档内容 ===");
for (CharacterEntry entry : characters) {
entry.character.display(entry.positionX, entry.positionY, entry.color);
}
}
public int getCharacterCount() {
return characters.size();
}
}
// 5. 主程序
public class TextEditorDemo {
public static void main(String[] args) {
System.out.println("=== 享元模式示例:文字编辑器 ===\n");
// 创建文档
Document document = new Document();
System.out.println("向文档添加字符...\n");
// 添加大量字符,很多是重复的
// 第一行: Hello World!
document.addCharacter('H', "Arial", 12, 10, 10, "黑色");
document.addCharacter('e', "Arial", 12, 20, 10, "黑色");
document.addCharacter('l', "Arial", 12, 30, 10, "黑色");
document.addCharacter('l', "Arial", 12, 40, 10, "黑色"); // 重复的'l'
document.addCharacter('o', "Arial", 12, 50, 10, "黑色");
document.addCharacter(' ', "Arial", 12, 60, 10, "黑色");
document.addCharacter('W', "Arial", 12, 70, 10, "黑色");
document.addCharacter('o', "Arial", 12, 80, 10, "黑色"); // 重复的'o'
document.addCharacter('r', "Arial", 12, 90, 10, "黑色");
document.addCharacter('l', "Arial", 12, 100, 10, "黑色"); // 重复的'l'
document.addCharacter('d', "Arial", 12, 110, 10, "黑色");
document.addCharacter('!', "Arial", 12, 120, 10, "红色");
// 第二行: 重复的字符测试
document.addCharacter('a', "Times New Roman", 14, 10, 30, "蓝色");
document.addCharacter('a', "Times New Roman", 14, 25, 30, "蓝色"); // 完全相同的字符
document.addCharacter('a', "Arial", 14, 40, 30, "蓝色"); // 字体不同,不是完全相同的字符
document.addCharacter('a', "Times New Roman", 16, 55, 30, "蓝色"); // 字号不同,不是完全相同的字符
// 显示文档
document.display();
// 显示统计信息
System.out.println("\n=== 内存使用统计 ===");
System.out.println("文档中字符总数: " + document.getCharacterCount());
System.out.println("实际创建的字符对象数: " + CharacterFactory.getPoolSize());
System.out.println("节省的内存: " +
(document.getCharacterCount() - CharacterFactory.getPoolSize()) +
" 个对象");
// 显示字符池内容
CharacterFactory.showPool();
// 性能对比:传统方式 vs 享元模式
System.out.println("\n=== 性能对比 ===");
// 传统方式:每个字符都创建新对象
long startTime = System.currentTimeMillis();
List<Character> traditionalList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
// 模拟添加字符,其中很多重复
char c = (char)('a' + (i % 26)); // 26个字母循环
traditionalList.add(new ConcreteCharacter(c, "Arial", 12));
}
long traditionalTime = System.currentTimeMillis() - startTime;
System.out.println("传统方式创建10000个字符对象用时: " + traditionalTime + "ms");
System.out.println("传统方式创建的对象数: " + traditionalList.size());
// 享元模式
startTime = System.currentTimeMillis();
Document largeDocument = new Document();
for (int i = 0; i < 10000; i++) {
char c = (char)('a' + (i % 26));
largeDocument.addCharacter(c, "Arial", 12, i*10, 50, "黑色");
}
long flyweightTime = System.currentTimeMillis() - startTime;
System.out.println("享元模式处理10000个字符用时: " + flyweightTime + "ms");
System.out.println("享元模式实际创建的对象数: " + CharacterFactory.getPoolSize());
System.out.println("内存节省: " + (10000 - CharacterFactory.getPoolSize()) + " 个对象");
}
}
示例2:游戏中的树木渲染(实用例子)
java
import java.util.*;
// 1. 享元接口:树
interface Tree {
void render(int x, int y, int height);
}
// 2. 具体享元:树的类型(内部状态)
class TreeType implements Tree {
private String name; // 内部状态:树种名称
private String texture; // 内部状态:纹理
private String color; // 内部状态:颜色
public TreeType(String name, String texture, String color) {
this.name = name;
this.texture = texture;
this.color = color;
System.out.println("创建树类型: " + name + " (纹理: " + texture + ", 颜色: " + color + ")");
}
@Override
public void render(int x, int y, int height) {
// 外部状态:位置和高度由客户端传入
System.out.println("渲染 " + name +
" 在位置(" + x + "," + y +
"), 高度: " + height +
"米, 颜色: " + color);
// 在实际游戏中,这里会调用图形API渲染
}
// 获取内部状态(用于比较)
public String getName() { return name; }
public String getTexture() { return texture; }
public String getColor() { return color; }
}
// 3. 享元工厂:树类型工厂
class TreeFactory {
private static Map<String, TreeType> treeTypes = new HashMap<>();
public static TreeType getTreeType(String name, String texture, String color) {
String key = name + "_" + texture + "_" + color;
if (treeTypes.containsKey(key)) {
return treeTypes.get(key);
}
TreeType type = new TreeType(name, texture, color);
treeTypes.put(key, type);
return type;
}
public static int getTypeCount() {
return treeTypes.size();
}
public static void showTypes() {
System.out.println("\n树类型池 (" + treeTypes.size() + "种类型):");
for (TreeType type : treeTypes.values()) {
System.out.println(" " + type.getName() + " - " + type.getColor());
}
}
}
// 4. 游戏中的具体树(包含外部状态)
class GameTree {
private TreeType type; // 共享的内部状态
private int x, y; // 外部状态:位置
private int height; // 外部状态:高度
public GameTree(String name, String texture, String color, int x, int y, int height) {
this.type = TreeFactory.getTreeType(name, texture, color);
this.x = x;
this.y = y;
this.height = height;
}
public void render() {
type.render(x, y, height);
}
// 可以改变外部状态
public void move(int newX, int newY) {
this.x = newX;
this.y = newY;
}
public void grow(int additionalHeight) {
this.height += additionalHeight;
}
// getters
public int getX() { return x; }
public int getY() { return y; }
public int getHeight() { return height; }
}
// 5. 游戏森林:管理大量树木
class Forest {
private List<GameTree> trees = new ArrayList<>();
private Random random = new Random();
// 预定义的树类型
private static final String[] TREE_NAMES = {"松树", "橡树", "枫树", "白桦", "柳树"};
private static final String[] TEXTURES = {"针叶", "宽叶", "红叶", "白皮", "垂枝"};
private static final String[] COLORS = {"深绿", "浅绿", "红色", "白色", "黄色"};
public void plantRandomTree(int areaWidth, int areaHeight) {
String name = TREE_NAMES[random.nextInt(TREE_NAMES.length)];
String texture = TEXTURES[random.nextInt(TEXTURES.length)];
String color = COLORS[random.nextInt(COLORS.length)];
int x = random.nextInt(areaWidth);
int y = random.nextInt(areaHeight);
int height = 5 + random.nextInt(20); // 5-25米
trees.add(new GameTree(name, texture, color, x, y, height));
}
public void plantTree(String name, String texture, String color,
int x, int y, int height) {
trees.add(new GameTree(name, texture, color, x, y, height));
}
public void renderForest() {
System.out.println("\n=== 渲染森林 (" + trees.size() + "棵树) ===");
for (GameTree tree : trees) {
tree.render();
}
}
public void growAllTrees(int growth) {
for (GameTree tree : trees) {
tree.grow(growth);
}
}
public int getTreeCount() {
return trees.size();
}
// 统计不同位置的树
public Map<String, Integer> countByType() {
Map<String, Integer> counts = new HashMap<>();
// 注意:这里简化处理,实际需要更多逻辑来识别相同类型
for (GameTree tree : trees) {
String key = tree.getX() + "," + tree.getY();
counts.put(key, counts.getOrDefault(key, 0) + 1);
}
return counts;
}
}
// 6. 主程序
public class GameForestDemo {
public static void main(String[] args) {
System.out.println("=== 享元模式示例:游戏森林 ===\n");
// 创建森林
Forest forest = new Forest();
System.out.println("种植树木...\n");
// 种植一些特定树木
forest.plantTree("松树", "针叶", "深绿", 10, 20, 15);
forest.plantTree("橡树", "宽叶", "浅绿", 30, 40, 20);
forest.plantTree("松树", "针叶", "深绿", 50, 60, 18); // 相同类型,不同位置
forest.plantTree("枫树", "红叶", "红色", 70, 80, 12);
forest.plantTree("松树", "针叶", "深绿", 90, 100, 16); // 相同类型,不同位置
forest.plantTree("橡树", "宽叶", "浅绿", 110, 120, 22); // 相同类型,不同位置
// 随机种植大量树木
System.out.println("随机种植1000棵树...");
for (int i = 0; i < 1000; i++) {
forest.plantRandomTree(1000, 1000);
}
// 渲染森林
forest.renderForest();
// 显示统计信息
System.out.println("\n=== 内存使用统计 ===");
System.out.println("森林中树木总数: " + forest.getTreeCount());
System.out.println("实际创建的树类型对象数: " + TreeFactory.getTypeCount());
System.out.println("节省的内存: " +
(forest.getTreeCount() - TreeFactory.getTypeCount()) +
" 个对象");
// 显示树类型池
TreeFactory.showTypes();
// 模拟游戏更新:所有树生长
System.out.println("\n=== 游戏更新:经过一年 ===");
forest.growAllTrees(2);
// 重新渲染
System.out.println("\n重新渲染森林...");
// forest.renderForest(); // 注释掉避免输出太多
// 性能对比
System.out.println("\n=== 性能对比 ===");
// 传统方式
long startTime = System.currentTimeMillis();
List<TreeType> traditionalTrees = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
// 模拟大量树对象,很多重复
String name = TREE_NAMES[i % TREE_NAMES.length];
traditionalTrees.add(new TreeType(name, "default", "green"));
}
long traditionalTime = System.currentTimeMillis() - startTime;
System.out.println("传统方式创建10000棵树对象用时: " + traditionalTime + "ms");
System.out.println("传统方式创建的对象数: " + traditionalTrees.size());
// 享元模式
startTime = System.currentTimeMillis();
Forest largeForest = new Forest();
for (int i = 0; i < 10000; i++) {
largeForest.plantRandomTree(10000, 10000);
}
long flyweightTime = System.currentTimeMillis() - startTime;
System.out.println("享元模式处理10000棵树用时: " + flyweightTime + "ms");
System.out.println("享元模式实际创建的类型对象数: " + TreeFactory.getTypeCount());
// 内存占用估算
System.out.println("\n=== 内存占用估算 ===");
int treeObjectSize = 100; // 假设每个树对象100字节
int typeObjectSize = 200; // 假设每个类型对象200字节
int traditionalMemory = 10000 * treeObjectSize;
int flyweightMemory = TreeFactory.getTypeCount() * typeObjectSize +
10000 * 12; // 外部状态(3个int,每个4字节)
System.out.println("传统方式内存占用: " + traditionalMemory + " 字节");
System.out.println("享元模式内存占用: " + flyweightMemory + " 字节");
System.out.println("内存节省: " + (traditionalMemory - flyweightMemory) + " 字节 (" +
((traditionalMemory - flyweightMemory) * 100 / traditionalMemory) + "%)");
}
}
示例3:更简化的例子:棋类游戏
java
import java.util.HashMap;
import java.util.Map;
// 1. 享元接口:棋子
interface ChessPiece {
void draw(int x, int y);
}
// 2. 具体享元:棋子类型
class PieceType implements ChessPiece {
private String name; // 内部状态:棋子名称
private String color; // 内部状态:颜色
private String symbol; // 内部状态:符号
public PieceType(String name, String color, String symbol) {
this.name = name;
this.color = color;
this.symbol = symbol;
System.out.println("创建棋子类型: " + name + " (" + color + ")");
}
@Override
public void draw(int x, int y) {
System.out.println("在位置(" + x + "," + y + ")绘制" + color + "的" + name + symbol);
}
}
// 3. 享元工厂
class PieceFactory {
private static Map<String, PieceType> pieces = new HashMap<>();
public static PieceType getPiece(String name, String color) {
String key = name + "_" + color;
if (pieces.containsKey(key)) {
return pieces.get(key);
}
// 根据棋子和颜色确定符号
String symbol = getSymbol(name);
PieceType piece = new PieceType(name, color, symbol);
pieces.put(key, piece);
return piece;
}
private static String getSymbol(String name) {
switch(name) {
case "王": return "♔";
case "后": return "♕";
case "车": return "♖";
case "象": return "♗";
case "马": return "♘";
case "兵": return "♙";
default: return "?";
}
}
public static int getPieceTypeCount() {
return pieces.size();
}
}
// 4. 棋盘上的具体棋子
class BoardPiece {
private PieceType type; // 共享的内部状态
private int x, y; // 外部状态:位置
public BoardPiece(String name, String color, int x, int y) {
this.type = PieceFactory.getPiece(name, color);
this.x = x;
this.y = y;
}
public void draw() {
type.draw(x, y);
}
public void move(int newX, int newY) {
this.x = newX;
this.y = newY;
System.out.println("移动棋子到(" + newX + "," + newY + ")");
}
}
// 5. 棋盘
class ChessBoard {
private BoardPiece[][] board = new BoardPiece[8][8];
public void setup() {
System.out.println("=== 设置国际象棋棋盘 ===");
// 白方
placePiece("车", "白", 0, 0);
placePiece("马", "白", 1, 0);
placePiece("象", "白", 2, 0);
placePiece("后", "白", 3, 0);
placePiece("王", "白", 4, 0);
placePiece("象", "白", 5, 0);
placePiece("马", "白", 6, 0);
placePiece("车", "白", 7, 0);
for (int i = 0; i < 8; i++) {
placePiece("兵", "白", i, 1);
}
// 黑方
placePiece("车", "黑", 0, 7);
placePiece("马", "黑", 1, 7);
placePiece("象", "黑", 2, 7);
placePiece("后", "黑", 3, 7);
placePiece("王", "黑", 4, 7);
placePiece("象", "黑", 5, 7);
placePiece("马", "黑", 6, 7);
placePiece("车", "黑", 7, 7);
for (int i = 0; i < 8; i++) {
placePiece("兵", "黑", i, 6);
}
}
private void placePiece(String name, String color, int x, int y) {
board[x][y] = new BoardPiece(name, color, x, y);
}
public void display() {
System.out.println("\n=== 棋盘显示 ===");
for (int y = 7; y >= 0; y--) {
System.out.print(y + " ");
for (int x = 0; x < 8; x++) {
if (board[x][y] != null) {
System.out.print("P "); // P表示有棋子
} else {
System.out.print(". ");
}
}
System.out.println();
}
System.out.println(" 0 1 2 3 4 5 6 7");
}
public void drawAllPieces() {
System.out.println("\n=== 绘制所有棋子 ===");
for (int y = 7; y >= 0; y--) {
for (int x = 0; x < 8; x++) {
if (board[x][y] != null) {
board[x][y].draw();
}
}
}
}
// 模拟移动
public void simulateMove() {
System.out.println("\n=== 模拟棋步 ===");
if (board[4][1] != null) { // 白兵
board[4][1].move(4, 3);
board[4][3] = board[4][1];
board[4][1] = null;
}
}
}
// 6. 主程序
public class ChessGameDemo {
public static void main(String[] args) {
System.out.println("=== 享元模式示例:国际象棋 ===\n");
ChessBoard board = new ChessBoard();
board.setup();
board.display();
board.drawAllPieces();
System.out.println("\n=== 内存统计 ===");
System.out.println("棋盘上棋子总数: 32");
System.out.println("实际创建的棋子类型对象数: " + PieceFactory.getPieceTypeCount());
// 假设有多个棋盘
System.out.println("\n=== 多棋盘场景 ===");
System.out.println("如果有10个棋盘:");
System.out.println("传统方式需要: 10 * 32 = 320 个棋子对象");
System.out.println("享元模式需要: " + PieceFactory.getPieceTypeCount() +
" 个类型对象 + 10 * 32 个位置信息");
System.out.println("节省了 " + (320 - PieceFactory.getPieceTypeCount()) +
" 个重复的类型对象");
// 模拟棋步
board.simulateMove();
board.display();
}
}
享元模式的优缺点
优点:
- 大幅减少内存使用:共享相同部分,避免重复对象
- 提高性能:减少对象创建和垃圾回收开销
- 支持大量对象:可以处理传统方式无法处理的大量对象
- 集中管理共享状态:便于统一管理和修改
缺点:
- 增加系统复杂性:需要区分内部状态和外部状态
- 可能引入线程安全问题:共享对象需要线程安全处理
- 外部状态管理复杂:客户端需要维护外部状态
- 可能影响执行效率:传递外部状态可能降低性能
使用场景
- 大量相似对象:系统中有大量相似对象,且这些对象可以共享部分状态
- 内存敏感应用:移动设备、嵌入式系统等内存受限环境
- 缓存数据:需要缓存大量重复数据
- 游戏开发:大量粒子、树木、NPC等游戏对象
- 图形编辑软件:大量图形元素共享样式属性
享元模式 vs 其他模式
| 模式 | 区别 |
|---|---|
| 享元 vs 单例 | 享元可以有多个共享实例,单例只有一个实例 |
| 享元 vs 原型 | 享元共享不变部分,原型复制整个对象 |
| 享元 vs 组合 | 享元处理大量细粒度对象,组合处理树形结构 |
| 享元 vs 对象池 | 享元共享不可变部分,对象池复用整个对象 |
Java中的享元模式应用
- String常量池:Java中的字符串常量池就是享元模式
- Integer缓存:Integer.valueOf()缓存-128到127的整数
- JDBC连接池:数据库连接共享
- 线程池:线程对象重用
实际应用技巧
- 识别内部状态:找出对象中不变的部分
- 外部状态参数化:将变化的部分作为参数传递
- 使用工厂管理:确保对象正确共享
- 考虑线程安全:共享对象需要同步机制
- 权衡利弊:不是所有场景都适合享元模式
享元模式的核心价值在于:通过共享来高效地支持大量细粒度对象,在内存和性能之间找到平衡。当系统中存在大量相似对象时,享元模式可以显著提高性能并减少内存占用。
代理设计模式
通俗解释
想象一下明星和经纪人的关系:
- 明星(真实对象):负责核心工作(唱歌、演戏)
- 经纪人(代理对象):负责处理各种杂事(安排行程、过滤粉丝、谈判合同)
当粉丝想见明星时,不能直接联系明星,而是通过经纪人。经纪人会:
- 先筛选粉丝的请求(访问控制)
- 安排合适的时间(延迟处理)
- 记录所有见面记录(日志记录)
- 最后才让粉丝见到明星
核心思想 :为另一个对象提供一个代理或占位符,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。
关键特点
- 控制访问:限制、增强或修改对目标对象的访问
- 透明性:客户端不知道自己在使用代理,以为直接和目标对象交互
- 延迟初始化:可以推迟创建开销大的对象
- 开闭原则:可以在不修改目标对象的情况下增加新功能
代理模式类型
- 虚拟代理:延迟创建开销大的对象
- 保护代理:控制访问权限
- 远程代理:为远程对象提供本地代表
- 智能代理:添加额外功能(日志、缓存等)
现实生活例子
- VPN代理:访问受限网站的中介
- 信用卡:现金的代理,提供额外功能(积分、分期)
- 网页缓存:保存网页副本,减少服务器请求
- 门禁系统:控制对建筑物的访问
- 游戏中的懒加载:只在需要时加载高清纹理
代码示例
示例1:虚拟代理 - 图片加载器(经典例子)
java
import java.util.ArrayList;
import java.util.List;
// 1. 主题接口:图片
interface Image {
void display();
String getFileName();
}
// 2. 真实主题:高分辨率图片(创建成本高)
class HighResolutionImage implements Image {
private String fileName;
private int width;
private int height;
private byte[] imageData;
public HighResolutionImage(String fileName) {
this.fileName = fileName;
loadImageFromDisk(); // 模拟高成本操作
}
private void loadImageFromDisk() {
System.out.println("正在从磁盘加载高分辨率图片: " + fileName);
// 模拟耗时操作
try {
Thread.sleep(2000); // 模拟加载时间
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟加载图片数据(这里用随机大小代替)
this.width = 1920 + (int)(Math.random() * 1000);
this.height = 1080 + (int)(Math.random() * 1000);
this.imageData = new byte[width * height * 3]; // 假设每个像素3字节
System.out.println("图片加载完成: " + fileName +
" (" + width + "x" + height + ")");
}
@Override
public void display() {
System.out.println("显示高分辨率图片: " + fileName +
" (" + width + "x" + height + ")");
// 在实际应用中,这里会调用图形API显示图片
}
@Override
public String getFileName() {
return fileName;
}
public int getWidth() { return width; }
public int getHeight() { return height; }
}
// 3. 虚拟代理:图片代理(延迟加载)
class ImageProxy implements Image {
private String fileName;
private HighResolutionImage realImage; // 真实图片(延迟加载)
private boolean isLoaded = false;
public ImageProxy(String fileName) {
this.fileName = fileName;
System.out.println("创建图片代理: " + fileName + "(图片尚未加载)");
}
@Override
public void display() {
// 延迟加载:只在需要显示时才加载真实图片
if (!isLoaded) {
loadRealImage();
}
realImage.display();
}
@Override
public String getFileName() {
return fileName;
}
private void loadRealImage() {
if (realImage == null) {
realImage = new HighResolutionImage(fileName);
isLoaded = true;
}
}
// 代理还可以提供额外功能
public void preload() {
System.out.println("预加载图片: " + fileName);
loadRealImage();
}
public boolean isImageLoaded() {
return isLoaded;
}
public void showThumbnail() {
System.out.println("显示缩略图: " + fileName + " [低分辨率预览]");
// 显示低分辨率预览,不需要加载完整图片
}
}
// 4. 客户端:图片查看器
class ImageViewer {
private List<Image> images = new ArrayList<>();
public void addImage(Image image) {
images.add(image);
}
public void displayAllImages() {
System.out.println("\n=== 显示所有图片 ===");
for (Image image : images) {
image.display();
}
}
public void displayImage(int index) {
if (index >= 0 && index < images.size()) {
images.get(index).display();
}
}
public void showAllThumbnails() {
System.out.println("\n=== 显示所有缩略图 ===");
for (Image image : images) {
if (image instanceof ImageProxy) {
((ImageProxy) image).showThumbnail();
} else {
System.out.println("显示: " + image.getFileName());
}
}
}
}
// 5. 主程序
public class VirtualProxyDemo {
public static void main(String[] args) {
System.out.println("=== 代理模式示例:虚拟代理(图片延迟加载) ===\n");
// 创建图片查看器
ImageViewer viewer = new ImageViewer();
System.out.println("创建图片对象(使用代理)...");
// 使用代理创建图片对象
viewer.addImage(new ImageProxy("nature.jpg"));
viewer.addImage(new ImageProxy("city.jpg"));
viewer.addImage(new ImageProxy("portrait.jpg"));
viewer.addImage(new ImageProxy("landscape.jpg"));
viewer.addImage(new ImageProxy("abstract.jpg"));
System.out.println("\n所有图片代理已创建,但真实图片尚未加载。");
System.out.println("内存占用小,启动速度快。\n");
// 用户浏览缩略图(不需要加载完整图片)
viewer.showAllThumbnails();
System.out.println("\n" + "=".repeat(50));
System.out.println("用户点击查看第一张图片...");
viewer.displayImage(0); // 只有这张图片会被加载
System.out.println("\n" + "=".repeat(50));
System.out.println("用户点击查看第三张图片...");
viewer.displayImage(2); // 只有这张图片会被加载
System.out.println("\n" + "=".repeat(50));
System.out.println("用户决定查看所有图片...");
viewer.displayAllImages(); // 加载剩余所有图片
// 性能对比
System.out.println("\n" + "=".repeat(50));
System.out.println("=== 性能对比:直接加载 vs 代理延迟加载 ===");
long startTime, endTime;
// 直接加载所有图片
System.out.println("\n1. 直接加载所有图片(传统方式):");
startTime = System.currentTimeMillis();
List<HighResolutionImage> directImages = new ArrayList<>();
directImages.add(new HighResolutionImage("photo1.jpg"));
directImages.add(new HighResolutionImage("photo2.jpg"));
directImages.add(new HighResolutionImage("photo3.jpg"));
directImages.add(new HighResolutionImage("photo4.jpg"));
directImages.add(new HighResolutionImage("photo5.jpg"));
endTime = System.currentTimeMillis();
System.out.println("启动时间: " + (endTime - startTime) + "ms");
System.out.println("内存占用: 高(所有图片已加载)");
// 使用代理延迟加载
System.out.println("\n2. 使用代理延迟加载:");
startTime = System.currentTimeMillis();
ImageViewer proxyViewer = new ImageViewer();
proxyViewer.addImage(new ImageProxy("photo1.jpg"));
proxyViewer.addImage(new ImageProxy("photo2.jpg"));
proxyViewer.addImage(new ImageProxy("photo3.jpg"));
proxyViewer.addImage(new ImageProxy("photo4.jpg"));
proxyViewer.addImage(new ImageProxy("photo5.jpg"));
endTime = System.currentTimeMillis();
System.out.println("启动时间: " + (endTime - startTime) + "ms");
System.out.println("内存占用: 低(只有代理对象)");
// 模拟用户行为:只查看部分图片
System.out.println("\n模拟用户行为(只查看2张图片):");
proxyViewer.displayImage(0);
proxyViewer.displayImage(2);
System.out.println("实际加载图片数: 2张(节省了3张图片的加载)");
}
}
示例2:保护代理 - 文档访问控制系统
java
import java.util.*;
// 1. 主题接口:文档
interface Document {
void view();
void edit();
void delete();
String getContent();
String getTitle();
}
// 2. 真实主题:敏感文档
class SensitiveDocument implements Document {
private String title;
private String content;
private String classification; // 机密等级
public SensitiveDocument(String title, String content, String classification) {
this.title = title;
this.content = content;
this.classification = classification;
}
@Override
public void view() {
System.out.println("查看文档: " + title);
System.out.println("内容: " + content);
}
@Override
public void edit() {
System.out.println("编辑文档: " + title);
// 实际编辑操作
}
@Override
public void delete() {
System.out.println("删除文档: " + title);
// 实际删除操作
}
@Override
public String getContent() {
return content;
}
@Override
public String getTitle() {
return title;
}
public String getClassification() {
return classification;
}
}
// 3. 用户类
class User {
private String username;
private String role; // 角色:admin, editor, viewer
private List<String> permissions;
public User(String username, String role) {
this.username = username;
this.role = role;
this.permissions = new ArrayList<>();
// 根据角色分配权限
switch(role) {
case "admin":
permissions.add("view");
permissions.add("edit");
permissions.add("delete");
break;
case "editor":
permissions.add("view");
permissions.add("edit");
break;
case "viewer":
permissions.add("view");
break;
}
}
public boolean hasPermission(String permission) {
return permissions.contains(permission);
}
public String getUsername() { return username; }
public String getRole() { return role; }
}
// 4. 保护代理:文档访问控制代理
class DocumentAccessProxy implements Document {
private SensitiveDocument realDocument;
private User user;
public DocumentAccessProxy(SensitiveDocument document, User user) {
this.realDocument = document;
this.user = user;
}
@Override
public void view() {
if (checkAccess("view")) {
System.out.println("用户 '" + user.getUsername() + "' 有查看权限");
realDocument.view();
} else {
System.out.println("访问被拒绝: 用户 '" + user.getUsername() +
"' 没有查看文档 '" + realDocument.getTitle() + "' 的权限");
}
}
@Override
public void edit() {
if (checkAccess("edit")) {
System.out.println("用户 '" + user.getUsername() + "' 有编辑权限");
realDocument.edit();
} else {
System.out.println("访问被拒绝: 用户 '" + user.getUsername() +
"' 没有编辑文档 '" + realDocument.getTitle() + "' 的权限");
}
}
@Override
public void delete() {
if (checkAccess("delete")) {
System.out.println("用户 '" + user.getUsername() + "' 有删除权限");
realDocument.delete();
} else {
System.out.println("访问被拒绝: 用户 '" + user.getUsername() +
"' 没有删除文档 '" + realDocument.getTitle() + "' 的权限");
}
}
@Override
public String getContent() {
if (checkAccess("view")) {
return realDocument.getContent();
} else {
return "[访问被拒绝: 您没有查看此文档的权限]";
}
}
@Override
public String getTitle() {
return realDocument.getTitle();
}
private boolean checkAccess(String operation) {
// 检查用户是否有相应权限
boolean hasPermission = user.hasPermission(operation);
// 可以添加更复杂的访问控制逻辑
if (operation.equals("view") && realDocument.getClassification().equals("绝密")) {
// 只有管理员可以查看绝密文档
return user.getRole().equals("admin");
}
return hasPermission;
}
// 代理特有方法:获取访问日志
public void logAccess(String operation) {
System.out.println("日志: 用户 '" + user.getUsername() +
"' 尝试 " + operation + " 文档 '" +
realDocument.getTitle() + "' - " +
new Date());
}
}
// 5. 文档管理系统
class DocumentManagementSystem {
private Map<String, Document> documents = new HashMap<>();
public void addDocument(String id, Document document) {
documents.put(id, document);
}
public Document getDocument(String id, User user) {
Document doc = documents.get(id);
if (doc == null) {
System.out.println("文档不存在: " + id);
return null;
}
// 如果是真实文档,返回保护代理
if (doc instanceof SensitiveDocument) {
return new DocumentAccessProxy((SensitiveDocument) doc, user);
}
return doc;
}
public void listDocuments(User user) {
System.out.println("\n=== 文档列表(用户: " + user.getUsername() + ") ===");
for (Map.Entry<String, Document> entry : documents.entrySet()) {
Document doc = entry.getValue();
// 通过代理访问标题
Document proxy = getDocument(entry.getKey(), user);
System.out.println("ID: " + entry.getKey() +
", 标题: " + proxy.getTitle());
}
}
}
// 6. 主程序
public class ProtectionProxyDemo {
public static void main(String[] args) {
System.out.println("=== 代理模式示例:保护代理(文档访问控制) ===\n");
// 创建用户
User admin = new User("admin_user", "admin");
User editor = new User("editor_user", "editor");
User viewer = new User("viewer_user", "viewer");
System.out.println("创建用户:");
System.out.println("1. " + admin.getUsername() + " (管理员)");
System.out.println("2. " + editor.getUsername() + " (编辑者)");
System.out.println("3. " + viewer.getUsername() + " (查看者)\n");
// 创建文档管理系统
DocumentManagementSystem dms = new DocumentManagementSystem();
// 添加敏感文档
dms.addDocument("doc001", new SensitiveDocument(
"公司财务报告",
"2023年总收入: 1,000万元,利润: 200万元",
"机密"
));
dms.addDocument("doc002", new SensitiveDocument(
"新产品研发计划",
"下一代产品将在2024年Q2发布,预算500万元",
"秘密"
));
dms.addDocument("doc003", new SensitiveDocument(
"战略合作备忘录",
"与ABC公司的合作细节...",
"绝密"
));
dms.addDocument("doc004", new SensitiveDocument(
"员工手册",
"公司规章制度...",
"公开"
));
System.out.println("文档系统已初始化,包含4个文档\n");
// 测试不同用户的访问权限
System.out.println("=".repeat(60));
System.out.println("测试1: 管理员访问文档");
System.out.println("=".repeat(60));
testUserAccess(dms, admin, "doc003");
System.out.println("\n" + "=".repeat(60));
System.out.println("测试2: 编辑者访问文档");
System.out.println("=".repeat(60));
testUserAccess(dms, editor, "doc002");
System.out.println("\n" + "=".repeat(60));
System.out.println("测试3: 查看者访问文档");
System.out.println("=".repeat(60));
testUserAccess(dms, viewer, "doc001");
System.out.println("\n" + "=".repeat(60));
System.out.println("测试4: 查看者尝试编辑文档");
System.out.println("=".repeat(60));
testUserEdit(dms, viewer, "doc004");
System.out.println("\n" + "=".repeat(60));
System.out.println("测试5: 编辑者尝试删除文档");
System.out.println("=".repeat(60));
testUserDelete(dms, editor, "doc001");
System.out.println("\n" + "=".repeat(60));
System.out.println("文档列表视图(不同用户看到的内容)");
System.out.println("=".repeat(60));
System.out.println("\n管理员看到的文档列表:");
dms.listDocuments(admin);
System.out.println("\n查看者看到的文档列表:");
dms.listDocuments(viewer);
// 显示所有文档内容(通过代理)
System.out.println("\n" + "=".repeat(60));
System.out.println("显示所有文档内容(通过代理访问控制)");
System.out.println("=".repeat(60));
User[] users = {admin, editor, viewer};
String[] docIds = {"doc001", "doc002", "doc003", "doc004"};
for (User user : users) {
System.out.println("\n用户: " + user.getUsername() + " (" + user.getRole() + ")");
for (String docId : docIds) {
Document doc = dms.getDocument(docId, user);
if (doc != null) {
System.out.println("文档: " + doc.getTitle());
System.out.println("内容预览: " +
doc.getContent().substring(0, Math.min(30, doc.getContent().length())) + "...");
}
}
}
}
private static void testUserAccess(DocumentManagementSystem dms, User user, String docId) {
Document doc = dms.getDocument(docId, user);
System.out.println("用户: " + user.getUsername() +
" (" + user.getRole() + ") 尝试查看文档 " + docId);
if (doc != null) {
doc.view();
}
}
private static void testUserEdit(DocumentManagementSystem dms, User user, String docId) {
Document doc = dms.getDocument(docId, user);
System.out.println("用户: " + user.getUsername() +
" (" + user.getRole() + ") 尝试编辑文档 " + docId);
if (doc != null) {
doc.edit();
}
}
private static void testUserDelete(DocumentManagementSystem dms, User user, String docId) {
Document doc = dms.getDocument(docId, user);
System.out.println("用户: " + user.getUsername() +
" (" + user.getRole() + ") 尝试删除文档 " + docId);
if (doc != null) {
doc.delete();
}
}
}
示例3:智能代理 - 带缓存和日志的数据库查询
java
import java.util.*;
// 1. 主题接口:数据库查询
interface DatabaseQuery {
List<Map<String, Object>> executeQuery(String sql);
String getLastQuery();
long getLastExecutionTime();
}
// 2. 真实主题:实际数据库查询(开销大)
class RealDatabaseQuery implements DatabaseQuery {
private String lastQuery;
private long lastExecutionTime;
@Override
public List<Map<String, Object>> executeQuery(String sql) {
System.out.println("执行真实数据库查询: " + sql);
lastQuery = sql;
long startTime = System.currentTimeMillis();
// 模拟数据库查询(耗时操作)
try {
Thread.sleep(1000 + (long)(Math.random() * 2000)); // 1-3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟查询结果
List<Map<String, Object>> results = new ArrayList<>();
int rowCount = 5 + (int)(Math.random() * 10); // 5-15行
for (int i = 0; i < rowCount; i++) {
Map<String, Object> row = new HashMap<>();
row.put("id", i + 1);
row.put("name", "用户" + (i + 1));
row.put("age", 20 + (int)(Math.random() * 30));
row.put("email", "user" + (i + 1) + "@example.com");
results.add(row);
}
lastExecutionTime = System.currentTimeMillis() - startTime;
System.out.println("查询完成,返回 " + rowCount + " 行数据,耗时 " + lastExecutionTime + "ms");
return results;
}
@Override
public String getLastQuery() {
return lastQuery;
}
@Override
public long getLastExecutionTime() {
return lastExecutionTime;
}
}
// 3. 智能代理:带缓存、日志和监控的数据库查询代理
class SmartDatabaseQueryProxy implements DatabaseQuery {
private RealDatabaseQuery realQuery;
private Map<String, CacheEntry> queryCache;
private List<QueryLog> queryLogs;
// 缓存条目
static class CacheEntry {
List<Map<String, Object>> results;
long timestamp;
long ttl; // 生存时间(毫秒)
CacheEntry(List<Map<String, Object>> results, long ttl) {
this.results = results;
this.timestamp = System.currentTimeMillis();
this.ttl = ttl;
}
boolean isExpired() {
return System.currentTimeMillis() - timestamp > ttl;
}
}
// 查询日志
static class QueryLog {
String sql;
long timestamp;
long executionTime;
boolean cached;
QueryLog(String sql, long executionTime, boolean cached) {
this.sql = sql;
this.timestamp = System.currentTimeMillis();
this.executionTime = executionTime;
this.cached = cached;
}
}
public SmartDatabaseQueryProxy() {
this.queryCache = new HashMap<>();
this.queryLogs = new ArrayList<>();
}
@Override
public List<Map<String, Object>> executeQuery(String sql) {
long startTime = System.currentTimeMillis();
List<Map<String, Object>> results;
boolean fromCache = false;
// 1. 检查缓存
if (queryCache.containsKey(sql)) {
CacheEntry entry = queryCache.get(sql);
if (!entry.isExpired()) {
System.out.println("从缓存获取查询结果: " + sql);
results = entry.results;
fromCache = true;
// 记录缓存命中
queryLogs.add(new QueryLog(sql, System.currentTimeMillis() - startTime, true));
return results;
} else {
System.out.println("缓存已过期: " + sql);
queryCache.remove(sql);
}
}
// 2. 执行真实查询(延迟初始化)
if (realQuery == null) {
realQuery = new RealDatabaseQuery();
System.out.println("初始化真实数据库连接...");
}
// 3. 执行查询
results = realQuery.executeQuery(sql);
// 4. 缓存结果(TTL = 30秒)
queryCache.put(sql, new CacheEntry(results, 30000));
// 5. 记录日志
long executionTime = System.currentTimeMillis() - startTime;
queryLogs.add(new QueryLog(sql, executionTime, false));
return results;
}
@Override
public String getLastQuery() {
if (realQuery != null) {
return realQuery.getLastQuery();
}
return "No queries executed yet";
}
@Override
public long getLastExecutionTime() {
if (realQuery != null) {
return realQuery.getLastExecutionTime();
}
return 0;
}
// 代理特有方法
public void clearCache() {
System.out.println("清除所有缓存");
queryCache.clear();
}
public void showCacheStats() {
System.out.println("\n=== 缓存统计 ===");
System.out.println("缓存条目数: " + queryCache.size());
int expiredCount = 0;
for (CacheEntry entry : queryCache.values()) {
if (entry.isExpired()) {
expiredCount++;
}
}
System.out.println("已过期条目: " + expiredCount);
// 显示缓存内容
if (!queryCache.isEmpty()) {
System.out.println("\n缓存内容:");
for (Map.Entry<String, CacheEntry> entry : queryCache.entrySet()) {
String sql = entry.getKey();
CacheEntry cacheEntry = entry.getValue();
long age = System.currentTimeMillis() - cacheEntry.timestamp;
System.out.println("SQL: " + sql.substring(0, Math.min(30, sql.length())) + "...");
System.out.println(" 缓存时间: " + age + "ms 前");
System.out.println(" 结果数: " + cacheEntry.results.size());
System.out.println(" 是否过期: " + (cacheEntry.isExpired() ? "是" : "否"));
}
}
}
public void showQueryLogs() {
System.out.println("\n=== 查询日志 ===");
System.out.println("总查询次数: " + queryLogs.size());
long totalTime = 0;
int cacheHits = 0;
for (QueryLog log : queryLogs) {
totalTime += log.executionTime;
if (log.cached) {
cacheHits++;
}
}
System.out.println("缓存命中率: " +
(queryLogs.size() > 0 ? (cacheHits * 100 / queryLogs.size()) : 0) + "%");
System.out.println("平均查询时间: " +
(queryLogs.size() > 0 ? (totalTime / queryLogs.size()) : 0) + "ms");
// 显示最近日志
System.out.println("\n最近查询记录:");
int limit = Math.min(5, queryLogs.size());
for (int i = Math.max(0, queryLogs.size() - limit); i < queryLogs.size(); i++) {
QueryLog log = queryLogs.get(i);
System.out.println("SQL: " + log.sql.substring(0, Math.min(30, log.sql.length())) + "...");
System.out.println(" 时间: " + new Date(log.timestamp));
System.out.println(" 耗时: " + log.executionTime + "ms");
System.out.println(" 来源: " + (log.cached ? "缓存" : "数据库"));
}
}
public void setCacheTTL(String sql, long ttl) {
if (queryCache.containsKey(sql)) {
queryCache.get(sql).ttl = ttl;
System.out.println("更新缓存TTL: " + sql + " -> " + ttl + "ms");
}
}
}
// 4. 客户端:应用程序
public class SmartProxyDemo {
public static void main(String[] args) {
System.out.println("=== 代理模式示例:智能代理(数据库查询缓存) ===\n");
// 创建智能代理
SmartDatabaseQueryProxy dbProxy = new SmartDatabaseQueryProxy();
System.out.println("初始化应用程序...");
System.out.println("使用智能数据库查询代理(带缓存功能)\n");
// 模拟应用程序查询
System.out.println("=".repeat(60));
System.out.println("模拟用户查询操作");
System.out.println("=".repeat(60));
// 第一次查询(从数据库)
System.out.println("\n查询1: 获取所有用户");
List<Map<String, Object>> results1 = dbProxy.executeQuery(
"SELECT * FROM users WHERE status = 'active'"
);
displayResults(results1);
System.out.println("\n" + "-".repeat(40));
// 第二次相同查询(应该从缓存)
System.out.println("\n查询2: 再次获取所有用户(应该从缓存)");
List<Map<String, Object>> results2 = dbProxy.executeQuery(
"SELECT * FROM users WHERE status = 'active'"
);
displayResults(results2);
System.out.println("\n" + "-".repeat(40));
// 第三次不同查询
System.out.println("\n查询3: 获取年轻用户");
List<Map<String, Object>> results3 = dbProxy.executeQuery(
"SELECT * FROM users WHERE age < 30"
);
displayResults(results3);
System.out.println("\n" + "-".repeat(40));
// 第四次查询(再次查询年轻用户,应该从缓存)
System.out.println("\n查询4: 再次获取年轻用户(应该从缓存)");
List<Map<String, Object>> results4 = dbProxy.executeQuery(
"SELECT * FROM users WHERE age < 30"
);
displayResults(results4);
System.out.println("\n" + "-".repeat(40));
// 第五次查询(新的查询)
System.out.println("\n查询5: 获取管理员用户");
List<Map<String, Object>> results5 = dbProxy.executeQuery(
"SELECT * FROM users WHERE role = 'admin'"
);
displayResults(results5);
System.out.println("\n" + "=".repeat(60));
System.out.println("显示缓存和日志统计");
System.out.println("=".repeat(60));
// 显示缓存统计
dbProxy.showCacheStats();
// 显示查询日志
dbProxy.showQueryLogs();
System.out.println("\n" + "=".repeat(60));
System.out.println("模拟缓存过期");
System.out.println("=".repeat(60));
// 模拟等待缓存过期
System.out.println("\n等待缓存过期(模拟5秒等待)...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改缓存TTL
dbProxy.setCacheTTL("SELECT * FROM users WHERE status = 'active'", 5000);
// 再次查询(部分可能已过期)
System.out.println("\n查询6: 再次获取所有用户(部分可能从缓存,部分从数据库)");
List<Map<String, Object>> results6 = dbProxy.executeQuery(
"SELECT * FROM users WHERE status = 'active'"
);
displayResults(results6);
System.out.println("\n" + "=".repeat(60));
System.out.println("清理缓存");
System.out.println("=".repeat(60));
// 清理缓存
dbProxy.clearCache();
dbProxy.showCacheStats();
System.out.println("\n" + "=".repeat(60));
System.out.println("最终性能对比");
System.out.println("=".repeat(60));
// 性能对比
System.out.println("\n对比:使用代理 vs 直接查询");
// 直接查询(无缓存)
System.out.println("\n1. 直接查询(无缓存):");
RealDatabaseQuery directQuery = new RealDatabaseQuery();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 3; i++) {
directQuery.executeQuery("SELECT * FROM products WHERE category = 'electronics'");
}
long directTime = System.currentTimeMillis() - startTime;
System.out.println("3次查询总耗时: " + directTime + "ms");
// 使用代理(有缓存)
System.out.println("\n2. 使用智能代理(有缓存):");
SmartDatabaseQueryProxy proxy = new SmartDatabaseQueryProxy();
startTime = System.currentTimeMillis();
for (int i = 0; i < 3; i++) {
proxy.executeQuery("SELECT * FROM products WHERE category = 'electronics'");
}
long proxyTime = System.currentTimeMillis() - startTime;
System.out.println("3次查询总耗时: " + proxyTime + "ms");
System.out.println("\n性能提升: " + (directTime - proxyTime) + "ms (" +
(directTime > 0 ? (100 - (proxyTime * 100 / directTime)) : 0) + "%)");
}
private static void displayResults(List<Map<String, Object>> results) {
if (results == null || results.isEmpty()) {
System.out.println("无结果");
return;
}
System.out.println("返回 " + results.size() + " 行数据:");
int count = 0;
for (Map<String, Object> row : results) {
if (count++ < 3) { // 只显示前3行
System.out.println(" 行" + count + ": " + row);
}
}
if (results.size() > 3) {
System.out.println(" ... 还有 " + (results.size() - 3) + " 行");
}
}
}
代理模式的优缺点
优点:
- 控制访问:可以控制客户端对目标对象的访问
- 延迟初始化:虚拟代理可以推迟创建开销大的对象
- 增强功能:可以在不修改目标对象的情况下添加额外功能
- 开闭原则:新功能通过新代理实现,不修改现有代码
- 远程访问:远程代理可以隐藏对象在网络中的细节
缺点:
- 增加复杂性:引入代理层增加系统复杂性
- 性能开销:代理调用可能增加响应时间
- 可能过度使用:不是所有情况都需要代理
代理模式 vs 其他模式
| 模式 | 区别 |
|---|---|
| 代理 vs 装饰器 | 代理控制访问,装饰器增强功能 |
| 代理 vs 适配器 | 代理不改变接口,适配器改变接口 |
| 代理 vs 外观 | 代理代表单个对象,外观代表整个子系统 |
| 代理 vs 桥接 | 代理控制访问,桥接分离抽象和实现 |
实际应用场景
- Spring AOP:基于代理实现面向切面编程
- Hibernate延迟加载:虚拟代理实现关联对象的延迟加载
- Java RMI:远程方法调用使用远程代理
- Spring Security:保护代理实现方法级安全控制
- MyBatis缓存:缓存代理减少数据库访问
代理模式实现方式
- 静态代理:手动创建代理类(如上面示例)
- 动态代理 :运行时动态创建代理(Java的
Proxy类) - CGLIB代理:通过继承实现代理(不需要接口)
java
// 动态代理示例
interface Service {
void serve();
}
class RealService implements Service {
public void serve() {
System.out.println("真实服务");
}
}
class DynamicProxyDemo {
public static void main(String[] args) {
Service realService = new RealService();
Service proxy = (Service) java.lang.reflect.Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class[]{Service.class},
(proxyObj, method, methodArgs) -> {
System.out.println("动态代理: 调用前");
Object result = method.invoke(realService, methodArgs);
System.out.println("动态代理: 调用后");
return result;
}
);
proxy.serve();
}
}
代理模式的核心价值在于:通过代理对象控制对目标对象的访问,实现访问控制、延迟初始化、缓存、日志等功能,而不需要修改目标对象本身。
愿我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!