欢迎光临
我们一直在努力

《设计模式》第一篇:初识

本期内容为自己总结归档,共分6章,本人遇到过的面试问题会重点标记。

《设计模式》第一篇:初识

《设计模式》第二篇:单例模式

《设计模式》第三篇:工厂模式

《设计模式》第四篇:观察模式

《设计模式》第五篇:策略模式

《设计模式》第六篇:装饰器模式

《设计模式》第七篇:适配器模式

《设计模式》第八篇:创建型模式

《设计模式》第九篇:结构型模式

《设计模式》第十篇:行为型模式

《设计模式》第十一篇:总结&常用案例

(若有任何疑问,可在评论区告诉我,看到就回复)

第一章:设计模式为什么重要?

1.1 一个真实世界的困境:电商订单系统的演进

让我们从一个简单的电商订单系统开始。最初的版本非常简单:

public class Order {
private double amount;

public double calculateTotal() {
return amount; // 最初只有商品价格
}
}

随着业务发展,我们需要添加运费:

public class Order {
private double amount;
private double shippingFee;

public double calculateTotal() {
return amount + shippingFee;
}
}

接着要添加税费、优惠券、会员折扣…

public class Order {
private double amount;
private double shippingFee;
private double tax;
private double couponDiscount;
private boolean isVIP;

public double calculateTotal() {
double total = amount + shippingFee + tax;

if (couponDiscount > 0) {
total -= couponDiscount;
}

if (isVIP) {
total *= 0.9; // VIP 9折
}

return total;
}
}

