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

写在前面

Hello,我是易元 ,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!

本文为设计模式间的组合使用,涉及代码较多,个人觉得熟能生巧,希望自己能从中学习到新的思维、新的灵感,希望大家也能有所收获。


接上一篇文章 《模式组合应用-桥接模式(一)》

桥接模式+装饰器模式

装饰器是一种结构型设计模式,允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。

场景

在一个消息通知系统中,我们需要支持多种通知渠道(例如: 邮件、短信、微信),同时每种通知又可能需要附加额外的功能(例如: 加密、压缩、日志记录等)。

各模式职责

  • 桥接模式: 分离通知类型和通知发送方式,将通知类型做为抽象部分,具体通知发送方式做为实现部分。
  • 装饰器模式: 动态地给通知添加额外的功能,无需修改原始结构。

具体结构设计

桥接模式
  • 抽象部分: Notification 定义通知的抽象类,持有对 MessageSender 的引用。
  • 扩展抽象部分: UrgentNotificationGeneralNotification 等具体通知类型。
  • 实现部分接口: MessageSender 定义消息发送的接口。
  • 具体实现者: EmailSenderSmsSenderWeChatSender 等具体消息发送器。
装饰器模式
  • 抽象组件: MessageSender 接口(同时作为桥接模式的实现部分接口)。
  • 具体组件: EmailSenderSmsSenderWeChatSender (同时作为桥接模式的具体实现者)。
  • 抽象装饰器: MessageSenderDecorator 实现了 MessageSender 接口,并持有一个 MessageSender 引用。
  • 具体装饰器: EncrptedSenderDecoratorCompressedSenderDecoratorLoggingSenderDecoratot 等,为消息发送添加特定功能。

代码示例

1. MessageSender
arduino 复制代码
/**
 * 消息发送接口
 * <p>
 * 桥接模式(实现部分接口)
 * 装饰器模式(抽象组件)
 */
public interface MessageSender {
    void send(String message);
}
  • 在桥接模式中,担任实现部分接口,定义了消息发送的通用行为。
  • 在装饰器模式中,充当了 抽象组件 的角色,定义了可以被装饰的对象的基本接口。
  • send(String message) 方法是所有消息发送器必须实现的方法。
2. EmailSender SmsSender WeChatSender
typescript 复制代码
/**
 * 邮件发送器
 * <p>
 * 桥接模式(具体实现者)
 * 装饰器模式(具体组件)
 */
public class EmailSender implements MessageSender {

    @Override
    public void send(String message) {
        System.out.println("发送邮件消息: " + message);
    }
    
}

/**
 * 短信发送器
 * <p>
 * 桥接模式(具体实现者)
 * 装饰器模式(具体组件)
 */
public class SmsSender implements MessageSender {

    @Override
    public void send(String message) {
        System.out.println("发送短信消息: " + message);
    }

}

/**
 * 微信发送器
 * <p>
 * 桥接模式(具体实现者)
 * 装饰器模式(具体组件)
 */
public class WeChatSender implements MessageSender {

    @Override
    public void send(String message) {
        System.out.println("发送微信消息: " + message);
    }

}
  • 在桥接模式中,它们是具体实现者角色,提供了通过不同渠道发送消息的具体实现。
  • 在装饰器模式中,它们是具体组件角色,是装饰器可以包装的原始对象。
3. MessageSenderDecorator
typescript 复制代码
/**
 * 消息发送器装饰类
 * <p>
 * 装饰器模式(抽象装饰器)
 *
 * @author YiYuan
 * @data 2025/8/18
 * @apoNote
 */
public abstract class MessageSenderDecorator implements MessageSender {

    protected MessageSender decoratorSender;

    public MessageSenderDecorator(MessageSender decoratorSender) {
        this.decoratorSender = decoratorSender;
    }

    @Override
    public void send(String message) {
        decoratorSender.send(message);
    }

}
  • 装饰器模式中的 抽象装饰器 ,它实现了 MessageSender 接口,并持有一个 MessageSender 类型的引用(decoratedSender)。
  • send() 方法默认只调用 decoratedSedner.send(),子类可以在此基础上天啊及额外的行为。
4. EncryptedSenderDecorator CompressedSenderDecorator LoggingSenderDecorator
java 复制代码
/**
 * 消息压缩发送器
 * <p>
 * 装饰器模式(具体装饰器)
 *
 * @author YiYuan
 * @data 2025/8/18
 * @apoNote
 */
public class CompressedSenderDecorator extends MessageSenderDecorator {

    public CompressedSenderDecorator(MessageSender decoratorSender) {
        super(decoratorSender);
    }

    @Override
    public void send(String message) {
        String compressedMessage = "[ZIP]" + message + "[/ZIP]";
        System.out.println("消息压缩中 ... ");

        super.send(compressedMessage);
    }
}

