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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

java 复制代码
public class Order {
    private double amount;
    
    public double calculateTotal() {
        return amount; // 最初只有商品价格
    }
}

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

java 复制代码
public class Order {
    private double amount;
    private double shippingFee;
    
    public double calculateTotal() {
        return amount + shippingFee;
    }
}

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

java 复制代码
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. 难以测试:各种条件组合导致测试用例指数级增长

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

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

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

java 复制代码
// 策略模式:不同的折扣策略
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 结构型模式:构建灵活的结构

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

核心思想:组合优于继承

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

  1. 类层次结构过于复杂

  2. 父类的修改会影响所有子类

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

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

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

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

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

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

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

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

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

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

2.5.1 单一职责原则(SRP)

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

优点:

  • 功能单一,职责清晰。
  • 增强可读性,方便维护
    缺点:
  • 拆分的太详细,类的数量会急剧增加。
  • 职责的的度量没有统一的标准,需要根据项目实现情况而定。

违反SRP的例子:

java 复制代码
public class UserService {
    // 用户验证
    public boolean validateUser(String username, String password) { ... }
    
    // 数据库操作
    public void saveUser(User user) { ... }
    
    // 发送邮件
    public void sendEmail(User user, String message) { ... }
}

遵循SRP的重构:

java 复制代码
public class UserValidator { ... }
public class UserRepository { ... }
public class EmailService { ... }
2.5.2 开闭原则(OCP)

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

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

2.5.3 里氏替换原则(LSP)

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

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

违反LSP的例子:

java 复制代码
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:为了使用模式而使用模式

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

反例:

java 复制代码
// 过度设计的单例模式
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推送),并且可以灵活添加新渠道。

初始设计(存在问题的版本):
java 复制代码
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
    }
}
重构后的设计(应用多种模式):
java 复制代码
// 策略模式:不同的消息类型
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);
    }
}

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

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

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

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

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

总结:开始设计模式之旅

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

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

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

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

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

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

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

相关推荐
3 小时前
java关于内部类
java·开发语言
好好沉淀3 小时前
Java 项目中的 .idea 与 target 文件夹
java·开发语言·intellij-idea
gusijin3 小时前
解决idea启动报错java: OutOfMemoryError: insufficient memory
java·ide·intellij-idea
To Be Clean Coder3 小时前
【Spring源码】createBean如何寻找构造器(二)——单参数构造器的场景
java·后端·spring
吨~吨~吨~3 小时前
解决 IntelliJ IDEA 运行时“命令行过长”问题:使用 JAR
java·ide·intellij-idea
你才是臭弟弟3 小时前
SpringBoot 集成MinIo(根据上传文件.后缀自动归类)
java·spring boot·后端
短剑重铸之日3 小时前
《设计模式》第二篇:单例模式
java·单例模式·设计模式·懒汉式·恶汉式
码农水水3 小时前
得物Java面试被问:消息队列的死信队列和重试机制
java·开发语言·jvm·数据结构·机器学习·面试·职场和发展
summer_du3 小时前
IDEA插件下载缓慢,如何解决?
java·ide·intellij-idea
C澒3 小时前
面单打印服务的监控检查事项
前端·后端·安全·运维开发·交通物流