模式组合应用-桥接模式(一)

写在前面

Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!
文章为设计模式间的组合使用,涉及代码较多,个人觉得熟能生巧,希望自己能从中学习到新的思维、新的灵感,希望大家也能有所收获。


模式回顾

定义

桥接模式是一种结构型涉及模式,它旨在将抽象部分与其实现部分分离,使得它们可以独立的变化。 桥接模式通过组合而非继承的方式,将抽象和实现解耦,从而解决了传统继承在多维度变化时导致的类爆炸问题。

角色

  • 抽象化(Abstraction): 定义抽象类的接口,并维护一个指向实现化对象的引用。通常是一个抽象类,持有 实现化的引用。
  • 扩展抽象化(Refined Abstraction): 是抽象化角色的具体实现,它扩展了抽象化接口,并可以调用实现化对象的具体方法。
  • 实现化(Implementor): 定义实现类的接口,这个接口不一定要与抽象化接口完全一致,它只给出实现化角色的基本操作。通常是一个接口。
  • 具体实现化(Concrete Implementor): 是实现化角色的具体实现,实现了接口定义的操作。

思考方向

  1. 是否存在多维度变化: 如果一个类存在两个或多个独立变化的维度,并且这些维度都需要独立扩展。
  2. 避免类爆炸: 当使用继承会导致类的数量急剧增加时,桥接模式可以有效的减少类的数量,简化系统结构。
  3. 抽象与实现是都需要独立演化: 如果抽象和实现需要独立的进行修改、扩展或重用。
  4. 运行时切换实现: 桥接模式允许在运行时动态的切换实现,因为抽象和实现是通过组合而不是继承来关联的。

桥接模式代码练习

案例

假设需要开发一个消息通知模块,需满足两个基本要求,第一个要求包含多种发送方式(如: 短信、邮件、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 的引用
  • 扩展抽象部分: SimpleProductViewDeatilProductView 等具体商品视图
  • 实现者接口: ProductRenderer 定义商品数据渲染的接口
  • 具体实现者: HtmlProductRendererJsonProductRenderer 具体渲染器。
策略模式
  • 抽象策略: PriceCalculationStrategy 定义价格计算策略的接口
  • 具体策略: OriginalPriceStrategyDiscountPriceStrategyMemberPricesStrategy 具体价格计算策略。
  • 上下文: 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
  • 风格的统一性: 每个工厂确保创建的对象属于同一个主题

--

未完,下一篇接着写,代码量比较多。

相关推荐
两码事1 小时前
告别繁琐的飞书表格API调用,让飞书表格操作像操作Java对象一样简单!
java·后端
shark_chili1 小时前
面试官再问synchronized底层原理,这样回答让他眼前一亮!
后端
灵魂猎手2 小时前
2. MyBatis 参数处理机制:从 execute 方法到参数流转全解析
java·后端·源码
饕餮争锋2 小时前
设计模式笔记_行为型_策略模式
笔记·设计模式·策略模式
柑木2 小时前
隐私计算-SecretFlow/SCQL-SCQL的两种部署模式
后端·安全·数据分析
是2的10次方啊2 小时前
🕺 行为型设计模式:对象协作的舞蹈家(中)
设计模式
灵魂猎手2 小时前
1. Mybatis Mapper动态代理创建&实现
java·后端·源码
泉城老铁2 小时前
在秒杀场景中,如何通过动态调整线程池参数来应对流量突增
后端·架构
小悲伤2 小时前
金蝶eas-dep反写上游单据
后端