/**
 * 消息加密发送器
 * <p>
 * 装饰器模式(具体装饰器)
 *
 * @author YiYuan
 * @data 2025/8/18
 * @apoNote
 */
public class EncryptedSenderDecorator extends MessageSenderDecorator {

    public EncryptedSenderDecorator(MessageSender decoratorSender) {
        super(decoratorSender);
    }

    @Override
    public void send(String message) {

        String encryptedMessage = "[AES]" + message + "[/AES]";

        System.out.println("消息加密中 ... ");

        super.send(encryptedMessage);
    }

}

/**
 * 日志记录器
 * <p>
 * 装饰器模式(具体装饰器)
 *
 * @author YiYuan
 * @data 2025/8/18
 * @apoNote
 */
public class LoggingSenderDecorator extends MessageSenderDecorator {

    public LoggingSenderDecorator(MessageSender decoratorSender) {
        super(decoratorSender);
    }

    @Override
    public void send(String message) {
        System.out.println("记录消息发送日志: " + message);
        super.send(message);
    }

}
  • 装饰器模式中的具体装饰器,继承自 MessageSenderDecorator
  • 每个装饰器都在调用 super.send() 之前或之后添加了特定的功能(加密、压缩、记录日志)
  • 装饰器之间可以任意组合,进而实现多种功能叠加。
5. Notification
java 复制代码
/**
 * 消息通知类型
 * <p>
 * 桥接模式(抽象部分)
 *
 * @author YiYuan
 * @data 2025/8/18
 * @apoNote
 */
public abstract class Notification {

    protected MessageSender sender;

    public Notification(MessageSender sender) {
        this.sender = sender;
    }

    public void setSender(MessageSender sender) {
        this.sender = sender;
    }

    public abstract void notifyUser(String message);
}
  • 桥接模式中的 抽象部分,维护一个 MessageSender 类型的引用,作为通知类型与消息发送方式之间的桥梁。
  • setSender() 方法允许在运行时动态改变通知的发送方式
  • notifyUser() 抽象方法,由具体的通知类型实现其特有的逻辑通知。
6. UrgentNotification GeneralNotification
java 复制代码
/**
 * 紧急消息
 * <p>
 * 桥接模式(抽象扩展部分)
 *
 * @author YiYuan
 * @data 2025/8/18
 * @apoNote
 */
public class UrgentNotification extends Notification {

    public UrgentNotification(MessageSender sender) {
        super(sender);
    }

    @Override
    public void notifyUser(String message) {

        System.out.println("紧急消息: ");
        sender.send("[紧急]" + message);

    }
    
}

/**
 * 普通消息
 * <p>
 * 桥接模式(抽象扩展部分)
 *
 * @author YiYuan
 * @data 2025/8/18
 * @apoNote
 */
public class GeneralNotification extends Notification {

    public GeneralNotification(MessageSender sender) {
        super(sender);
    }

    @Override
    public void notifyUser(String message) {
        System.out.println("普通消息: ");
        sender.send(message);
    }
    
}
  • 桥接模式中的 抽象部分扩展,代表了不同优先级的通知类型。
  • 通过调用 sender.send() 方法,将实际的消息发送工作委托给具体的 MessageSender 实现(这个实现可能已经被一个或多个装饰器包装)
7. 测试类
java 复制代码
public class MessageSenderTest {

    @Test
    public void test_bridge() {

        System.out.println("\n--- 桥接模式 ---");
        MessageSender emailSender = new EmailSender();
        Notification urgentEmail = new UrgentNotification(emailSender);
        urgentEmail.notifyUser("检测到系统出现异常!");

        System.out.println();
        MessageSender smsSender = new SmsSender();
        Notification generalSms = new GeneralNotification(smsSender);
        generalSms.notifyUser("购买商品已发货。");

    }

    @Test
    public void test_decorator() {

        System.out.println("\n--- 装饰器模式 ---");
        MessageSender encryptedEmailSender = new EncryptedSenderDecorator(new EmailSender());
        encryptedEmailSender.send("机密内容");
        System.out.println();

        MessageSender compressedSmsSender = new CompressedSenderDecorator(new SmsSender());
        compressedSmsSender.send("超长文件内容");
        System.out.println();

        MessageSender loggingEncryptedWeChatSender = new LoggingSenderDecorator(new EncryptedSenderDecorator(new WeChatSender()));
        loggingEncryptedWeChatSender.send("多装饰器使用");

    }

