在正式进入设计模式之前,我们先花点时间把一些基础的面向对象概念捋一捋。虽然这些概念我们在学习 Java 的时候或多或少都接触过,但在设计模式的实际应用里,它们可不只是课本里的术语,而是很多模式背后的根基。
接口是 Java 中非常核心的一个概念,它就像是“规范”或者“协议”,定义了一组方法的名称和参数格式,但并不去实现这些方法的具体逻辑。我们可以把接口理解成是一种“承诺”——谁实现了这个接口,谁就必须按照接口定义的方法去完成逻辑。
在 Java 中,接口的作用主要有两个:
从 Java 8 开始,接口还可以带有默认方法(default)和静态方法,甚至可以有私有方法,但它们仍不能有实例变量,也不能有构造方法,接口本质上还是以“行为定义”为主。
抽象类和接口有些相似,也是用来定义一个抽象的模板,让子类去继承并补全细节。但抽象类更像是“半成品”或者“骨架”,已经包含了一些默认的行为实现,还预留了部分抽象方法交给子类实现。
抽象类有几个特点:
abstract 关键字标识)。通常我们会在类之间存在父子关系,且想在父类中提供一些默认实现的时候,选用抽象类会更合适;而当我们只需要定义一套“规范”时,接口是更好的选择。
继承是面向对象编程中的一个基本特性,它允许我们创建一个类,并“复用”另一个类的代码。简单说,就是子类自动拥有父类的属性和方法,而不需要重新写一遍。
继承有两个主要用途:
需要注意的是,Java 不支持多重继承(一个类不能继承多个类),是为了避免“菱形继承”这种带来歧义的场景。不过接口是可以多实现的,这在实际设计中提供了更灵活的扩展方式。
多态是面向对象设计中最关键的一环。所谓多态,指的是同一操作作用于不同对象时,可以表现出不同的行为。这是我们实现“扩展性”和“可替换性”的基础。
多态有三种形式:
理解多态最关键的一点是:我们可以“面向父类编程”,而在运行时根据对象的实际类型调用具体实现。这为设计模式中的很多“可扩展”“可替换”行为打下了基础。
梳理完了这些基础知识,目的是为了打牢理解设计模式的基本功。因为很多设计模式的核心思想,其实就是在这些语言特性之上构建出来的。如果对这些基础知识掌握得不牢,设计模式学起来就容易停留在表面,很难真正理解背后的设计意图。
接下来,我们就可以正式进入设计模式的学习了。
大家有没有思考过如下几个问题?
如果存在上述问题,那么我们一定要学习软件开发中的重要技能 —— 设计模式。
设计模式是软件开发人员在软件开发过程中面临的一般问题的 通用 解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
通俗地说就是前辈们在写代码时摸索出了一些不错的方法,可以用于解决一类问题、更好地开发和维护项目。于是其他软件开发者纷纷效仿,久而久之,就得出了一套优秀的软件开发方法总结。
目前最为经典的设计模式有 23 种,学习之后,不仅能帮助我们开拓思路、写出更优质的代码、提高项目的开发和维护效率;还能够帮助我们更好地阅读和理解源码,甚至可以根据文件名称直接推断出源码的架构设计(有点行话的感觉)!因此,在准备阅读框架源码前,强烈建议先学习设计模式。
此外,设计模式也是软件开发相关岗位面试的重点(尤其是大厂、后端开发岗位),建议大家有时间的话都要学习。
我们在学习设计模式时,不能只把它当成一些“用法模板”来背。设计模式真正的价值,其实是背后那一套“为什么要这么设计”的思路。它不是突然冒出来的,而是建立在一系列面向对象设计原则之上的。
这些设计原则,很多我们在项目中可能没系统学过,但如果认真看过一些优秀的源码,或者踩过不少坑,就会慢慢体会到它们的价值。下面我们来简单梳理几个最基础、也是最重要的设计原则,它们几乎贯穿了所有设计模式。
一个类,只干一件事。
一个类只负责一个功能模块上的事情。如果一个类同时负责业务逻辑、数据库访问、日志处理等多种职责,那么一旦某个职责发生变化,就有可能影响到整个类的稳定性。
这个原则听上去简单,实际操作起来却常常被忽视。很多时候是图省事,结果一个类越写越大,维护起来就越来越难。
设计模式里,比如装饰器模式、职责链模式,其实都是在帮我们把复杂职责拆分开,控制类的粒度。
对扩展开放,对修改关闭。
当我们新增功能时,应该通过“新增代码”的方式来实现,而不是去修改已有的类或方法。这样做的好处是,原来的功能不会被影响,风险小;新功能也能保持独立,维护方便。
这个原则能帮助我们实现“加功能不动老代码”。比如策略模式、工厂模式、本质上都是在帮助我们在已有体系上“平滑加功能”。
子类对象必须能够替换掉父类对象,并保持原有功能不变。
通俗点说,继承关系不能乱用。继承不是为了代码复用,而是为了“保持通用的行为”。如果子类违背了父类的行为约定,那就会出现一些难以察觉的 bug。
这个原则是判断继承设计是否合理的一个标准。像模板方法模式、桥接模式这些,都很强调父子类之间的替换关系。
高层模块不应该依赖低层模块,两者都应该依赖抽象。
我们应该“面向接口编程”,而不是直接依赖某个具体的类。
比方说,一个支付系统,业务逻辑层不要直接依赖“微信支付”或“支付宝支付”的实现类,而应该依赖一个 Payment 接口。这样我们就可以灵活地切换实现方式,而不会动到业务逻辑的代码。
像工厂模式、策略模式、依赖注入的思想,基本都是围绕这个原则展开的。
接口要小而精,不要大而全。
接口一旦设计成“全能型”,实现起来就容易出现“我不需要这个方法但也要实现它”的情况。这样不仅多余,还容易让接口的维护变得困难。
比如我们在系统中定义了 UserService 接口,如果这个接口又要注册用户、又要导出报表、还要批量删除,很多实现类就会出现强行实现的尴尬场面。
像组合模式、适配器模式这些,往往就是在帮助我们更好地隔离接口、解耦功能。
只和直接相关的类交流,尽量减少对外部对象内部细节的依赖。
也叫“最少知道原则”,就是说一个类应该尽量只关注自己的直接朋友,不要对别人家的细节过于熟悉。这样可以减少耦合,提高模块的独立性。
典型的例子就是外观模式,它通过提供统一入口,让调用者不需要接触系统内部的各种细节类。
这些设计原则看似是理论,其实非常实用。它们决定了我们写的代码是否易读、是否好维护、业务功能是否能快速扩展。
而设计模式,就是这些原则的“落地方案”。也就是说,设计模式本质上是在帮我们实践这些设计原则,让系统更健壮、更灵活、更可扩展。
掌握了这些原则,理解设计模式就不是死记硬背,而是顺理成章的推导过程。我们后面讲的每一个设计模式,其实都可以回头映射到这些原则上。
从设计模式的大类上来看,设计模式共分成三种类型:
直接看下面的思维导图,分类很清晰:

主流的设计模式共有 23 种,建议大家按照以下四个阶段来学习:
其中第一个阶段和第二个阶段 可以同时进行 ,即对于每个设计模式的学习都是:先了解、再编码实现。
依次了解每一种设计模式的应用场景、特点、UML 类图,能够对设计模式有个基础的印象。
根据使用频率、难易度、面试考察率等综合排序,仅供参考,并不绝对!
优先:
一般:
低优先:
本阶段的目标:依次编码实现每个设计模式,用任何支持面向对象的编程语言都可以,最好能够独立(不借助任何资料)从 0 写出每个设计模式的代码。
本阶段的目标:通过做项目或阅读项目源码来进一步强化每个设计模式的实际应用。做到能根据某个场景主动选出合适的设计模式来优化代码、灵活运用,并且能够通过文件命名、项目目录结构等途径来快速判断出某个框架是否使用了设计模式。
面试时对设计模式的考察主要有 4 种形式:
更多面试题可以在面试鸭《设计模式面试题题库》中查看。
