写在前面
Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!
文章为设计模式间的组合使用,涉及代码较多,个人觉得熟能生巧,希望自己能从中学习到新的思维、新的灵感,希望大家也能有所收获。
模式回顾
定义
桥接模式是一种结构型涉及模式,它旨在将抽象部分与其实现部分分离,使得它们可以独立的变化。 桥接模式通过组合而非继承的方式,将抽象和实现解耦,从而解决了传统继承在多维度变化时导致的类爆炸问题。
角色
- 抽象化(Abstraction): 定义抽象类的接口,并维护一个指向实现化对象的引用。通常是一个抽象类,持有 实现化的引用。
- 扩展抽象化(Refined Abstraction): 是抽象化角色的具体实现,它扩展了抽象化接口,并可以调用实现化对象的具体方法。
- 实现化(Implementor): 定义实现类的接口,这个接口不一定要与抽象化接口完全一致,它只给出实现化角色的基本操作。通常是一个接口。
- 具体实现化(Concrete Implementor): 是实现化角色的具体实现,实现了接口定义的操作。
思考方向
- 是否存在多维度变化: 如果一个类存在两个或多个独立变化的维度,并且这些维度都需要独立扩展。
- 避免类爆炸: 当使用继承会导致类的数量急剧增加时,桥接模式可以有效的减少类的数量,简化系统结构。
- 抽象与实现是都需要独立演化: 如果抽象和实现需要独立的进行修改、扩展或重用。
- 运行时切换实现: 桥接模式允许在运行时动态的切换实现,因为抽象和实现是通过组合而不是继承来关联的。
桥接模式代码练习
案例
假设需要开发一个消息通知模块,需满足两个基本要求,第一个要求包含多种发送方式(如: 短信、邮件、APP内部推送),第二个要求是需要区分消息的类型(不同的类型展现形式也不同,如:紧急通知,普通通知、错误消息等)。
案例分析
以桥接模式为代码开发的主要框架,以消息的类型为抽象部分,发送的方式为实现部分,使得两个维度的变化相对独立。
代码
实现部分接口类
arduino
public interface MessageSender {
void send(String message);
}
MessageSender
为消息发送接口类,定义实现部分的基本功能,包含一个send()
方法。
具体实现类
typescript
/**
* 短信发送
*/
public class SmsSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("通过短信发送: " + message);
}
}
/**
* 邮件发送
*/
public class EmailSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("通过邮件发送: " + message);
}
}
/**
* APP推送
*/
public class AppPushSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("通过APP推送: " + message);
}
}
SmsSender
EmailSender
AppPushSender
实现部分的具体实现类,实现了send()
接口方法,并编写各自发送方式。
抽象部分类
csharp
public abstract class AbstractMessage {
protected MessageSender messageSender;
protected AbstractMessage(MessageSender messageSender) {
this.messageSender = messageSender;
}
public abstract void send(String message);
}
AbstractMessage
为抽象部分,抽象类中包含实现部分对象的引用messageSender
,并定义了一个send()
方法,用于描述消息内容的类型。
抽象部分扩展类
java
/**
* 错误消息
*/
public class ErrorMessage extends AbstractMessage {
protected ErrorMessage(MessageSender messageSender) {
super(messageSender);
}
@Override
public void send(String message) {
messageSender.send("[错误]" + message + " 系统需要检查!");
}
}
/**
* 普通消息
*/
public class NormalMessage extends AbstractMessage {
protected NormalMessage(MessageSender messageSender) {
super(messageSender);
}
@Override
public void send(String message) {
messageSender.send("[普通]" + message);
}
}
/**
* 紧急消息
*/
public class UrgentMessage extends AbstractMessage {
protected UrgentMessage(MessageSender messageSender) {
super(messageSender);
}
@Override
public void send(String message) {
System.out.println("[紧急]" + message + "请立即处理!");
}
}
ErrorMessage
NormalMessage
UrgentMessage
为抽象部分的扩展类,继承了抽象部分类,并对抽象方法进行实现,定义各自消息类型的格式。
测试类
ini
@Test
public void test_send() {
MessageSender emailSend = new EmailSender();
MessageSender smsSend = new SmsSender();
MessageSender appPush = new AppPushSender();
AbstractMessage normalEmail = new NormalMessage(emailSend);
AbstractMessage urgentSms = new UrgentMessage(smsSend);
AbstractMessage errorAppPush = new ErrorMessage(appPush);
normalEmail.send("系统升级通知");
urgentSms.send("服务器负载过高");
errorAppPush.send("数据库连接失败");
AbstractMessage normalSms = new NormalMessage(smsSend);
normalSms.send("每日报告已生成");
}
运行结果
less
通过邮件发送: [普通]系统升级通知
[紧急]服务器负载过高请立即处理!
通过APP推送: [错误]数据库连接失败 系统需要检查!
通过短信发送: [普通]每日报告已生成
Process finished with exit code 0
以上为桥接模式代码应用的复习,加深印象。
桥接模式+策略模式
策略模式: 定义一系列的算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法独立于使用它的客而变化。
案例
在一个电商平台中,商品详情页需要支持多种展示方式(例如: 简洁视图、详细视图、画廊视图),同时为了应对不同的促销活动,商品价格 的计算方式页需要灵活切换(例如: 原价、打折价、会员价)。
模式职责
- 桥接模式: 将商品展示方式(抽象部分)与具体的商品数据渲染(实现部分)解耦。
- 策略模式: 封装不同的价格计算算法,使它们可以相互替换,而不会影响客户端。
具体设计
桥接模式
- 抽象部分:
ProductView
定义商品视图的抽象类,持有对ProductRenderer
的引用 - 扩展抽象部分:
SimpleProductView
、DeatilProductView
等具体商品视图 - 实现者接口:
ProductRenderer
定义商品数据渲染的接口 - 具体实现者:
HtmlProductRenderer
、JsonProductRenderer
具体渲染器。
策略模式
- 抽象策略:
PriceCalculationStrategy
定义价格计算策略的接口 - 具体策略:
OriginalPriceStrategy
、DiscountPriceStrategy
、MemberPricesStrategy
具体价格计算策略。 - 上下文:
Product
持有对PriceCalculationStrategy
的引用,并提供设置策略的方法。
代码
ProductRenderer 接口
arduino
/**
* 产品修饰类
* <p>
* 桥接模式-接口部分
*/
public interface ProductRenderer {
String render(String productData);
}
- 桥接模式中的 实现部分接口,定义了渲染商品数据的通用接口。
render(String productData)
方法接受商品数据并返回渲染后的商品介绍。
实现部分具体实现者
typescript
/**
* Html 商品渲染
* <p>
* 桥接模式 - 具体实现者
*/
public class HtmlProductRenderer implements ProductRenderer {
@Override
public String render(String productData) {
return "<html><body><h1>" + productData + "</h1></body></html>";
}
}
/**
* Json 商品渲染
*/
public class JsonProductRenderer implements ProductRenderer {
@Override
public String render(String productData) {
return "{"product":"" + productData + ""}";
}
}
- 桥接模式中的 具体实现者,分别实现了将商品数据渲染成HTML格式和JSON格式的逻辑。
ProductView 抽象类
csharp
public abstract class ProductView {
protected ProductRenderer productRenderer;
public ProductView(ProductRenderer productRenderer) {
this.productRenderer = productRenderer;
}
public void setProductRenderer(ProductRenderer productRenderer) {
this.productRenderer = productRenderer;
}
public abstract void display(String productData);
}
- 桥接模式中的抽象部分,维护一个
ProductRenderer
类型的引用,作为连接抽象和实现 的桥梁。 setProductRenderer()
方法允许在运行时,动态改变渲染器。display(String productData)
抽象方法,由具体的视图类型实现其展示逻辑。
抽象部分扩展类
java
/**
* 详细商品视图
* <p>
* 桥接模式-扩展抽象部分
*/
public class DetailProductView extends ProductView {
public DetailProductView(ProductRenderer productRenderer) {
super(productRenderer);
}
@Override
public void display(String productData) {
System.out.println("Detail View: " + productRenderer.render("Detailed Product Info: " + productData));
}
}
/**
* 简单视图扩展
* <p>
* 桥接模式-扩展抽象部分
*/
public class SimpleProductView extends ProductView {
public SimpleProductView(ProductRenderer productRenderer) {
super(productRenderer);
}
@Override
public void display(String productData) {
System.out.println("Simple View: " + productRenderer.render(productData));
}
}
- 桥接模式中的抽象扩展部分,代表了不同复杂度的商品视图。
- 通过调用
productRenderer.render()
方法,将实际渲染工作委托给了具体ProductRenderer
实现类。
PriceCalculationStrategy(策略抽象类)
csharp
/**
* 价格计算策略接口
* <p>
* 策略模式-抽象策略
*/
public interface PriceCalculationStrategy {
double calculatePrice(double originalPrice);
}
- 定义了价格计算的通用接口,
calculatePrice(double originalPrice)
方法接受原始价格并返回计算后的价格。
策略实现类
csharp
/**
* 原始价格策略
* <p>
* 策略模式-具体策略
*/
public class OriginalPriceStrategy implements PriceCalculationStrategy {
@Override
public double calculatePrice(double originalPrice) {
System.out.println(" ===原始价格=== ");
return originalPrice;
}
}
/**
* 会员价格策略
* <p>
* 策略模式-具体策略
*/
public class MemberPriceStrategy implements PriceCalculationStrategy {
/**
* 会员折扣价
*/
private double memberDiscount;
public MemberPriceStrategy(double memberDiscount) {
this.memberDiscount = memberDiscount;
}
@Override
public double calculatePrice(double originalPrice) {
System.out.println("使用会员折扣,折扣价格: " + memberDiscount);
return originalPrice - memberDiscount;
}
}
/**
* 折扣价格策略
* <p>
* 策略模式-具体策略
*/
public class DiscountPriceStrategy implements PriceCalculationStrategy {
/**
* 折扣率
*/
private double discountRate;
public DiscountPriceStrategy(double discountRate) {
this.discountRate = discountRate;
}
@Override
public double calculatePrice(double originalPrice) {
System.out.println("使用折扣计算折扣价格, 折扣率: " + discountRate);
return originalPrice * (1 - discountRate);
}
}
- 策略模式中的具体策略类,对
calculatePrice()
方法进行实现,填充了各自的计算逻辑和必须的参数。
Product(上下文)
arduino
public class Product {
private String name;
private double originalPrice;
private PriceCalculationStrategy priceStrategy;
public Product(String name, double originalPrice, PriceCalculationStrategy priceStrategy) {
this.name = name;
this.originalPrice = originalPrice;
this.priceStrategy = priceStrategy;
}
public void setPriceStrategy(PriceCalculationStrategy priceStrategy) {
this.priceStrategy = priceStrategy;
}
public String getName() {
return name;
}
public double getCalculatedPrice() {
return priceStrategy.calculatePrice(originalPrice);
}
}
- 策略模式中的上下文,持有一个
PriceCalculationStrategy
类型的引用, setPriceStrategy()
方法允许在运行时动态设置价格计算策略。getCalculatedPrice()
方法 将价格计算的职责委托给当前设置的PriceCalculationStrategy
实现类。
测试类
java
@Test
public void test_bridge_strategy() {
Product product = new Product("Smart Watch", 300.0, new OriginalPriceStrategy());
SimpleProductView productViewWithBridge = new SimpleProductView(new HtmlProductRenderer());
productViewWithBridge.display(product.getName() + " - Original Price: " + product.getCalculatedPrice());
product.setPriceStrategy(new DiscountPriceStrategy(0.2));
productViewWithBridge.setProductRenderer(new JsonProductRenderer());
productViewWithBridge.display(product.getName() + " - Discounted Price: " + product.getCalculatedPrice());
System.out.println("\n 应用会员价格策略和HTML渲染器: ");
product.setPriceStrategy(new MemberPriceStrategy(25.0));
productViewWithBridge.setProductRenderer(new HtmlProductRenderer());
productViewWithBridge.display(product.getName() + "- Member Price: " + product.getCalculatedPrice());
}
运行结果
javascript
===原始价格===
Simple View: <html><body><h1>Smart Watch - Original Price: 300.0</h1></body></html>
使用折扣计算折扣价格, 折扣率: 0.2
Simple View: {"product":"Smart Watch - Discounted Price: 240.0"}
应用会员价格策略和HTML渲染器:
使用会员折扣,折扣价格: 25.0
Simple View: <html><body><h1>Smart Watch- Member Price: 275.0</h1></body></html>
Process finished with exit code 0
组合优势
- 独立变化: 桥接模式使得商品视图和数据渲染可以独立变化,策略模式使得价格计算算法可以独立变化,两种设计模式的结合,使得系统在多个维度上都具有高度的灵活性和可扩展性。
- 高度复用: 渲染器和价格策略都可以被不同的商品视图和商品对象复用。
- 增强扩展性: 增加新的商品视图类型、新的数据渲染方式或新的价格计算策略,都只需要添加新的类,无需修改现有代码。
桥接模式+抽象工厂模式
提供一个接口,用于创建相关或依赖对象的家族,而无需指定它们的具体类。
案例
ERP系统报表功能需求:
- 报表类型支持:销售报表 库存报表
- 导出格式支持:PDF Excel
- 界面主题选项:经典主题 现代主题
模式职责
- 桥接模式: 将报表生成逻辑与导出格式实现解耦,使得二者能够独立变化。
- 抽象工厂模式: 创建主题相关的报表和导出格式对象族(经典主题/现代主题)
具体设计
桥接模式
- 抽象部分:
Peport
定义报告的结构,并持有一个ExportFormat
的引用 - 扩展抽象部分:
InventoryReport
SalesReport
分别生成库存报表、销售报表内容 - 实现部分:
ExportFormat
定义export()
方法 - 具体实现:
PDFExport
ExcelExport
分别提供基础 PDF 、Excel 导出逻辑
抽象工厂模式
- 抽象工厂:
ThemeFactory
接口,定义了创建产品族的一系列方法 - 具体工厂:
ClassicThemeFactory
ModernThemeFactory
分别创建经典主题风格 和 现代主题风格的对象 - 抽象产品:
Peport
- 具体产品:
SalesReport
InventoryReport
PDFExport
ExcelExport
代码
ExportFormat
arduino
/**
* 导出格式
* <p>
* 桥接模式-实现接口部分
*/
public interface ExportFormat {
String export(String content);
}
- 桥接模式中的实现者接口,定义了报表导出的方法,
export(String content)
方法 接受原始内容,返回添加格式后的内容。
具体实现类
typescript
/**
* PDF导出实现
*/
public class PDFExport implements ExportFormat {
@Override
public String export(String content) {
return "生成PDF文档: " + content;
}
}
/**
* Excel 导出实现
*/
public class ExcelExport implements ExportFormat {
@Override
public String export(String content) {
return "生成Excel文件: " + content;
}
}
- 桥接模式中的具体实现者,分别将接受的内容导出为 PDF 和 Excel
Report
typescript
/**
* 报表
* <p>
* 桥接模式-抽象部分
*/
public abstract class Report {
protected String title;
protected List<String> data;
protected ExportFormat exportFormat;
public Report(ExportFormat exportFormat) {
this.exportFormat = exportFormat;
}
public void setTitle(String title) {
this.title = title;
}
public void setData(List<String> data) {
this.data = data;
}
public abstract String generate();
public String export() {
return exportFormat.export(generate());
}
}
- 桥接模式中的抽象部分,维护一个
ExportFormat
类型的引用。 generate()
方法用于获取导出的数据内容export()
方法委托ExportFormat
实现类 增加导出的格式内容。
抽象部分扩展类
java
/**
* 库存报表
*/
public class InventoryReport extends Report {
public InventoryReport(ExportFormat exportFormat) {
super(exportFormat);
this.title = "库存报表";
}
@Override
public String generate() {
StringBuilder content = new StringBuilder(title + ":\n");
for (String item : data) {
content.append(" * ").append(item).append("\n");
}
return content.toString();
}
}
/**
* 销售报表
*/
public class SalesReport extends Report {
public SalesReport(ExportFormat exportFormat) {
super(exportFormat);
this.title = "销售报表";
}
@Override
public String generate() {
StringBuilder content = new StringBuilder(title + ":\n");
for (String item : data) {
content.append(" - ").append(item).append("\n");
}
return content.toString();
}
}
- 桥接模式中的抽象部分的扩展,分别用于获取库存 、销售 报表数据。
ThemeFactory(抽象工厂类)
csharp
/**
* 主题对象
* <p>
* 抽象工厂
*/
public interface ThemeFactory {
Report createSalesReport();
ExportFormat createPDFExport();
Report createInventorReport();
ExportFormat createExcelExport();
}
- 抽象工厂模式中的抽象工厂接口,声明创建相关产品对象的接口。
具体工厂类
typescript
/**
* 经典主题风格
* <p>
* 具体工厂
*/
public class ClassicThemeFactory implements ThemeFactory {
@Override
public Report createSalesReport() {
return new SalesReport(createPDFExport());
}
@Override
public ExportFormat createPDFExport() {
return new PDFExport() {
@Override
public String export(String content) {
return "经典主题 - " + super.export(content) + " (带传统边框)";
}
};
}
@Override
public Report createInventorReport() {
return new InventoryReport(createExcelExport());
}
@Override
public ExportFormat createExcelExport() {
return new ExcelExport() {
@Override
public String export(String content) {
return "经典主题 - " + super.export(content) + " (使用传统颜色)";
}
};
}
}
/**
* 现代主题风格
* <p>
* 具体工厂
*/
public class ModernThemeFactory implements ThemeFactory {
@Override
public Report createSalesReport() {
return new SalesReport(createPDFExport());
}
@Override
public ExportFormat createPDFExport() {
return new PDFExport() {
@Override
public String export(String content) {
return "现代主题 - " + super.export(content) + " (简约设计)";
}
};
}
@Override
public Report createInventorReport() {
return new InventoryReport(createExcelExport());
}
@Override
public ExportFormat createExcelExport() {
return new ExcelExport() {
@Override
public String export(String content) {
return "现代主题 - " + super.export(content) + " (扁平化风格)";
}
};
}
}
- 抽象工厂模式中的具体工厂类,分别用于创建经典主题和现代主题风格的导出格式对象族,遵循抽象工厂模式,创建统一风格的主题产品。
测试类
ini
@Test
public void test_report() {
List<String> salesData = Arrays.asList("产品A: 100件", "产品B: 200件", "产品C: 150件");
List<String> inventoryData = Arrays.asList("仓库1: 500件", "仓库2: 300件", "仓库3: 450件");
System.out.println(" === ERP系统报表模块 ===");
System.out.println();
// 使用经典主题
System.out.println("【经典主题报表】");
ThemeFactory classicFactory = new ClassicThemeFactory();
Report classicFactorySalesReport = classicFactory.createSalesReport();
classicFactorySalesReport.setData(salesData);
System.out.println(classicFactorySalesReport.export());
System.out.println();
// 使用现代主题
System.out.println("【现代主题报表】");
ThemeFactory modernFactory = new ModernThemeFactory();
// 现代订单报表
Report modernFactorySalesReport = modernFactory.createSalesReport();
modernFactorySalesReport.setData(salesData);
System.out.println(modernFactorySalesReport.export());
// 现代库存报表
Report modernFactoryInventorReport = modernFactory.createInventorReport();
modernFactoryInventorReport.setData(inventoryData);
System.out.println(modernFactoryInventorReport.export());
}
运行结果
less
=== ERP系统报表模块 ===
【经典主题报表】
经典主题 - 生成PDF文档: 销售报表:
- 产品A: 100件
- 产品B: 200件
- 产品C: 150件
(带传统边框)
【现代主题报表】
现代主题 - 生成PDF文档: 销售报表:
- 产品A: 100件
- 产品B: 200件
- 产品C: 150件
(简约设计)
现代主题 - 生成Excel文件: 库存报表:
* 仓库1: 500件
* 仓库2: 300件
* 仓库3: 450件
(扁平化风格)
Process finished with exit code 0
组合优势
- 高度的灵活性: 新增报表类型或导出格式时,可以直接新增对应实现类,无需修改现有代码。新增主图变化扩展
ThemeFactory
,新增报表逻辑扩展Report
,新增导出格式则扩展ExportFormat
。 - 风格的统一性: 每个工厂确保创建的对象属于同一个主题
--
未完,下一篇接着写,代码量比较多。