    @Test
    public void test_bridge_decorator() {

        System.out.println("\n--- 桥接模式与装饰器模式组合应用 ---");

        MessageSender encryptedEmail = new EncryptedSenderDecorator(new EmailSender());
        Notification urgentEncryptedEmail = new UrgentNotification(encryptedEmail);
        urgentEncryptedEmail.notifyUser("邮件加密发送");
        System.out.println();

        MessageSender loggingCompressedSms = new LoggingSenderDecorator(new CompressedSenderDecorator(new SmsSender()));
        Notification generalLoggingCompressedSms = new GeneralNotification(loggingCompressedSms);
        generalLoggingCompressedSms.notifyUser("短信发送普通超长内容");
        System.out.println();

        System.out.println("\n--- 动态切换通知的发送方式和附加功能 ---");
        Notification dynamicNotification = new GeneralNotification(new EmailSender());
        dynamicNotification.notifyUser("一般消息内容");

        System.out.println("\n切换发送方式");
        dynamicNotification.setSender(new EncryptedSenderDecorator(new SmsSender()));
        dynamicNotification.notifyUser("保密消息内容");

        System.out.println("\n切换发送方式");
        dynamicNotification.setSender(new LoggingSenderDecorator(new WeChatSender()));
        dynamicNotification.notifyUser("二级消息内容");
    }
}
  • test_bridge 展示了如何创建不同类型的通知(UrgentNotification GeneralNotification)并结合不同的发送器(EmailSender SmsSender)
  • test_decorator 展示了如何通过装饰器为 MessageSender 添加功能,例如加密邮件发送器、压缩短信发送器,以及多重装饰(带日志写入的加密微信发送器)
  • test_bridge_decorator 展示了如何将桥接模式中的 MessageSender 接口与装饰器模式结合,意味着通知类型(抽象部分)可以与任何被装饰 过的消息发送器(实现部分)组合。例如,一个紧急通知可以通过一个加密的邮件发送器发送,而普通通知可以通过一个带日志写入和压缩功能的短信发送器发送。

组合优势

  • 功能动态扩展: 装饰器模式允许在运行时动态地为消息发送器添加新的功能(如加密、压缩、日志),而无需修改其核心代码,这与桥接模式的解耦 能力相结合,使得通知系统在功能和渠道两个维度上都具有极高的灵活性。
  • 避免类爆炸: 如果使用继承来添加功能,会导致大量的子类组合,装饰器模式避免了这种类爆炸,使得功能可以独立地添加和组合。
  • 职责清晰: 桥接模式分离了通知类型和发送机制,装饰器模式分离了核心发送功能和附加功能,功能的划分使得系统更易于理解和维护。
  • 高度可配置: 客户端可以根据需要自由组合不同的通知类型、发送渠道和附加功能,创建出高度定制化的通知行为。

桥接模式+模板方法模式

模板方法模式,行为设计模式,在超类中定义一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。

场景

在文档处理系统中,需要支持多种文档类型(例如: 报告、合同、发票),每种文档的生成过程都有固定的步骤(例如: 数据加载、内容格式化、保存), 但具体的格式化方式可能因输出格式(例如: 纯文本、HTML、PDF)而异。

模式职责

  • 桥接模式: 将文档类型(抽象部分) 与具体的文档输出格式(实现部分)解耦
  • 模板方法: 定义文档生成过程的骨架,将具体步骤的实现延迟到子类。

具体设计

桥接模式
  • 抽象部分: Document 定义文档的抽象类,持有对 DocumentFormatter 的引用。
  • 扩展抽象部分: 具体文档类型 ReportDocument ContractDocument InvoiceDocument 等。
  • 实现者接口: DocumentFormantter 定义了文档格式化的接口。
  • 具体实现者: 具体格式化器,包含 PlaninTextFormatter HtmlFormatter PdfFormatter 等。
模板方法模式
  • 抽象类: Document 抽象类(同时作为桥接模式的抽象部分), 定义了 generate() 模板方法,其中包含固定的文档生成步骤(loadData() formatContent() saveDocument())。
  • 具体方法: loadData()saveDocument() 可以是具体方法也可以是钩子方法。
  • 抽象方法/钩子方法: formatContent() 是一个抽象方法, 有子类(ReportDocument ContractDocument InvoiceDocument) 实现, 并利用桥接模式的 DocumentFormatter 来完成具体的格式化。

代码示例

1. DocumentFormatter
arduino 复制代码
/**
 * 文档格式化接口
 * <p>
 * 桥接模式(实现者接口)
 */
public interface DocumentFormatter {

    String format(String content);

}
  • 桥接模式中的 实现者接口,定义了文档内容如何格式化的通用方法,format(String content) 接收原始内容并返回格式化后的字符串。
2. PlainTextFormatter HtmlFormatter PdfFormatter
typescript 复制代码
/**
 * 纯文本格式化类
 * <p>
 * 桥接模式(具体实现者)
 */
public class PlainTextFormatter implements DocumentFormatter {

    @Override
    public String format(String content) {
        return "[纯文本] " + content;
    }

}

/**
 * HTML格式化类
 * <p>
 * 桥接模式(具体实现者)
 */
