设计模式这题最尴尬的地方在于:答"是什么"人人能背,答"为什么"就开始分化。GoF 二十三个模式的名字能报一遍,但面试官真正想听的是——这个模式解决了什么变化带来的问题。Android 语境下,模式不是背出来的,是从"代码要改很多处"这类具体痛点里长出来的。
先把结论放在前面:设计模式是对反复出现的设计问题的解法总结,它解决的是"耦合"与"变化"两件事。三大分类在 Android 语境下各有典型:创建型解决"谁来做、怎么做",典型是工厂与单例;结构型解决"怎么组合",典型是适配器与装饰器;行为型解决"对象之间怎么协作、职责怎么分",典型是观察者与策略。判断该不该用模式的标准只有一个:这段代码是不是会因为变化而反复改。
机制拆解
拿最常被问的三个模式说清机制。单例保证一个类在进程内只有一个实例,核心是"私有构造 + 全局访问点",Android 里的天然落点就是 Application(进程内天然唯一)与各种 Manager 类。 工厂把"创建哪一类"从调用方挪到工厂类,调用方只依赖接口或类型参数,Android 里的落点是 ViewModelProvider.Factory、ViewModelProvider.AndroidViewModelFactory,以及各种解析器按类型注册后统一 create 的注册表写法。 观察者定义一对多的订阅关系,被观察者状态变化时通知所有订阅者,Android 里的落点遍布各处——LiveData/StateFlow、自定义的下载进度回调、Handler 配合的弱引用监听器集合,甚至 OnSharedPreferenceChangeListener。
策略模式是 Android 里性价比最高的一种:把"这块行为要能被替换"抽成一个接口,调用方持有接口引用而不是实现类。它和工厂常配合——策略解决"用哪个行为",工厂解决"造哪个对象"。 模板方法在 Android 里体现为 Activity/Fragment 的生命周期回调:框架把流程定好(onCreate → onStart → onResume),开发者填内容。装饰器对应各种流与包装:BufferedInputStream 包 InputStream、OkHttp 的 Interceptor 链层层包装原请求。
这些坑的正确绕法
最常见的坑是"为了用模式而用模式",把所有类都套上工厂接口,三层跳转只为创建一个对象。 表现是排查一个崩溃要从 Activity 一路点到工厂再点到实现,日志里全是工厂方法,调试成本陡增;更严重的是新增一个实现类要改五个文件。根因是把"变化点"当成了"现在就有需求"。判断方法是问自己:这个创建点现在有第二种实现吗?将来半年内会有吗?如果只有一个实现且短期不会变,直接 new 最清晰。
其次是把职责拆错,模式用对了但边界错了。 例如在 Activity 里既写界面逻辑又发网络请求又解析数据,无论怎么套模式都还是乱。Android 上的常见反倒是"过度分层":一个简单列表为了"可扩展"拆成 Model / DataSource / Repository / UseCase 四层,实际每层只有一个实现,层间层层转发,改一行要动五个文件。模式解决的是变化,不是层次。
还有一个更隐蔽的坑:单例持有 Context,内存泄漏。 static 字段持有 Activity 或 View 的引用会让整棵视图树无法回收,Activity 销毁后仍然驻留。 Android 上单例应持有 Application(或通过 getApplicationContext() 拿),需要绑定 Activity 时用弱引用 + 生命周期感知,或干脆改用 ViewModel 承载这类"活到界面结束"的状态。面试里能主动说出"单例不是万能的,作用域要匹配生命周期",就比只会背"私有构造 + 双重检查锁"高一档。
代码里见真章
看一段能直接跑的代码,把上面的机制落到具体写法上:
// 策略:把可替换的行为抽成接口,调用方只依赖接口
interface RetryPolicy {
boolean shouldRetry(int attempt, IOException e); }
class ExponentialPolicy implements RetryPolicy {
public boolean shouldRetry(int attempt, IOException e) {
return attempt < 3 && !(e instanceof SSLException); // 证书类不重试
}
}
class Downloader {
// 只依赖接口,不感知具体实现
private final RetryPolicy policy;
Downloader(RetryPolicy policy) {
this.policy = policy; }
}
// 工厂:注册表写法,按类型创建,避免每加一个就改一处
class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<?>, Supplier<ViewModel>> registry = new HashMap<>();
ViewModelFactory register(Class<?> type, Supplier<ViewModel> creator) {
registry.put(type, creator); return this; // 链式注册
}
@SuppressWarnings("unchecked")
@Override public <T extends ViewModel> T create(Class<T> modelClass) {
Supplier<ViewModel> s = registry.get(modelClass);
if (s == null) throw new IllegalArgumentException("未注册: " + modelClass);
return (T) s.get();
}
}
// 观察者:保留强/弱引用要显式选择,否则泄漏
class DownloadListeners {
private final List<WeakReference<Listener>> refs = new ArrayList<>();
public void add(Listener l) {
refs.add(new WeakReference<>(l)); // 弱引用避免泄漏界面
if (refs.size() > 20) purge(); // 顺手清掉已回收的
}
public void notifyProgress(int p) {
for (Iterator<WeakReference<Listener>> it = refs.iterator(); it.hasNext();) {
Listener l = it.next().get();
if (l == null) it.remove(); else l.onProgress(p);
}
}
}
这段代码值得盯三处:第一处,RetryPolicy 把"能不能重试"的判断从 Downloader 里抽走,新增策略不需要动下载逻辑;第二处,工厂用注册表而不是 if-else 类型链,加一个 ViewModel 只需多一行注册;第三处,观察者列表用 WeakReference 并在通知时顺手清理 null,这是 Android 场景下的必要工程细节。
这题在面试里怎么问、怎么答
"设计模式解决了什么问题?"核心答一句话:封装变化,让依赖倒转——调用方依赖抽象而非实现,从而在实现替换时不影响调用方。 展开说:创建型把"谁创建"与"用什么创建"分开;结构型让组合方式可替换;行为型把"做什么"与"怎么做"分开。
"策略和工厂怎么配合?""单例和依赖注入冲突吗?""为什么 Kotlin 里少用单例?"三个高频追问。策略+工厂已在代码里演示。单例 vs DI:DI 容器管理生命周期与依赖关系,更易测试(可以注入假实现),单例只是"全局唯一"这一件事,混用时容易出现隐藏依赖,测试时无法替换。 Kotlin 少用单例是因为可以用 object 声明或把状态放进 ViewModel/StateFlow,作用域更明确。
"说一个你在项目里用设计模式的例子。"要讲清四段:痛点(哪段代码改起来很痛)、选型(为什么是这个模式不是别的)、代价(多了哪些文件与跳转、测试怎么写)、结果(改动收敛在某几个文件、回归范围变小)。只说"用了工厂模式"的答案会被追问穿。
"使用中遇到过什么问题?"案例一:某模块的支付实现从"直接 new"改成策略接口并加注册表,收益是新增第二家支付只改注册处,但代价是调试时要多看一层;结论是只为确定的会变点加抽象。案例二:一个静态工具类持有 Activity 导致连续打开详情页后内存不降;定位到静态字段捕获了界面上下文;修复为改为弱引用 + 生命周期感知回调。
再补一个工程上值得讲清的点:Android 有大量"框架已经替你实现了模式"的地方。Activity 生命周期是模板方法、RecyclerView.Adapter 是观察者、ListAdapter 加了 DiffUtil 是策略的体现、ViewPager2 的回调链是责任链。 识别出这些既省代码又少踩坑——ListAdapter 就是典型:自己实现 notifyItemRangeInserted 容易算错位置,而 DiffUtil 在后台线程算完差异再一次性刷新,这是框架替模式兜了坑。面试里能说出"哪些模式框架已经做了",比背二十三个名字更有价值。
给正在准备面试的你
这题可以画成一张"痛点 → 模式"映射图:左侧列五个真实痛点(对象创建太散、类型判断 if-else 越写越长、行为要可替换、状态变化要通知多处、层次过深看不清),右侧列对应的模式(工厂/单例、注册表+策略、策略+模板方法、观察者、组合优先),中间用虚线连接。虚线的粗细表示"该模式解决的痛点强度"——某些连线很细,说明那里其实不需要模式。面试中讲设计模式,图的右边通常要能画出来,左边才是加分点。
再补工程案例与踩坑——应用落点是把项目里 if (type == A) new A() else if (type == B) new B() 的类型分支收成注册表或策略接口,审查所有静态字段是否捕获了界面上下文,并为工厂类补上"未注册类型"这个分支的明确报错。
复习时别孤立刷题:单例模式六种写法——单例是创建型模式里最常被追问写法细节的那个。
划两句重点:模式解决的是"会变的地方",只为确定的变化点加抽象;策略抽行为、工厂管创建、观察者做通知;Android 上单例必须持 Application 而非界面上下文,优先用框架已经实现的模式。
下一篇聊单例模式六种写法:线程安全与懒加载的平衡——沿着今天这条主线继续往前走。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。你的支持就是这个系列持续更新的动力。
「Android软件开发面试·从入门到精通」连载系列
上一篇:新时间-API:LocalDateTime-取代-Date-的理由
下一篇预告:单例模式六种写法:线程安全与懒加载的平衡
有任何问题欢迎在评论区留言交流。