一、六大设计原则
1、六大设计原则速览
- S - 单一职责原则,Single Responsibility Principle (SRP)
一个类只干一件事。就像一个厨师只负责炒菜,不负责端盘子。
- O - 开闭原则,Open-Closed Principle (OCP)
对扩展开放,对修改关闭
在已有的代码基础上进行扩展,而不是修改已有的代码
- L - 里氏替换原则,Liskov Substitution Principle (LSP)
子类必须能完全替换父类
子类只能扩展父类的方法,但是不能重写父类的方法。
若要替换建议使用组合,少用继承。
- I - 接口隔离原则,Interface Segregation Principle (ISP)
接口要小而专,不要臃肿。
- D - 依赖倒置原则,Dependency Inversion Principle (DIP)
抽象不应依赖于细节,细节应依赖于抽象。
面向接口编程,而不是面向具体实现
- D - 迪米特法则(又称最少知道原则),Law of Demeter (LoD) 或 Principle of Least Knowledge (PLK)
不与陌生人说话。类似客人只找前台办事,不需要知道酒店内部怎么运作。
快速记忆六大设计原则,推荐使用英文首字母联想法:SOLID(稳固)加一个 D。
2、六大设计原则深度理解(面试追问点)
| 原则 | 面试官可能追问的点 | Android 中的体现 |
|---|---|---|
| 单一职责 (SRP) | 如何判断类的职责是否单一? | Activity 只负责界面交互,业务逻辑抽离到 Presenter/ViewModel。 |
| 开闭原则 (OCP) | 能否举例说明不修改代码而是扩展代码? | RecyclerView.Adapter 通过继承实现新类型的条目,而不修改原有代码。 |
| 里氏替换 (LSP) | 子类重写父类方法时需注意什么? | 例如 View 的子类 TextView 可以完全替代 View,但重写 onDraw 时需调用 super.onDraw() 保持父类行为。 |
| 接口隔离 (ISP) | 接口臃肿有什么坏处? | 如 OnClickListener 和 OnLongClickListener 分开,避免实现不需要的方法。 |
| 依赖倒置 (DIP) | 依赖抽象和依赖具体实现有什么不同? | Retrofit 的 CallAdapter.Factory 依赖抽象,用户可以自定义扩展。 |
| 迪米特法则 (LoD) | 如何避免“过度中介”导致性能问题? | BroadcastReceiver 通过 Intent 传递数据,而不是直接访问其他组件的内部。 |
二、23种设计模式
1、23种设计模式快速记忆口诀
- 单元建两厂。 (创建型 5)
- 万代配音响,结构巧组装。 (结构型 7)
- 中介带访者,策马来观摩。 (行为型 11)
- 未备军令状,解释怕担责。
- 单(单例)元(原型)建(建造者)两厂(工厂方法、抽象工厂)
- 万(万同外,外观)代(代理)配(适配器)音响(享元),结构巧(巧同桥,桥接)组(组合)装(装饰)。
- 中介(中介者)带(迭代器)访者(访问者),策(策略模式)马来观(观察者)摩(模板方法)。 未备(备忘录)军令(命令)状(状态),解释(解释器)怕担责(责任链)。
2、核心原理与“解决什么问题”对照表
1. 创建型模式 (5种) —— 解决“对象的生老病死”
| 设计模式 | 核心原理 | 解决什么问题(痛点) |
|---|---|---|
| 单例 (Singleton) | 确保全局唯一实例,提供唯一访问点。 | 只想要一个:解决资源冲突(如全局配置、线程池)。 |
| 原型 (Prototype) | 通过拷贝(克隆)现有实例来创建新对象。 | 想克隆对象:解决复杂对象创建开销大或初始化慢。 |
| 建造者 (Builder) | 将复杂对象的构建与表示分离,按步创建。 | 创建复杂对象:解决构造函数参数爆炸、顺序易错。 |
| 工厂方法 (Factory Method) | 定义创建接口,由子类决定实例化哪一个产品。 | 让子类决定创建谁:解决单一种类产品的扩展性问题。 |
| 抽象工厂 (Abstract Factory) | 提供创建一系列相关或相互依赖产品族的接口。 | 创建产品族:解决配套产品(如整套皮肤、整机零件)的统一创建。 |
2. 结构型模式 (7种) —— 解决“类与类的胶水问题”
| 设计模式 | 核心原理 | 解决什么问题(痛点) |
|---|---|---|
| 外观 (Facade) | 为子系统提供统一的高层入口界面。 | 简化接口:解决子系统太复杂,客户端不想了解内部细节。 |
| 代理 (Proxy) | 为对象提供代理以控制其访问。 | 控制访问:解决非业务逻辑侵入(如加缓存、权限、日志)。 |
| 适配器 (Adapter) | 转换接口使不兼容的类可以协作。 | 接口不通用:解决旧接口不兼容,实现“老树嫁新枝”。 |
| 享元 (Flyweight) | 通过共享池技术支持大量细粒度对象。 | 共享节省内存:解决百万相似对象导致的内存溢出。 |
| 桥接 (Bridge) | 抽象与实现分离,使二者独立扩展。 | 两个维度变化:解决多维度扩展导致的类爆炸。 |
| 组合 (Composite) | 将对象组合成树形结构表示整体与部分。 | 树形结构:解决递归处理繁琐,让操作整体像操作个体。 |
| 装饰 (Decorator) | 动态地给对象添加额外职责。 | 动态加功能:解决继承过于臃肿,实现功能的透明包装。 |
3. 行为型模式 (11种) —— 解决“对象怎么打交道”
| 设计模式 | 核心原理 | 解决什么问题(痛点) |
|---|---|---|
| 中介者 (Mediator) | 封装对象间交互,解除多对多耦合。 | 网状变星状:解决对象间牵一发而动全身的强耦合。 |
| 迭代器 (Iterator) | 顺序访问聚合对象元素而不暴露内部。 | 统一遍历方式:解决不暴露容器内部结构下的数据访问。 |
| 访问者 (Visitor) | 数据结构稳定,在外部定义新操作。 | 数据结构与操作解耦:解决数据稳、算法多变的难题。 |
| 策略 (Strategy) | 定义算法族并封装,使其可相互替换。 | 算法可切换:解决 if-else 过多,支持算法独立演进。 |
| 模板方法 (Template Method) | 定义算法骨架,具体步骤延迟到子类。 | 固定算法步骤:解决重复代码,保留通用流程。 |
| 观察者 (Observer) | 一对多依赖,状态改变自动通知。 | 一对多通知:解决状态分发的硬编码耦合。 |
| 备忘录 (Memento) | 捕获并恢复对象内部状态。 | 保存与恢复:解决“后悔药”问题,记录并回滚状态。 |
| 命令 (Command) | 将请求封装为对象,支持排队、撤销。 | 解耦请求与执行:解决请求的记录、撤销和重做。 |
| 状态 (State) | 行为随内部状态改变而改变。 | 状态改变行为:解决复杂状态机判定(消除 switch)。 |
| 解释器 (Interpreter) | 定义文法并构建解析逻辑。 | 特定语言文法:解决自定义规则解析(如公式、脚本)。 |
| 责任链 (CoR) | 请求沿链条传递,直到有对象处理。 | 多个对象处理请求:解决发送者不知道谁能处理的问题。 |
3、23种设计模式深度剖析(含Android源码示例)
创建型模式
-
单例 (Singleton)
- Android 体现:
Application的静态实例;InputMethodManager.getInstance();LayoutInflater.from(context)返回与特定 Context 关联的单例(实为作用域复用)。 - 补充:除了常规懒汉、饿汉、双重校验锁,注意
ViewModelProvider的缓存机制(虽然不是单例,但属于作用域内的复用)。
- Android 体现:
-
原型 (Prototype)
- Android 体现:
Bundle的深拷贝 (Bundle#deepCopy());Intent的克隆;Bitmap的copy方法。 - 注意:浅拷贝与深拷贝的区别。
- Android 体现:
-
建造者 (Builder)
- Android 体现:
AlertDialog.Builder、NotificationCompat.Builder、OkHttpClient.Builder。 - 面试常问:为什么用 Builder 而不用多参构造?答:参数可选、不可变对象、链式调用可读性高。
- Android 体现:
-
工厂方法 (Factory Method)
- Android 体现:
BitmapFactory.decodeXXX静态工厂方法;Fragment.instantiate;LayoutInflater的inflate方法(根据 XML 标签创建 View)。
- Android 体现:
-
抽象工厂 (Abstract Factory)
- Android 体现:
MediaPlayerFactory在底层根据文件类型创建不同MediaPlayer实现;FragmentFactory用于创建不同 Fragment;OkHttp的Interceptor和CallAdapter工厂(如Retrofit的CallAdapter.Factory)。
- Android 体现:
结构型模式
-
外观 (Facade)
- Android 体现:
Context是一个巨大的外观,封装了ActivityManager、PackageManager、WindowManager等系统服务。
- Android 体现:
-
代理 (Proxy)
- Android 体现:Binder 的代理(AIDL 生成的
Proxy类)是远程代理;Glide的缓存机制也用到了虚拟代理(先加载缓存,再加载原图)。
- Android 体现:Binder 的代理(AIDL 生成的
-
适配器 (Adapter)
- Android 体现:
RecyclerView.Adapter将数据集合适配为RecyclerView可用的ViewHolder列表;ArrayAdapter适配数组到 ListView。
- Android 体现:
-
享元 (Flyweight)
- Android 体现:
Message.obtain()复用 Message 对象;Editable.Factory通过享元缓存Editable实例;TypedArray内部缓存了已解析的样式资源;EventBus的SubscribeMethod缓存。
- Android 体现:
-
桥接 (Bridge)
- Android 体现:
View与Window/WindowManager的关系:View是抽象,Window是实现(例如 PhoneWindow 和 其他 Window),两者独立扩展。
- Android 体现:
-
组合 (Composite)
- Android 体现:
ViewGroup与View的关系,ViewGroup既可以是容器也可以是叶子(当它没有子 View 时),完美体现了组合模式。
- Android 体现:
-
装饰 (Decorator)
- Android 体现:
ContextWrapper及其子类(如ContextThemeWrapper、Application)层层装饰ContextImpl,增加不同功能;BufferedInputStream装饰FileInputStream。
- Android 体现:
行为型模式
-
中介者 (Mediator)
- Android 体现:
KeyguardManager协调锁屏界面与系统;LiveData可以看作 UI 组件与数据源的中介;InputMethodManager作为软键盘与输入框的中介。
- Android 体现:
-
迭代器 (Iterator)
- Android 体现:
Cursor遍历数据库结果集;ArrayMap的迭代器;List的迭代器。
- Android 体现:
-
访问者 (Visitor)
- Android 体现:
Element和ElementVisitor在编译时注解处理中常见(如AbstractProcessor访问语法树节点);PackageParser解析 Manifest 时访问组件。
- Android 体现:
-
策略 (Strategy)
- Android 体现:动画插值器
Interpolator;ValueAnimator的TypeEvaluator;ThreadPoolExecutor的拒绝策略;Retrofit的Converter。
- Android 体现:动画插值器
-
模板方法 (Template Method)
- Android 体现:
AsyncTask的四个方法(onPreExecute、doInBackground等);Activity的生命周期方法;View的draw流程(onDraw由子类实现)。
- Android 体现:
-
观察者 (Observer)
- Android 体现:
LiveData、RxJava、View.setOnClickListener(虽然接口回调是一种简化,但本质是观察者模式);ContentObserver监控数据变化;Adapter.notifyDataSetChanged()。
- Android 体现:
-
备忘录 (Memento)
- Android 体现:
onSaveInstanceState保存状态,onRestoreInstanceState恢复;EditText的自动保存(Freeze机制);TextView的SavedState类。
- Android 体现:
-
命令 (Command)
- Android 体现:
Runnable可以看作命令对象;Handler的Message携带命令;UndoManager在文本编辑中的实现;PendingIntent跨进程执行命令。
- Android 体现:
-
状态 (State)
- Android 体现:
ConnectivityManager的网络状态变化;View的Enabled、Pressed状态变化导致绘制行为不同(StateListDrawable也是一种状态模式的应用);WiFi 管理(连接中/断开/连接)。
- Android 体现:
-
解释器 (Interpreter)
- Android 体现:正则表达式引擎;
PackageManager的权限解析;Resource中的@string占位符解析;PackageParser解析 AndroidManifest.xml。
- Android 体现:正则表达式引擎;
-
责任链 (CoR)
- Android 体现:
ViewGroup的事件分发(onInterceptTouchEvent→ 子View→ 父View);OkHttp的拦截器链;Handler的消息处理链(Looper→Handler)。
- Android 体现:
三、相似模式对比
1. 代理 (Proxy) vs 装饰 (Decorator)
- 外观相似:都持有目标对象的引用,且与目标对象实现相同的接口。
- 核心差异:
- 代理模式:侧重于“控制访问”。代理类通常在内部自己创建目标实例,客户端甚至不知道目标对象。主要用于 AOP(日志、权限、跨进程 AIDL)。
- 装饰模式:侧重于“动态加功能”。装饰类由外部传入目标实例,目的是在不改变原类的情况下,通过“套娃”层层增强功能。如
ContextWrapper装饰Context。
- Android 示例:
- 代理:
ActivityManager.getService()返回的是IActivityManager的 Binder 代理。 - 装饰:
BufferedInputStream装饰FileInputStream。
- 代理:
2. 工厂方法 (Factory Method) vs 抽象工厂 (Abstract Factory)
- 工厂方法:一个工厂只产一种产品。例如:BMWFactory 只产 BMW。
解决:将对象的创建延迟到子类,符合开闭原则。 - 抽象工厂:一个工厂产一个产品族(多类相关产品)。例如:AppleFactory 产 iPhone、iPad、Mac。
解决:配套产品的统一创建,防止产品“配错套”。 - 注意:抽象工厂通常由多个工厂方法组合而成。
3. 适配器 (Adapter) vs 外观 (Facade)
- 适配器:转换接口。目的是把 A 接口变成 B 接口,解决“不兼容”问题。
- 外观:简化接口。目的是把 10 个复杂子接口包装成 1 个简单接口,解决“调用难”问题。
- Android 示例:
- 适配器:
ListAdapter将数组适配为ListView可用的数据项。 - 外观:
MediaPlayer封装了音频解码、输出等复杂子系统,对外只提供start、stop等简单方法。
- 适配器:
四、消灭 if-else 三剑客:策略、状态、责任链
这三个模式都能消除臃肿的逻辑判断,但适用的业务逻辑维度完全不同。
| 维度 | 策略模式 (Strategy) | 状态模式 (State) | 责任链模式 (CoR) |
|---|---|---|---|
| 核心目的 | 算法替换 | 行为随状态变迁 | 请求的传递与处理 |
| 消除什么 | 消除平级并列的 if-else | 消除状态转换的 switch-case | 消除处理优先级的嵌套 if |
| 判定权利 | 客户端决定用哪种策略 | 环境类/状态类自身控制跳转 | 处理节点决定处理或下传 |
| 相互关系 | 各策略彼此独立,无关联 | 状态间有转换逻辑 | 节点间构成链式引用 |
| Android 举例 | 动画的 Interpolator | WiFi 管理(连接中/断开/连接) | View 的事件分发机制 |
面试总结口诀:
- 横向并列选策略(比如:支付渠道、折扣算法)。
- 内部流转选状态(比如:订单流转、下载任务)。
- 纵向过滤选责任(比如:登录校验链、OkHttp 拦截器)。
实战建议:
- 策略模式:适用于同一行为的不同实现,且客户端可以主动选择。例如支付渠道(微信、支付宝、银联),将每种支付逻辑封装成策略,通过工厂或 Map 动态获取,消除
if (type == WECHAT)。 - 状态模式:适用于对象的行为依赖于其内部状态,且状态可以流转。例如订单状态(待支付→已支付→已发货),状态转换逻辑封装在状态类中,避免在业务类中写满
if (state == PAID) { ... }。 - 责任链模式:适用于请求需要经过多个处理者,但处理者未知或动态变化。例如用户输入校验(非空、长度、格式),可以构建一个校验链,每个节点负责一项校验,通过则传给下一节点。
五、Android 源码考点直击(加分项汇总)
- 单例:
InputMethodManager.getInstance()、LayoutInflater.from(context)、Application实例。 - 原型:
Bundle#deepCopy()、Intent克隆。 - 建造者:
AlertDialog.Builder、NotificationCompat.Builder、OkHttpClient.Builder。 - 工厂方法:
BitmapFactory.decodeXXX、Fragment.instantiate。 - 抽象工厂:
Retrofit的CallAdapter.Factory、MediaPlayer底层工厂。 - 外观:
Context封装系统服务。 - 代理:Binder 的 Proxy 类(AIDL 自动生成)。
- 适配器:
RecyclerView.Adapter、ArrayAdapter。 - 享元:
Message.obtain()、TypedArray样式缓存。 - 桥接:
View与Window。 - 组合:
ViewGroup与View。 - 装饰:
ContextWrapper、BufferedInputStream。 - 中介者:
KeyguardManager、LiveData。 - 迭代器:
Cursor、ArrayMap迭代器。 - 访问者:编译时注解处理
ElementVisitor。 - 策略:
Interpolator、TypeEvaluator、线程池拒绝策略。 - 模板方法:
AsyncTask、Activity生命周期。 - 观察者:
LiveData、RxJava、OnClickListener、ContentObserver。 - 备忘录:
onSaveInstanceState。 - 命令:
Runnable、Handler.Message、PendingIntent。 - 状态:
ConnectivityManager网络状态、StateListDrawable。 - 解释器:正则表达式、
PackageParser解析 Manifest。 - 责任链:
ViewGroup事件分发、OkHttp拦截器。
六、面试准备建议
- 结合项目举例:不要只背诵定义,每个模式都想一个自己项目中的实际应用(哪怕简单场景),并说明为什么选择该模式,带来了什么好处(可维护性、扩展性等)。
- 画 UML 图:面试中如果能简单画出类图,会大大加分。例如解释代理模式时,手绘一个 Proxy 和 RealSubject 的类图。
- 理解“组合优于继承”:很多模式(如装饰、策略)都体现了这一原则,可以在多个模式分析中提及,展示深度。
- 区分设计原则与设计模式:原则是指导思想,模式是具体实现方案。例如开闭原则可以通过策略模式、装饰模式等来实现。
- 准备一道综合题:比如“如何设计一个图片加载框架?”,可以从单例(缓存管理器)、策略(加载策略)、代理(缓存代理)、观察者(下载进度)、建造者(请求参数配置)等多个角度回答。
特别说明(面试避坑):
- 工厂方法 vs 抽象工厂:工厂方法针对的是一个产品(如:造手机的工厂),而抽象工厂针对的是产品族(如:造手机、造耳机、造电脑的全能工厂)。
- 代理 vs 装饰:代码结构相似,意图不同。代理控制访问,装饰增强功能。