public class HtmlFormatter implements DocumentFormatter{

    @Override
    public String format(String content) {
        return "<html><body><p>" + content + "</p></body></html>";
    }
}

/**
 * PDF格式化类
 * <p>
 * 桥接模式(具体实现者)
 */
public class PdfFormatter implements DocumentFormatter {

    @Override
    public String format(String content) {
        return "[PDF] " + content;
    }

}
  • 桥接模式中的 具体实现者,分别实现了将文档内容格式化为纯文本、HTML 和 模拟PDF格式的逻辑。
3. Documnet
csharp 复制代码
/**
 * 文档类
 * <p>
 * 桥接模式(抽象部分)/模板方法模式(抽象类)
 */
public abstract class Document {

    protected DocumentFormatter formatter;

    public Document(DocumentFormatter formatter) {
        this.formatter = formatter;
    }

    public void setFormatter(DocumentFormatter formatter) {
        this.formatter = formatter;
    }

    public final void generate() {

        loadData();
        String formatContent = this.formatContent();
        this.saveDocument(formatContent);
        System.out.println("文档生成成功。");

    }

    protected void loadData() {
        System.out.println("加载文档数据...");
    }

    protected abstract String formatContent();

    protected void saveDocument(String content) {
        System.out.println("将内容保存至文档: " + content);
    }
}
  • 桥接模式中的 抽象部分,维护了一个 DocumentFormatter 类型的引用,作为文档类型与格式化器直接的桥梁,setFormatter()允许动态 切换格式化器。
  • 模板方法模式的 抽象类,定义了 generate() 模板方法,该方法封装了文档生成过程的固定骨架: loadData() -> formatContent() -> saveDocument()
  • loadData()saveDocument() 是具体方法,提供了通用的实现。
  • formatContent() 是一个抽象方法,它将具体的格式化逻辑延迟到子类实现。
4. ReportDocument ContractDocument
typescript 复制代码
/**
 * 报告文件
 * <p>
 * 桥接模式(扩展抽象部分)/模板方法模式(具体子类)
 */
public class ReportDocument extends Document {

    private String reportData;

    public ReportDocument(DocumentFormatter formatter, String reportData) {
        super(formatter);
        this.reportData = reportData;
    }

    @Override
    protected String formatContent() {
        System.out.println("--- 格式化报表内容 ---");

        return formatter.format("报告数据: " + reportData);
    }

    @Override
    protected void loadData() {
        super.loadData();
        System.out.println("加载特定报表数据...");
    }
}

/**
 * 合同文件
 * <p>
 * 桥接模式(扩展抽象部分)/模板方法模式(具体子类)
 */
public class ContractDocument extends Document {

    private String contractDetails;

    public ContractDocument(DocumentFormatter formatter, String contractDetails) {
        super(formatter);
        this.contractDetails = contractDetails;
    }

    @Override
    protected String formatContent() {
        System.out.println("--- 格式化合同文件内容 ---");
        return formatter.format(contractDetails);
    }

}
  • 桥接模式中的 扩展抽象部分,代表具体的文档类型。
  • 模板方法模式中的 具体子类,继承自 Dcoument 抽象类,并实现了 formatContent() 抽象方法,在 formatContent() 中,通过调用 formatter.format() 方法,将实际的格式化工作委托给具体的 DocumentFormatter 实现。
  • ReportDocument 中重写了 loadData() 方法,以展示子类可以扩展或修改模板方法中的特定步骤。
5. 测试类
ini 复制代码
    @Test
    public void test_bridge_template() {

        DocumentFormatter pdfFormatter = new PdfFormatter();
        ReportDocument salesReport = new ReportDocument(pdfFormatter, "小米SU7销售报表");
        System.out.println("PDF格式的销售报表: ");
        salesReport.generate();

        System.out.println();
        DocumentFormatter htmlFormatter = new HtmlFormatter();
        ContractDocument serviceContract = new ContractDocument(htmlFormatter, "服务品质协议");
        serviceContract.generate();

        System.out.println();
        System.out.println("--- 切换格式化器 ---");
        salesReport.setFormatter(htmlFormatter);
        System.out.println("HTML格式的销售报表: ");
        salesReport.generate();

    }

组合优势

  • 流程与实现分离: 模板方法模式定义了固定的操作流程,桥接模式则将流程中的可变部分(具体实现) 抽象出来,并允许其独立变化,使得文档生成 的核心流程保持稳定,而具体格式化细节可以灵活切换。
  • 职责明确清晰: 模板方法模式关注 如何执行一系列操作,桥接模式关注 如何将抽象和实现解耦。

桥接模式+工厂方法模式

工厂方法模式,创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

场景

在一个多媒体播放器应用中,我们需要支持多种播放设备(例如: 电视机、音响、投影仪),并且每种设备都可以播放不同类型的媒体(例如: 电影、 音乐、图片)。同时,为了创建这些媒体播放器,我们希望能够根据不同的设备类型,动态地创建响应的媒体播放器实例。

