设计模式与 Android 源码应用
设计模式几乎每场中级面试都问,且面试官爱问**“在 Android 源码/常用库里的实际应用”**——光背定义不够,要能举出框架里的真实例子。本篇按这个思路组织。
一、六大设计原则(SOLID)
| 原则 | 核心含义 | Android 反例 → 正例 | 面试落点 |
|---|---|---|---|
| 单一职责(SRP) | 一个类只承担一个变化原因 | Activity 同时写 UI、网络、缓存、埋点 → 拆成 ViewModel、Repository、Tracker | 不是“类越小越好”,而是变更原因隔离 |
| 开闭原则(OCP) | 对扩展开放,对修改关闭 | 支付页新增渠道就改 when(type) → 抽 PaymentStrategy,新增渠道只加实现 | 用多态/注册表替代反复改老代码 |
| 里氏替换(LSP) | 子类可替换父类而不破坏调用方预期 | 自定义 View 重写 onMeasure 却不尊重 MeasureSpec → 保持父类契约 | 继承要遵守父类语义,否则组合优先 |
| 接口隔离(ISP) | 接口最小化,不强迫实现无用方法 | UserModuleService 同时暴露登录、头像、支付状态 → 拆 AuthService/ProfileService | 组件化接口下沉时尤其重要 |
| 依赖倒置(DIP) | 高层依赖抽象,不依赖具体实现 | ViewModel 直接 new RetrofitApi → 依赖 UserRepository 接口,由 Hilt/工厂注入实现 | DI 的理论基础,也是可测试性的基础 |
| 迪米特法则 | 最少知识,只和直接朋友通信 | 页面跨模块拿 OrderManager.userManager.tokenStore.token → 通过 AuthService.getToken() | 减少调用链泄漏,降低跨模块耦合 |
Android 重构口诀:Activity 变薄(SRP),新增业务走扩展点(OCP),继承守契约(LSP),跨模块接口要小(ISP),高层依赖接口(DIP),不要跨层级“摸内部对象”(迪米特)。
二、创建型模式
单例(最高频)
确保一个类只有一个实例。Kotlin 用 object 天然单例。Java 重点是双重检查锁(DCL):
class Singleton private constructor() {
companion object {
@Volatile private var instance: Singleton? = null
fun get(): Singleton =
instance ?: synchronized(this) {
instance ?: Singleton().also { instance = it }
}
}
}
- volatile 的作用:防止指令重排导致拿到未初始化完成的对象。
- Android 应用:
Application、各种 Manager(getSystemService 返回的)、OkHttpClient 单例。
工厂 / 抽象工厂
封装对象创建,调用方只描述“我要什么”,不关心具体构造细节。
- 角色:调用方(Client)→ 工厂(Factory)→ 具体产品(Product)。
- Android 应用:
BitmapFactory.decodeStream()根据输入流/Options 创建Bitmap;LayoutInflater.from(context)根据Context找到合适 inflater;Executors根据方法创建不同线程池。 - 为什么匹配:调用方不直接
new Bitmap(...),而把复杂创建逻辑、兼容分支、缓存/复用细节交给工厂。
建造者(Builder)
链式构造复杂对象,适合可选参数多、构建过程需要校验的场景。
- 角色:
Builder暂存配置,build()/show()生成最终对象。 - Android 应用:
AlertDialog.Builder.setTitle().setPositiveButton().show()、OkHttpClient.Builder.addInterceptor().build()、Retrofit.Builder.baseUrl().addConverterFactory().build()、Notification.Builder、Glide 请求构建。 - 为什么匹配:避免超长构造函数,把“配置过程”和“不可变/可执行对象”分离;面试讲 OkHttp/Retrofit 时可顺带讲 Builder。
原型(Prototype)
通过拷贝已有对象创建新对象,适合“保留大部分配置,只改少数字段”。Android 应用:Intent.clone()/复制 Bundle 后修改 extras。它匹配原型模式,因为新对象来自已有对象快照,而不是从零组装。
三、结构型模式
代理(Proxy)
为真实对象提供一个代理入口,在调用前后做控制、延迟、跨进程或增强。
- 角色:接口 Subject、真实对象 RealSubject、代理 Proxy。
- Retrofit:
retrofit.create(Api::class.java)用Proxy.newProxyInstance生成接口实现;调用api.getUser()时进入InvocationHandler,解析注解并创建 HTTP 请求。 - Binder AIDL:客户端拿到的是
Stub.Proxy,方法调用先写入Parcel,再通过 Binder 驱动发给服务端Stub.onTransact()。 - 为什么匹配:调用方以为自己在调普通接口,实际被代理拦截并转成网络/跨进程调用。
适配器(Adapter)
把一个接口转换成调用方需要的另一个接口。
- 角色:目标接口(Target)、被适配者(Adaptee)、适配器(Adapter)。
- RecyclerView.Adapter:数据源可能是
List<Item>,但RecyclerView需要getItemCount/onCreateViewHolder/onBindViewHolder这组接口;Adapter 把数据转换成可复用的ViewHolder绑定流程。 - 为什么匹配:
RecyclerView不直接认识业务数据,只认识 Adapter 契约;业务侧实现契约即可接入框架。
装饰器(Decorator)
在不修改原对象的情况下动态叠加能力。
- 角色:组件接口 Component、被装饰对象 ConcreteComponent、装饰器 Decorator。
- Android 应用:
ContextWrapper持有一个 baseContext并转发大多数调用;ContextThemeWrapper在 base 能力上增加 theme 解析能力。 - 为什么匹配:外部仍按
Context使用,但功能被包装增强;这与继承扩展不同,装饰可以按需组合。
外观(Facade)
提供统一高层接口,隐藏子系统复杂度。Android 应用:Context.getSystemService() 把 ActivityManager、ClipboardManager、LayoutInflater 等系统能力收口到统一入口;调用方不需要知道服务发现和 Binder 细节。它匹配外观模式,因为 Context 是简化入口,不是具体业务实现本身。
四、行为型模式
观察者(Observer,高频)
一对多依赖,被观察者状态变化时通知观察者。
- 角色:Subject 保存观察者列表,Observer 接收回调。
- Android 应用:
LiveData.observe(owner) {}在生命周期活跃时通知观察者;OnClickListener是 View 事件的观察回调;Adapter.notifyDataSetChanged()通知 RecyclerView 数据变化。 - 为什么匹配:数据/事件源不直接依赖具体页面逻辑,只通知已注册观察者;注意 Flow 更偏响应式流,面试可类比观察者但要说明它还有冷/热流、背压、协程上下文语义。
责任链(Chain of Responsibility,高频)
请求沿链传递,每个节点决定处理、增强或继续传递。
- OkHttp:
RealInterceptorChain.proceed(request)把请求交给下一个拦截器;应用拦截器、重试重定向、桥接、缓存、连接、CallServer 等节点逐层处理。 - View 事件分发:
Activity.dispatchTouchEvent → ViewGroup.dispatchTouchEvent/onInterceptTouchEvent → View.dispatchTouchEvent/onTouchEvent,每层决定拦截、消费或继续下发/回传。 - 为什么匹配:发送方不需要知道最终谁处理,链上每个节点只关心自己职责;这也是和 12 篇 OkHttp 的跨章重复点,这里讲模式原因即可。
策略(Strategy)
封装可互换算法,调用方依赖统一接口。Android 应用:属性动画依赖 TimeInterpolator.getInterpolation(input),线性、加速、回弹等插值器都可替换。它匹配策略模式,因为动画框架不改主流程,只替换“时间进度如何映射为动画进度”的算法。
模板方法(Template Method)
父类/框架定义流程骨架,子类填充步骤。Android 应用:ActivityThread/框架按生命周期调度,业务 Activity 重写 onCreate/onStart/onResume;自定义 View 重写 onMeasure/onDraw。它匹配模板方法,因为整体流程由框架控制,应用只实现钩子步骤。AsyncTask 也体现该模式但已废弃,面试只作历史例子。
命令(Command)
把请求封装成对象,便于排队、延迟、取消或跨线程传递。Android 应用:Handler.post(Runnable) 把一段动作封装进 MessageQueue;Message.what/obj/callback 描述要执行的命令。它匹配命令模式,因为发送方只投递命令对象,执行时机由 Looper 决定。
其他
- 迭代器:集合的
Iterator。 - 备忘录:
onSaveInstanceState。
五、记忆法:按“框架反推模式“
面试被问设计模式,从你熟的框架反推最稳:
- OkHttp 拦截器 → 责任链
- Retrofit create → 代理(动态代理)
- 各种 Builder → 建造者
- LiveData/Flow → 观察者
- RecyclerView.Adapter → 适配器
- 事件分发 → 责任链
- Glide.with → 建造者 + 单例
高频面试题
Q1:手写一个线程安全的单例。
用 DCL(双重检查锁)+ volatile,或静态内部类(依赖 JVM 类加载的线程安全初始化 + 懒加载),Kotlin 直接 object。volatile 防止指令重排拿到半初始化对象。
Q2:DCL 里 volatile 为什么必须加?
instance = Singleton() 不是原子操作,分为分配内存、初始化、赋值引用三步。指令重排后其他线程可能拿到已赋值但未初始化完成的对象。volatile 禁止重排。
Q3:OkHttp 用了什么设计模式? 责任链(拦截器链,核心)、建造者(OkHttpClient.Builder)、工厂、外观。重点讲责任链:请求依次经过各拦截器,每个调 chain.proceed 传递。
Q4:Retrofit 怎么用设计模式把接口变成实现? 动态代理(代理模式)。create() 用 Proxy.newProxyInstance 生成接口代理,方法调用被 InvocationHandler 拦截,解析注解构造请求。
Q5:观察者模式在 Android 哪里用到? LiveData/Flow 的数据观察、RxJava、事件监听器、BroadcastReceiver。一对多,被观察者变化时通知所有观察者。
Q6:事件分发是什么设计模式? 责任链。事件沿 View 树(Activity→ViewGroup→View)传递,每层决定拦截处理还是向下/向上传递。
Q7:MVC/MVP/MVVM 算设计模式吗? 算架构模式(架构层面的模式),不是 GoF 23 种设计模式。MVVM 内部用到观察者(数据绑定)。区分“设计模式“(类级别)和“架构模式“(应用级别)。