Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

设计模式与 Android 源码应用

设计模式几乎每场中级面试都问,且面试官爱问**“在 Android 源码/常用库里的实际应用”**——光背定义不够,要能举出框架里的真实例子。本篇按这个思路组织。

一、六大设计原则(SOLID)

原则核心含义Android 反例 → 正例面试落点
单一职责(SRP)一个类只承担一个变化原因Activity 同时写 UI、网络、缓存、埋点 → 拆成 ViewModelRepositoryTracker不是“类越小越好”,而是变更原因隔离
开闭原则(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 持有一个 base Context 并转发大多数调用;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 内部用到观察者(数据绑定)。区分“设计模式“(类级别)和“架构模式“(应用级别)。