模式职责

  • 桥接模式: 将媒体播放器(抽象部分) 与 具体的播放设备(实现部分) 解耦。
  • 工厂方法模式: 定义了一个用于创建媒体播放器对象的接口,由子类进行实例化。

具体设计

桥接模式
  • 抽象部分: MediaPlayer 定义媒体播放器的抽象类,持有对 PlaybackDevice 的引用。
  • 扩展抽象部分: 具体媒体播放器,MoviePlayer MusicPlayer ImagePlayer
  • 实现者接口: PlaybackDevice 定义了播放设备的接口。
  • 具体实现者: 具体播放设备,包含 Television SpeakerSystem Projector
工厂方法模式
  • 抽象创建者: MediaPlayerFactory 定义创建媒体播放器对象的工厂方法。
  • 具体创建者: TelevisionPlayerFactory SpeakerPlayerFactory ProjectorPlayerFactory,每个工厂负责创建特定设备上的媒体播 放器。
  • 产品: MediaPlayer 抽象类,同时作为桥接模式的抽象部分。

代码示例

1. PlaybackDevice
arduino 复制代码
/**
 * 播放设备
 * <p>
 * 桥接模式(实现者接口)
 */
public interface PlaybackDevice {

    void play(String mediaType, String content);

    void stop();

}
  • 桥接模式中的 实现者接口,定义了播放设备的通用行为,如 play()stop()
2. Television SpeakerSystem Projector
typescript 复制代码
/**
 * 电视机
 * <p>
 * 桥接模式(具体实现者)
 */
public class Television implements PlaybackDevice {

    private String name;

    public Television(String name) {
        this.name = name;
    }

    @Override
    public void play(String mediaType, String content) {
        System.out.println(name + " 电视机播放 " + mediaType + " : " + content);
    }

    @Override
    public void stop() {
        System.out.println(name + " 电视机停止播放.");
    }
}

/**
 * 音响
 * <p>
 * 桥接模式(具体实现者)
 */
public class SpeakerSystem implements PlaybackDevice {

    private String name;

    public SpeakerSystem(String name) {
        this.name = name;
    }

    @Override
    public void play(String mediaType, String content) {
        System.out.println(name + " 音响播放 " + mediaType + ": " + content);
    }

    @Override
    public void stop() {
        System.out.println(name + " 音响停止播放. ");
    }
}

/**
 * 投影仪
 * <p>
 * 桥接模式(具体实现者)
 */
public class Projector implements PlaybackDevice {

    private String name;

    public Projector(String name) {
        this.name = name;
    }

    @Override
    public void play(String mediaType, String content) {
        System.out.println(name + " 投影仪播放 " + mediaType + ": " + content);
    }

    @Override
    public void stop() {
        System.out.println(name + " 投影仪停止播放.");
    }
}
  • 桥接模式中的 具体实现者,分别实现 PlaybackDevice 接口,代表不同的播放设备。
3. MediaPlayer
csharp 复制代码
/**
 * 媒体播放器
 * <p>
 * 桥接模式(抽象部分) / 工厂方法模式(产品)
 */
public abstract class MediaPlayer {

    protected PlaybackDevice device;

    public MediaPlayer(PlaybackDevice device) {
        this.device = device;
    }

    public void setDevice(PlaybackDevice device) {
        this.device = device;
    }

    public abstract void playMedia(String content);

    public void stopMedia() {
        device.stop();
    }
}
  • 桥接模式中的 抽象部分,维护了一个 PlaybackDevice 类型的引用,提供 setDetvice() 方法,允许动态切换播放设备。
  • 工厂方法模式的 产品,它是工厂方法模式中呗创建的抽象产品, 定义了抽象方法 playMedia(),需要具体的媒体播放器实现特有播放逻辑。
4. MoviePlayer MusicPlayer ImagePlayer
java 复制代码
/**
 * 电影播放器
 * <p>
 * 桥接模式(扩展抽象部分)
 */
public class MoviePlayer extends MediaPlayer {

    public MoviePlayer(PlaybackDevice device) {
        super(device);
    }

    @Override
    public void playMedia(String content) {
        System.out.println("播放电影: ");
        device.play("电影", content);
    }
}

/**
 * 音乐播放器
 * <p>
 * 桥接模式(扩展抽象部分)
 */
public class MusicPlayer extends MediaPlayer {

    public MusicPlayer(PlaybackDevice device) {
        super(device);
    }

    @Override
    public void playMedia(String content) {
        System.out.println("播放音乐: ");
        device.play("音乐", content);
    }
}

/**
 * 图片播放器
 * <p>
 * 桥接模式(扩展抽象部分)
 */