这就是典型的“代码坏味道”——每当有新需求时,我们都在不断修改同一个类,添加更多的条件判断。这种代码有几个严重问题:

  • 违反开闭原则:每次修改都需要直接修改源代码

  • 难以测试:各种条件组合导致测试用例指数级增长

  • 难以维护:一个地方的修改可能影响看似无关的功能

  • 1.2 设计模式的救赎:重构后的优雅设计

    如果我们应用设计模式来重构这个系统,会变成什么样?

    // 策略模式:不同的折扣策略
    public interface DiscountStrategy {
    double applyDiscount(double amount);
    }

    public class VIPDiscount implements DiscountStrategy {
    public double applyDiscount(double amount) {
    return amount * 0.9;
    }
    }

    public class CouponDiscount implements DiscountStrategy {
    private double couponValue;

    public double applyDiscount(double amount) {
    return amount – couponValue;
    }
    }

    // 装饰器模式:动态添加费用
    public abstract class OrderDecorator {
    protected Order order;

    public OrderDecorator(Order order) {
    this.order = order;
    }

    public abstract double calculateTotal();
    }

    public class ShippingFeeDecorator extends OrderDecorator {
    public ShippingFeeDecorator(Order order) {
    super(order);
    }

    public double calculateTotal() {
    return order.calculateTotal() + 10.0; // 基础运费
    }
    }

    通过这样的重构,我们获得了:

    • 可扩展性:添加新折扣类型不需要修改现有代码

    • 可测试性:每个策略都可以独立测试

    • 可维护性:每个类职责单一,易于理解和修改

    1.3 设计模式的核心价值

    设计模式的价值远不止于代码组织,它还在以下几个层面带来深远影响:

    1.3.1 提升沟通效率

    想象两个开发者之间的对话:

    没有设计模式:

    "我需要一个类,这个类只能创建一个实例,全局访问,还要考虑多线程安全…"

    有设计模式:

    "这里用单例模式。"

    设计模式提供了共享的词汇表,让开发者可以用更少的词汇表达更复杂的概念。

    1.3.2 加速学习曲线

    当我加入一个新的团队时,如果看到代码中使用了熟悉的模式,我能更快地理解系统架构。设计模式就像是软件架构的通用设计模式语言,降低了新人理解系统的门槛。

    1.3.3 避免重复造轮子

    很多设计问题是通用的。设计模式记录了经过验证的解决方案,让我们可以直接站在巨人的肩膀上,而不是从零开始摸索。

    第二章:设计模式全景概览

    2.1 设计模式的起源:GOF 23种模式

    设计模式的概念最早出现在建筑领域。建筑师克里斯托弗·亚历山大在《建筑的永恒之道》中提出:许多建筑问题都有共同的解决方案模式。

    1994年,Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides四位作者(被称为"四人帮"或GOF)出版了《设计模式:可复用面向对象软件的基础》,系统地提出了23种设计模式。

    这23种模式被分为三大类,下面是它们的全景图:

    2.2 创建型模式:优雅地创建对象

    创建型模式专注于对象的创建机制,帮助我们更灵活、更合理地创建对象实例。

    核心思想:解耦对象的创建与使用

    想象一个汽车制造厂。司机不需要知道汽车是如何制造的,只需要知道如何驾驶。同样,在软件中,创建型模式将对象的创建过程封装起来,让使用者无需关心创建细节。

    ⭐常用创建型模式对比:
    模式解决的核心问题典型应用场景
    单例模式 确保一个类只有一个实例,并提供全局访问点 数据库连接池、配置管理器、日志记录器
    工厂方法 将对象创建延迟到子类,让子类决定创建什么对象 跨平台UI组件、支付方式选择
    抽象工厂 创建一系列相关或依赖的对象族 GUI库、跨数据库访问层
    建造者 分离复杂对象的构建过程和表示 创建复杂对象(如SQL查询、HTTP请求)
    原型模式 通过复制现有对象来创建新对象 游戏中的敌人复制、成本较高的对象创建

    2.3 结构型模式:构建灵活的结构

    结构型模式关注如何将类或对象组合成更大的结构,同时保持结构的灵活和高效。

    核心思想:组合优于继承

    在面向对象设计中,继承是最常见的代码复用方式。但过度使用继承会导致:

  • 类层次结构过于复杂

  • 父类的修改会影响所有子类

  • 编译时确定的静态关系,缺乏灵活性

  • 结构型模式提倡使用组合来替代继承,提供更大的灵活性。

    ⭐常用结构型模式对比:
    模式解决的核心问题典型应用场景
    适配器模式 让不兼容的接口能够协同工作 旧系统集成、第三方库适配
    装饰器模式 动态地给对象添加额外职责 Java I/O流、中间件链
    代理模式 为其他对象提供一种代理以控制访问 远程代理、虚拟代理、保护代理
    外观模式 为复杂子系统提供统一入口 框架API设计、简化复杂调用
    组合模式 以树形结构处理对象集合 文件系统、GUI组件树

    2.4 行为型模式:对象间的协作与职责分配

    行为型模式关注对象之间如何交互、如何分配职责,以及算法的封装。

    核心思想:将变化的部分封装起来

    软件中最难维护的部分就是经常变化的部分。行为型模式将这些变化封装在独立的类中,使系统更加稳定和灵活。

    ⭐常用行为型模式对比:
    模式解决的核心问题典型应用场景
    观察者模式 对象间的一对多依赖关系 事件处理系统、MVC架构
    策略模式 算法的封装与替换 支付方式、排序算法
    模板方法 定义算法的骨架,延迟步骤实现 框架钩子、工作流程
    责任链 请求的链式处理 过滤器链、审批流程
    状态模式 对象状态改变时改变行为 订单状态机、游戏角色状态

    2.5 ⭐设计模式的基石:SOLID原则

    在深入学习具体模式之前,理解SOLID原则至关重要。这些原则是设计模式的理论基础和指导思想。

    2.5.1 单一职责原则(SRP)

    一个类应该只有一个引起它变化的原因。

    优点:

    • 功能单一,职责清晰。
    • 增强可读性,方便维护

    缺点:

    • 拆分的太详细,类的数量会急剧增加。
    • 职责的的度量没有统一的标准,需要根据项目实现情况而定。

    违反SRP的例子:

    public class UserService {
    // 用户验证
    public boolean validateUser(String username, String password) { … }

    // 数据库操作
    public void saveUser(User user) { … }

    // 发送邮件
    public void sendEmail(User user, String message) { … }
    }

    遵循SRP的重构:

    public class UserValidator { … }
    public class UserRepository { … }
    public class EmailService { … }

    2.5.2 开闭原则(OCP)

    软件实体应该对扩展开放,对修改关闭。

            这是我们文章开头订单系统的核心问题。通过策略模式和装饰器模式,我们成功地遵循了OCP原则:当需要新的折扣类型或费用类型时,我们扩展新的类,而不是修改现有代码。

    2.5.3 里氏替换原则(LSP)

    子类型必须能够替换它们的父类型。

            这意味着任何父类出现的地方,子类都应该能够无缝替换。这个原则确保了继承关系的正确使用。

    违反LSP的例子:

    class Rectangle {
    protected int width, height;

    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }
    }

    class Square extends Rectangle {
    // 正方形要求宽高相等
    @Override
    public void setWidth(int width) {
    super.setWidth(width);
    super.setHeight(width);
    }

    @Override
    public void setHeight(int height) {
    super.setWidth(height);
    super.setHeight(height);
    }
    }

    这里Square无法完美替换Rectangle,因为设置宽高的行为不一致。

    2.5.4 接口隔离原则(ISP)

    客户端不应该被迫依赖它们不使用的接口。

    将庞大的接口拆分为更小、更具体的接口,让客户端只需知道它们真正需要的方法。

    2.5.5 依赖倒置原则(DIP)

    高层模块不应该依赖低层模块,两者都应该依赖抽象。

    这是实现松耦合的关键。通过依赖抽象(接口或抽象类),而不是具体实现,我们的系统变得更加灵活。

    第三部分:如何学习设计模式

    3.1 学习路径建议

    学习设计模式不是一蹴而就的过程。我建议按照以下路径循序渐进:

    3.2 避免常见误区

    在学习设计模式的过程中,有几个常见误区需要警惕:

    误区1:为了使用模式而使用模式

    这是最常见的错误。设计模式是解决特定问题的工具,不是必须遵循的教条。如果简单代码就能解决问题,就不要强行使用模式。

    反例:

    // 过度设计的单例模式
    public class SimpleConfig {
    private static volatile SimpleConfig instance;
    private static final Object lock = new Object();

    private SimpleConfig() {
    // 私有构造器
    }

    public static SimpleConfig getInstance() {
    if (instance == null) {
    synchronized (lock) {
    if (instance == null) {
    instance = new SimpleConfig();
    }
    }
    }
    return instance;
    }

    // 实际上只有一个简单的配置项
    private String config = "default";

    public String getConfig() { return config; }
    }

    如果配置确实简单且不需要延迟初始化,直接使用静态变量可能更清晰。

    误区2:过度设计,过早优化

    "将来可能需要"是一个危险的信号。遵循YAGNI原则(You Ain't Gonna Need It),只有在真正需要时才引入复杂性。

    误区3:死记硬背模式结构

    理解模式的意图和适用场景比记住UML图更重要。同样的模式在不同场景下可能有不同的实现方式。

    第四部分:从理论到实践:设计模式在真实项目中的应用

    让我们看一个综合案例,展示多个设计模式如何协同工作。

    4.1 场景:电商平台通知系统

    需求:电商平台需要向用户发送各种通知(订单确认、发货通知、促销信息),支持多种渠道(短信、邮件、App推送),并且可以灵活添加新渠道。

    初始设计(存在问题的版本):

    public class NotificationService {
    public void send(String message, String type, String channel) {
    if ("order".equals(type)) {
    if ("sms".equals(channel)) {
    // 发送订单短信
    System.out.println("发送订单短信: " + message);
    } else if ("email".equals(channel)) {
    // 发送订单邮件
    System.out.println("发送订单邮件: " + message);
    }
    // 更多条件判断…
    } else if ("promotion".equals(type)) {
    // 更多条件判断…
    }
    // 每增加一种类型或渠道,就要添加更多if-else
    }
    }

    重构后的设计(应用多种模式):

    // 策略模式:不同的消息类型
    public interface MessageType {
    String format(String content);
    }

    public class OrderMessage implements MessageType {
    public String format(String content) {
    return "[订单] " + content;
    }
    }

    public class PromotionMessage implements MessageType {
    public String format(String content) {
    return "[促销] " + content;
    }
    }

    // 策略模式:不同的发送渠道
    public interface Channel {
    void send(String formattedMessage);
    }

    public class SMSChannel implements Channel {
    public void send(String formattedMessage) {
    System.out.println("通过短信发送: " + formattedMessage);
    }
    }

    public class EmailChannel implements Channel {
    public void send(String formattedMessage) {
    System.out.println("通过邮件发送: " + formattedMessage);
    }
    }

    // 工厂模式:创建不同类型的消息处理器
    public class MessageProcessorFactory {
    public static MessageProcessor createProcessor(String type) {
    switch (type) {
    case "order":
    return new OrderProcessor();
    case "promotion":
    return new PromotionProcessor();
    default:
    throw new IllegalArgumentException("未知的消息类型");
    }
    }
    }

    // 观察者模式:当需要添加新功能时(如日志记录、数据统计)
    public abstract class MessageObserver {
    public abstract void onMessageSent(String message);
    }

    public class LoggingObserver extends MessageObserver {
    public void onMessageSent(String message) {
    System.out.println("记录日志: " + message);
    }
    }

    // 主服务类,现在变得非常简洁
    public class NotificationService {
    private List<Channel> channels = new ArrayList<>();
    private List<MessageObserver> observers = new ArrayList<>();

    public void send(String content, String type) {
    MessageProcessor processor = MessageProcessorFactory.createProcessor(type);
    String formattedMessage = processor.process(content);

    for (Channel channel : channels) {
    channel.send(formattedMessage);
    }

    for (MessageObserver observer : observers) {
    observer.onMessageSent(formattedMessage);
    }
    }

    public void addChannel(Channel channel) {
    channels.add(channel);
    }

    public void addObserver(MessageObserver observer) {
    observers.add(observer);
    }
    }

    这个重构后的设计具有以下优点:

  • 符合开闭原则:添加新消息类型或新渠道只需添加新类,无需修改现有代码

  • 单一职责:每个类只负责一个特定功能

  • 可测试性:每个组件都可以独立测试

  • 灵活性:可以动态添加/移除渠道和观察者

  • 总结:开始设计模式之旅

    设计模式不是高深莫测的理论,而是解决实际开发问题的实用工具集。总结一下:

  • 为什么需要设计模式:解决代码复杂性,提高可维护性和可扩展性

  • 设计模式的分类:创建型、结构型、行为型三大类别

  • SOLID原则:设计模式的理论基础

  • 如何学习设计模式:从原则到实践,避免常见误区

  • 记住,设计模式的最终目标不是写出"符合模式"的代码,而是写出清晰、可维护、可扩展的代码。模式只是达到这个目的的手段之一。

            在接下来的系列文章中,我们将深入探讨每个核心模式,通过更多真实案例帮助你真正掌握这些强大的工具。

    赞(0)
    未经允许不得转载:171主机测评 » 《设计模式》第一篇:初识
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址