public class ImagePlayer extends MediaPlayer {

    public ImagePlayer(PlaybackDevice device) {
        super(device);
    }

    @Override
    public void playMedia(String content) {
        System.out.println("播放图片: ");
        device.play("图片", content);
    }
}
  • 桥接模式中的 扩展抽象部分,代表不同类型的媒体播放器。
  • 实现 MediaPlayer 抽象方法,通过调用 device.play() 方法,将实际的播放工厂委托给具体的 PlaybackDevice 实现。
5. MediaPlayerFactory
csharp 复制代码
/**
 * 多媒体播放器工厂
 * <p>
 * 工厂方法模式(抽象创建者)
 */
public interface MediaPlayerFactory {

    MediaPlayer createMoviePlayer();

    MediaPlayer createMusicPlayer();

    MediaPlayer createImagePlayer();

}
  • 工厂方法模式中的 抽象创建者,定义了创建各种媒体播放器( MoviePlayer MusicPlayer ImagePlayer ) 的工厂方法。
6. TelevisionPlayerFactory SpeakerPlayerFactory
csharp 复制代码
/**
 * 电视播放器工厂
 * <p>
 * 工厂方法模式(具体创建者)
 */
public class TelevisionPlayerFactory implements MediaPlayerFactory {

    private PlaybackDevice television;

    public TelevisionPlayerFactory(String name) {
        this.television = new Television(name);
    }

    @Override
    public MediaPlayer createMoviePlayer() {
        System.out.println("为电视创建电影播放器.");
        return new MoviePlayer(television);
    }

    @Override
    public MediaPlayer createMusicPlayer() {
        System.out.println("为电视创建音乐播放器.");
        return new MusicPlayer(television);
    }

    @Override
    public MediaPlayer createImagePlayer() {
        System.out.println("为电视创建图片播放器.");
        return new ImagePlayer(television);
    }
}

/**
 * 音响播放器工厂
 * <p>
 * 工厂方法模式(具体创建者)
 */
public class SpeakerPlayerFactory implements MediaPlayerFactory {

    private PlaybackDevice speakerSystem;

    public SpeakerPlayerFactory(String name) {
        this.speakerSystem = new SpeakerSystem(name);
    }

    @Override
    public MediaPlayer createMoviePlayer() {
        System.out.println("为扬声器创建电影音频播放器.");
        return new MoviePlayer(speakerSystem);
    }

    @Override
    public MediaPlayer createMusicPlayer() {
        System.out.println("为扬声器创建音乐播放器.");
        return new MusicPlayer(speakerSystem);
    }

    @Override
    public MediaPlayer createImagePlayer() {
        System.out.println("扬声器无法播放图片信息.");
        return null;
    }
}
  • 工厂方法模式中的 具体创建者,它们实现了 MediaPlayerFacotry 接口。
  • TelevisionPlayerFactory 负责创建在 Television 设备上播放的各种媒体播放器。
  • SpeakerPlayerFactory 负责创建在 SpeakerSystem 设备上播放的各种媒体播放器,但 createImagePlayer() 方法返回null,音响 系统不支持图片播放,工厂方法可以根据具体创建者的特性来决定是否支持某种产品。
测试类
ini 复制代码
public class DeviceTest {

    @Test
    public void test_design_factory() {

        MediaPlayerFactory tvFactory = new TelevisionPlayerFactory("小米电视机");
        MediaPlayer tvMoviePlayer = tvFactory.createMoviePlayer();
        tvMoviePlayer.playMedia("《泰坦尼克号》");

        System.out.println();

        MediaPlayer musicPlayerOnTV = tvFactory.createMusicPlayer();
        musicPlayerOnTV.playMedia("《阿刁》");

        System.out.println();

        MediaPlayerFactory speakerFactory = new SpeakerPlayerFactory("漫步者ASZ");
        MediaPlayer speakerMusicPlayer = speakerFactory.createMusicPlayer();
        speakerMusicPlayer.playMedia("《My Heart Will Go On》");

        MediaPlayer imagePlayerOnSpeakers = speakerFactory.createImagePlayer();
        if (imagePlayerOnSpeakers == null) {
            System.out.println("音响无法播放图片信息.");
        }

    }

}

组合优势

  • 工厂方法模式担任了媒体播放器对象创建的任务,将客户端与具体播放器类的实例化过程解耦
  • 当需要根据不同条件来创建不同种类的对象,并且这些对象本身又具有独立变化的抽象和实现维度时,这种组合非常有效。

桥接模式+组合模式

组合模式,结构型设计模式,可以使用它将对象组合成数状结构,并且能像使用单独对象一样使用它们。

场景

开发一个简单图形编辑器,用户可以绘制各种图形(例如: 圆形、矩形、文本框),这些图形可以单独存在,也可以组合成复杂的复合图形,同时,这些图 形需要支持不同的渲染方式(如:矢量渲染、位图渲染)。

模式职责

  • 桥接模式: 将图形作为抽象部分,具体渲染方式作为实现部分,进行解耦。
  • 组合模式: 将对象组合成树形结构以表示 部分-整体 的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

具体设计

桥接模式
  • 抽象部分: Shape 定义了图形的抽象类,持有对 Rederer 的引用。
  • 扩展抽象部分: 具体的图形,包含 Circle Rectangle Text 等。
  • 实现者接口: Rederer 定义图形渲染方式的接口。
  • 具体实现者: 具体图形渲染器,包含 VectorRenderer RasterRenderer 等。
组合模式
  • 组件: Shape 抽象类(桥接模式的抽象部分),定义了所有图形(包括复合图形)的通用接口,如 draw() add() remove() getChild() 等管理子组件的方法。
  • 叶子: Circle Rectangle Text 等具体图形,没有子组件。 放器。
  • 复合: CompositeShape 包含子组件的集合,并实现了组件接口,将对子组件的操作委托给它们。

代码示例

1. Rederer
arduino 复制代码
/**
 * 渲染器接口
 * <p>
 * 桥接模式(实现者接口)
 */
public interface Renderer {

    void renderCircle(double radius);

    void renderRectangle(double width, double height);

    void renderText(String text);

}
  • 桥接模式中的 实现者接口,定义了图形渲染的通用行为,如 renderCircle() renderRectangle() renderText()
2. VectorRenderer RasterRenderer
arduino 复制代码
/**
 * 矢量图形渲染器
 * <p>
 * 桥接模式(具体实现者)
 */
public class VectorRenderer implements Renderer {

    @Override
    public void renderCircle(double radius) {
        System.out.println("[矢量图] 半径为: " + radius + "的圆。");
    }

    @Override
    public void renderRectangle(double width, double height) {
        System.out.println("[矢量图] 宽度: " + width + ", 高度: " + height + "的矩形。");
    }

    @Override
    public void renderText(String text) {
        System.out.println("[矢量图] 内容为: " + text + "的文本框。");
    }
}

/**
 * 位图渲染器
 * <p>
 * 桥接模式(具体实现者)
 */
public class RasterRenderer implements Renderer {

    @Override
    public void renderCircle(double radius) {
        System.out.println("[位图] 半径为: " + radius + "的圆。");
    }

    @Override
    public void renderRectangle(double width, double height) {
        System.out.println("[位图] 宽度: " + width + ", 高度: " + height + "的矩形。");
    }

    @Override
    public void renderText(String text) {
        System.out.println("[位图] 内容为: " + text + "的文本框。");
    }
}
  • 桥接模式中的 具体实现者,分别实现了矢量渲染和位图渲染的逻辑。
3. Shape
csharp 复制代码
/**
 * 图形
 * <p>
 * 桥接模式(抽象部分)/组合模式(组件)
 */
public abstract class Shape {

    protected Renderer renderer;

    public Shape(Renderer renderer) {
        this.renderer = renderer;
    }

    public void setRenderer(Renderer renderer) {
        this.renderer = renderer;
    }

    public abstract void draw();

    public void add(Shape shape) {
        throw new UnsupportedOperationException("方法未实现!");
    }

    public void remove(Shape shape) {
        throw new UnsupportedOperationException("方法未实现!");
    }

    public void getChild(int i) {
        throw new UnsupportedOperationException("方法未实现!");
    }
}
  • 桥接模式中的 抽象部分,维护了一个 Renderer 类型的引用,提供 setRenderer() 方法,允许动态切换渲染器。
  • 组合模式中的 组件,定义了所有图形(包括叶子和复合)的通用接口 draw(),同时定义了 add() remove() getChild() 等用于管理子 组件的方法,这些方法在叶子节点中会抛出 UnsupportedOperationException
4. Circle Rectangle Text
arduino 复制代码
/**
 * 圆形
 * <p>
 * 桥接模式(扩展抽象部分)/组合模式(叶子)
 */
public class Circle extends Shape {

    private double radius;

    public Circle(Renderer renderer, double radius) {
        super(renderer);
        this.radius = radius;
    }

    @Override
    public void draw() {
        renderer.renderCircle(radius);
    }
}

/**
 * 矩形
 * <p>
 * 桥接模式(扩展抽象部分)/组合模式(叶子)
 */
public class Rectangle extends Shape {

    private double width;

    private double height;

    public Rectangle(Renderer renderer, double width, double height) {
        super(renderer);
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        renderer.renderRectangle(width, height);
    }
}

/**
 * 文本框
 * <p>
 * 桥接模式(扩展抽象部分)/组合模式(叶子)
 */
public class Text extends Shape {

    private String content;

    public Text(Renderer renderer, String content) {
        super(renderer);
        this.content = content;
    }

    @Override
    public void draw() {
        renderer.renderText(content);
    }
}
  • 桥接模式中的 扩展抽象部分,代表具体的图形类型。
  • 组合模式中的 叶子,它们没有子组件,实现了 draw() 方法,并委托给 renderer 进行实际的绘制,不实现 add() remove() getChild() 方法,或抛出异常。
5. CompositeShape
typescript 复制代码
/**
 * 复合图形类
 * <p>
 * 组合模式(组件)
 */
public class CompositeShape extends Shape {

    private List<Shape> children = new ArrayList<>();

    private String compositeName;

    public CompositeShape(Renderer renderer, String compositeName) {
        super(renderer);
        this.compositeName = compositeName;
    }

    @Override
    public void draw() {
        System.out.println("---开始绘制名为 [" + compositeName + "] 的复合图---");
        for (Shape shape : children) {
            shape.setRenderer(shape.renderer);
            shape.draw();
        }
        System.out.println("---名为 [" + compositeName + "] 的复合图绘制结束---");
    }

    @Override
    public void setRenderer(Renderer renderer) {
        super.setRenderer(renderer);
        for (Shape child : children) {
            child.setRenderer(this.renderer);
        }
    }

    @Override
    public void add(Shape shape) {
        children.add(shape);
    }

    @Override
    public void remove(Shape shape) {
        children.remove(shape);
    }

    @Override
    public void getChild(int i) {
        children.get(i);
    }
    
}
  • 组合模式中的 复合,它维护了一个 List<Shape> 来存储子组件
  • 实现了 Shape 接口的 draw() 方法,该方法会遍历集合中的子组件并调用它们的 draw() 方法,从而实现对整个复合结构的绘制。
  • draw() 方法中,通过调用 shape.setRenderer(this.renderer) 确保子图形与复合图形使用相同的渲染器,在 setRenderer() 方法中 也选择性的将渲染器传播给子组件,使得复合图形的渲染器可以影响到其所有子类。
测试类
ini 复制代码
public class RendererTest {

    @Test
    public void test_design_composite() {

        Renderer vectorRenderer = new VectorRenderer();
        Renderer rasterRenderer = new RasterRenderer();

        CompositeShape group1 = new CompositeShape(vectorRenderer,"group1");
        group1.add(new Circle(vectorRenderer, 5.0));
        group1.add(new Rectangle(vectorRenderer, 8.0, 6.0));

        CompositeShape group2 = new CompositeShape(rasterRenderer,"group2");
        group2.add(new Text(rasterRenderer, "超大的文本框"));
        group2.add(new Circle(rasterRenderer, 3.0));

        CompositeShape drawing = new CompositeShape(vectorRenderer,"drawing");
        drawing.add(group1);
        drawing.add(group2);
        drawing.add(new Rectangle(vectorRenderer, 100.0, 50.0));
        drawing.draw();

        System.out.println("\n改变整个绘图的渲染器\n");
        drawing.setRenderer(rasterRenderer);
        drawing.draw();

        System.out.println("\n改变group1子组件的渲染器\n");
        group1.setRenderer(vectorRenderer);
        drawing.draw();
    }

}
  • 构建了包含有两个子组件的复合结构,且两个复合结构使用了不同的渲染器。
  • 当改变主复合图形的渲染器时,将渲染器自动传播到所有子图形中,演示对整个图形结构渲染方式的统一设置。
  • 改变其中子组件渲染器,演示独立的选择其中渲染方式。

桥接模式作为一种结构型设计模式,其核心价值在于将抽象与实现解耦,使得两者能够独立演化和扩展。通过 组合由于继承 的原则,有效的解决了传统继承模式在多维度变化场景下导致的类爆炸问题。 本文主要练习了 桥接模式与其他设计模式的组合使用(工厂方法模式、抽象工厂模式、策略模式、装饰器模式、模板方法模式和组合模式)。

相关推荐
J2K1 分钟前
JDK都25了,你还没用过ZGC?那真得补补课了
java·jvm·后端
EMQX2 分钟前
ESP32 + MCP over MQTT:通过大模型控制智能硬件设备
后端·mcp
郭京京3 分钟前
go框架gin(中)
后端·go
郭京京4 分钟前
go框架gin(下)
后端·go
林树的编程频道5 分钟前
单例模式的推导
后端
就是帅我不改6 分钟前
揭秘Netty高性能HTTP客户端:NIO编程的艺术与实践
后端·面试·github
Ray669 分钟前
SugLucene索引构建
后端
舒一笑37 分钟前
Saga分布式事务框架执行逻辑
后端·程序员·设计
Emma歌小白1 小时前
完整后台模块模板
后端
得物技术1 小时前
MySQL单表为何别超2000万行?揭秘B+树与16KB页的生死博弈|得物技术
数据库·后端·mysql