假设你是“模式小馆”的餐厅老板。每天中午,几百位客人涌进店里,你每天要操心三件事:第一,客人点单千奇百怪,后厨怎么快速识别订单、批量做出对应的菜品?第二,后厨只能有一位主厨说了算,如果每个人都来指挥,厨房准得打起来;第三,菜做好之后,怎么让所有服务员第一时间知道“三号桌的鱼香肉丝好了”,而不是扯着嗓子挨个喊? 这三个问题,恰好对应了软件世界里最经典的三道“设计模式”硬菜:工厂模式(Factory)、单例模式(Singleton)和观察者模式(Observer)。设计模式,就是无数程序员前辈把反复踩过的坑和验证有效的解法,总结成的一本“行业菜谱”。今天我们就走进模式小馆的后厨,把这本菜谱上的三道硬菜拆开揉碎了讲清楚,看完你会发现:原来写代码和开餐厅,操心的是同一类事。
如果只能从设计模式里挑三个最常用的带进团队,那必然是工厂、单例和观察者。读懂它们,你就能在代码评审中看懂一半的套路,把杂乱的类组织成分工明确的流水线——这是从写代码迈向做工程的关键一步。
目录
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
设计模式总览:一本不炒菜的菜谱
模式小馆的后厨墙上贴着一张发黄的手写纸,上面不是菜名,而是“如何应对催单”“突然停电怎么办”“新来的帮厨怎么快速上手”——这些全是老师傅们踩过无数次坑之后总结的救火指南。软件设计模式就是程序界的这张纸:它不直接炒菜(不写业务代码),但它告诉你在什么情况下用什么锅、什么火候,以及为什么这么做不会糊锅。
设计模式,是前辈们在长期工程实践中总结出的、针对反复出现的特定问题的成熟解决方案模板。一个完整的模式包含三个要素:名称(方便团队交流时喊得出名字)、问题(它解决什么场景)、方案(具体怎么落地)。就像菜谱上写着“鱼香肉丝”四个字,背后是“客人想吃酸甜微辣”的问题和“大火快炒、鱼香汁比例”的方案。
设计模式背后还有一套设计原则:单一职责、开闭原则、里氏替换、依赖倒置、接口隔离、迪米特法则。按照用途,模式又分为三大类:创建型(怎么造对象)、结构型(怎么组合对象)、行为型(怎么协作对象)。我们今天的三个主角中,工厂和单例属于创建型,观察者属于行为型。
1. ROI(投入产出比):判断一个模式值不值得引入的指标,分子是省下的时间,分母是花掉的时间,比值大于 1 才划算。
2. 分子 T_saved(省下的维护时间):把用模式避免的重复改代码、排查 bug、新人培训的时间累加起来。
3. 分母 T_cost(投入的成本):学习模式要花时间,多写一层抽象要花时间,未来可能出现的过度设计也要算进去。
主厨常说:“不要为了用菜谱而做菜,要为了解决问题才翻菜谱。”这就是设计模式的第一课:模式是药方,不是保健品。接下来,我们先看第一道硬菜——工厂模式。
工厂模式原理:点单台与备餐流水线
在模式小馆,客人从来不会冲进后厨喊“我要一份五花肉切片后爆炒”,他们只会对点单台说“鱼香肉丝”。点单台把客人的需求翻译成一张标准的厨房工单,后厨照单做菜。客人(调用方)和后厨(具体实现)之间,永远隔着一个点单台——这个点单台,就是工厂。
工厂模式的本质是“用工厂方法代替直接 new”:把创建对象的逻辑从客户端剥离出来,集中到工厂里。它有三个进化阶段。第一阶段是简单工厂:一个工厂类根据传入的参数,用 if-else 或 switch 返回不同的产品。就像点单台只有一位负责划单的老员工,看一眼订单就喊后厨做菜。
第二阶段是工厂方法:定义一个创建对象的接口,让子类决定实例化哪个类。就像点单台拆成了“川菜窗口”“粤菜窗口”,每个窗口自己决定做什么菜。第三阶段是抽象工厂:创建一族相关或依赖的产品,而不只是单个产品。这相当于把“川菜全套菜单”打包成一个方案,一张单子下来,凉菜、热菜、汤全部配齐。
1. f(t)(工厂的选择函数):工厂的核心动作,把“订单类型”映射到“具体产品类”,客户端只认这个函数,不认具体类。
2. t = 1, 2, 3(订单类型标识):调用方传入的区分参数,例如 1 代表“鱼香肉丝”,2 代表“宫保鸡丁”,3 代表“麻婆豆腐”。
3. DishA / DishB / DishC(产品类):工厂根据类型产出的具体对象。DishA 对应一种具体产品,三者都实现了同一个“菜品接口”,所以客户端可以统一对待。
把“选择权”从客户端手里收走,正是工厂存在的意义。客人不需要知道后厨有几个灶台、几点备料,他只需要把单子递给点单台——当未来新增菜品时,也只有点单台需要调整。
工厂模式实战:把菜单变成配置文件
模式小馆的老板发现,每次上新菜都要请工程师来改点单台的收银系统,改一次动一次代码,费时又容易出 bug。于是他想到一个办法:把菜单做成可以替换的卡片,点单台只需读卡片就能认识新菜。换菜单只需要换卡片,不用拆机器——这就是“配置驱动工厂”。
实战中的工厂,讲究“对扩展开放,对修改关闭”:新增一个产品时,不改任何已有的工厂代码,只往配置里加一条记录。具体做法是维护一个“注册表”(一个把名称映射到类的字典),再解析配置文件拿到名称,最后用注册表动态创建对应的类。调用方写的是 factory.create("linear"),至于 linear 背后是哪个类、怎么构造,调用方一概不知。
这么做的收益可以从一个简单的公式看出来:一次扩展,对已有代码的修改量为零。
1. Δmodify(对已有代码的修改量):理想状态下,新增产品不需要动任何已有代码,这个值等于 0。
2. Δextend(新增扩展模块的数量):新功能通过新增一个注册项或一个新类来实现,这个值等于 1。
3. 一起看(开闭原则的数学表达):修改量为 0 而扩展量为 1,意味着系统在“不拆机器”的前提下完成了上新。
工厂实战最大的陷阱是“为工厂而工厂”:一共只有一种产品也硬套工厂,只会多出一层没人看得懂的抽象。记住主厨的话:如果煎一个鸡蛋都要建一条生产线,那厨房迟早被自己造的机器堵死。
单例模式原理:全店只有一位主厨
模式小馆立了一条铁规矩:后厨只能有一位主厨。如果每个帮厨都认为自己是主厨,菜谱会被撕烂,灶台会被抢占,出菜顺序会乱成一锅粥。在软件世界里,“全店只有一个主厨”对应的情况是:某个类在整个程序生命周期中只能有一个实例——这就是单例模式。
单例要解决两个问题:一是全局唯一,二是控制访问入口。它的实现有三种经典姿势。第一种是饿汉式单例:类加载时就创建实例,相当于主厨一开店就站在灶台前。优点是没有线程安全问题,缺点是即使今天没客人,主厨也得白白站一天岗,造成资源浪费。
第二种是懒汉式单例:第一次使用时才创建,相当于客人点菜了主厨才开工。它的代价是要处理并发:两个客人同时点菜,可能同时冲进厨房去找主厨,结果创建出两个“主厨”。解决办法是加锁,但每次都加锁性能太差。于是有了第三种——双重检查锁:先快速看一眼实例是否存在,不存在才加锁,进入锁后再检查一次。
1. 外层判断 s = null(快速通道):先看一眼实例是否已经存在,存在就立刻返回,绝大多数访问都不用抢锁。
2. lock(加锁):只有发现实例还没创建的线程才去抢锁,其他线程在门外排队。
3. 内层二次判断(防重创建):抢到锁后必须再看一次,因为等待期间可能有其他线程已经把实例创建好了。
单例的关键词是“唯一”和“受控”,就像主厨的权力必须由餐厅制度授予,而不是谁嗓门大谁就当主厨。
单例模式实战:主厨上岗与防冒名
主厨就一个,所以他的薪资单、独门配方、排班表都挂在他一个人名下。软件里那些全局共享的东西——数据库连接池、配置中心、日志对象、消息队列客户端——都适合用单例来管理。它们有一个共同点:整个系统只需要一份,而且这份数据要被各处共享。如果每个人各连各的数据库,连接池瞬间被打爆。
不过,单例也有“被冒名顶替”的风险。Java 的反射机制可以强行调用私有构造方法,序列化反序列化也可以绕开单例规则,生成第二个实例。这就像有人伪造了主厨的工牌混进后厨。防御手段包括:在私有构造方法里抛异常、重写 readResolve 方法,最省心的则是直接用枚举定义单例——枚举天然保证全局唯一,反射和序列化都拿它没办法。
衡量单例的访问成本,可以看这个公式:并非每次访问都要完整地走一遍创建流程。
1. T_check(检查开销):判断实例是否已存在的耗时,通常是判断一次引用是否为空,纳米级操作。
2. P_init(首次初始化的概率):只有程序第一次访问时,这个概率为 1,之后都是 0。
3. T_new(创建实例的成本):分配内存、执行构造函数的开销,只在首次访问时支付一次。
实战中还要警惕“单例滥用”:把不需要全局唯一的类也做成单例,等于让一堆假主厨排队进厨房。主厨唯一的理由是“真的只需要一个”,而不是“这样调用方便”。
观察者模式原理:后厨的广播系统
菜做好之后,主厨绝不会端着盘子满大厅找客人,他只会对着对讲机喊一声:“三号桌鱼香肉丝,好了!”所有服务员耳朵一动,该上菜的上菜,该催单的催单。主厨不需要认识每个服务员,服务员也不需要挤在厨房门口——他们之间只靠一句话完成协作。这就是观察者模式。
观察者模式定义了对象之间“一对多”的依赖关系:当一个对象(主题)的状态发生变化时,所有依赖它的对象(观察者)都会自动收到通知。模式小馆里,主题是后厨的广播台,观察者是各位服务员。广播台只维护一张“订阅者名单”,它不需要知道名单上的服务员叫什么、负责哪片区域,只需要在出菜的时候喊一嗓子。
这种设计的核心价值是松散耦合:主题和观察者互相不认识,随时可以增减观察者而不影响主题。实现上,主题维护一个订阅者列表,提供 subscribe(订阅)和 unsubscribe(退订)方法,状态变化时遍历列表逐个通知。通知还分两种模型:推模型(主动把完整数据发给观察者)和拉模型(只通知“变了”,观察者自己来取数据)。就像广播里可以喊“三号桌鱼香肉丝好了”(推),也可以只喊“三号桌,来个人”(拉)。
1. T_sync(同步通知总耗时):主题挨个调用观察者,每个耗时 t_i,总耗时是所有耗时之和。
2. T_async(异步通知总耗时):观察者并行执行时,总耗时不是相加而是取最慢的一个。
3. N(观察者数量):订阅者的个数。同步模型下,每多一个观察者,主题就要多等一份时间。
广播系统的美妙之处在于,主厨喊话时根本不用回头确认谁在听。软件世界的“喊话”同样如此:发布者与订阅者互不打扰,系统才能灵活伸缩。
观察者模式实战:别忘了给服务员解注册
有个服务员离职了,但他的名字还留在广播名单上。每次出菜,他的对讲机照样响——人走了,耳朵还挂在频道上。这在观察者模式里是最典型的坑:忘记解订阅导致的内存泄漏。你以为是小事,积累多了,内存被一堆“幽灵服务员”占满,程序越来越慢最后崩溃。
实战中,观察者模式遍布各种事件监听:按钮点击、网络请求回调、前端数据双向绑定、消息队列的消费端。使用时有三个要点。第一,订阅和退订必须成对出现,创建观察者的地方,一定要在销毁时调用 unsubscribe。第二,警惕通知顺序:多个观察者按注册顺序执行,一个观察者抛异常可能中断整条通知链,所以要捕获异常或定义好容错策略。第三,把同步广播改成异步队列,防止慢观察者拖垮主线程。
可以用一个公式来诊断“内存泄漏”:
1. T_release(本应释放的时间点):业务上这个观察者已经不再需要,垃圾回收应该把它收走。
2. T_unsubscribe(实际解订阅的时间点):程序中真正调用退订方法的时间。如果永远不调用,这个值趋于无穷大。
3. 差值大于零(泄漏条件):当释放晚于解订阅,说明主题还强引用着观察者,对象无法被回收。
给服务员办离职手续,不只是注销工牌,还要把他的名字从广播名单上划掉。观察者模式的优雅,恰恰藏在“记得退订”这个不起眼的细节里。
综合实战演练:配齐一套后厨班子
晚餐高峰的模式小馆,是一台精密协作的机器:客人下单(客户端请求),点单台识别订单(工厂生产对象),主厨统筹全局(单例控制唯一资源),广播通知服务员上菜(观察者分发事件)。三个模式单独看都不复杂,组合起来,才撑得起一整条业务流水线。
实战中,三个模式经常搭配出现。工厂 + 单例:模型工厂本身做成单例,全系统共享同一个工厂实例,避免重复创建工厂的开销。工厂 + 观察者:事件驱动的 UI 框架里,工厂负责创建组件,观察者负责把用户操作广播给业务逻辑。单例 + 观察者:全局事件总线用单例持有订阅者列表,任何模块都能发布和订阅事件。架构的演进路径通常是从“硬编码分支”到“工厂解耦”,再到“观察者驱动”——就像小馆从一人包办,进化到点单台、主厨、服务员各司其职。
衡量这套班子健不健康,可以用内聚与耦合之比:
1. E_internal(内聚度):模块内部元素之间的连接数量,同一件事的代码待在一起,内聚就高。
2. E_external(耦合度):模块与外部世界的连接数量,连接越多,改起来越容易伤及无辜。
3. M(模块独立度):比值越大,说明这个模块“自给自足”的程度越高,是重构追求的方向。
重构时不要为了“用模式”而硬套模式,而是先闻出代码坏味道(大量 if-else 分支、到处 new、通知靠手动挨个调用),再对症下药。模式小馆的老板明白:菜谱是死的,后厨是活的——把菜谱吃透,才能让后厨运转如飞。
AI 应用场景
AI 模型推理工厂
阶段:模型服务化部署
解决的问题:AI 平台同时提供多种模型推理服务(文本分类、人脸识别、OCR),传统方式在接口层写大量 if-else 按请求类型创建模型对象,新增模型必须改核心代码。工厂模式把模型创建收敛到注册表,新模型只需注册一条配置即可上线,就像点单台换了一张新菜单。
具体案例:某 AI 平台提供 “chat“ 与 ”embedding” 两类模型接口,后端用 ModelFactory 统一创建模型实例,运营人员上线新模型时只改 config.json,服务代码零改动。
分布式训练配置中心
阶段:训练与调参
解决的问题:分布式训练时,每个 worker 都要读取相同的学习率、模型路径、数据目录。如果各读各的配置文件,极易出现“两个 worker 用的 batch_size 不一样”的诡异问题。单例模式的配置中心保证全进程只有一份配置对象,任何模块拿到的都是同一份快照。
具体案例:某团队用 PyTorch 跑分布式训练,ConfigCenter 单例在进程启动时加载一次 YAML,所有 rank 的 worker 读取同一份配置,训练结果可复现,不再出现“配置漂移”。
训练指标观察者广播
阶段:训练过程监控
解决的问题:训练循环中,loss、accuracy 需要实时同步给日志系统、可视化面板和自动早停模块。传统做法是在循环里手动逐个调用,代码耦合度高,想新增一个监控项就要改训练主循环。观察者模式让训练循环只负责“广播指标”,订阅者自由增删。
具体案例:某大模型训练任务中,每 100 步通过 MetricsHub 发布一次指标,日志订阅者写盘,监控订阅者推送曲线,早停订阅者判断 loss 是否达标并触发 stopping。
Python 演示
"""设计模式实战: 工厂/单例/观察者在 AI 服务中的应用""" import threading class ConfigCenter: """单例模式: 全局唯一的配置中心""" _instance = None _lock = threading.Lock() def __new__(cls): if cls._instance is None: with cls._lock: if cls._instance is None: cls._instance = super().__new__(cls) cls._instance.model_dir = "./models" cls._instance.batch_size = 32 return cls._instance class BaseModel: def inference(self, x): raise NotImplementedError class LinearModel(BaseModel): def inference(self, x): return sum(x) * 0.1 class TransformerModel(BaseModel): def inference(self, x): return sum(x) * 0.5 class ModelFactory: """工厂模式: 按名称生产模型实例""" registry = {"linear": LinearModel, "transformer": TransformerModel} @classmethod def create(cls, name): if name not in cls.registry: raise ValueError("unknown model: " + name) return cls.registry[name]() class MetricsHub: """观察者模式: 训练指标广播站""" def __init__(self): self._subscribers = [] def subscribe(self, fn): self._subscribers.append(fn) def notify(self, metrics): for fn in self._subscribers: fn(metrics) def main(): cfg = ConfigCenter() other = ConfigCenter() assert cfg is other, "singleton check failed" model = ModelFactory.create("linear") assert isinstance(model, LinearModel) hub = MetricsHub() hub.subscribe(lambda m: print("log:", m)) def early_stop(metrics): if metrics["loss"] < 0.1: print("loss ok, early stop") hub.subscribe(early_stop) for epoch in range(5): metrics = {"epoch": epoch, "loss": 1.0 / (epoch + 2)} hub.notify(metrics) if __name__ == "__main__": main()
if cls._instance is None:双重检查的第一道“快筛”,实例已存在就直接返回,避免每次访问都抢锁。with cls._lock:只有通过快筛的线程才进锁,保证同一时刻只有一个线程进入创建区。cls._instance = super().__new__(cls)用__new__分配内存,绕开__init__的重复初始化。registry = {"linear": LinearModel, "transformer": TransformerModel}工厂的“注册表”,把名称和类的关系集中在一处。return cls.registry[name]()工厂的核心动作:查表、取类、实例化,调用方完全不接触具体类。hub.subscribe(lambda m: print("log:", m))观察者注册:把“打印日志”行为挂到广播站。hub.notify(metrics)发布事件:遍历订阅者列表,一次广播全员收到。
常见误区
误区有人看到代码里出现 new,就条件反射地包一层工厂,觉得“用了工厂 = 代码高级”。结果一个只有一种产品的小类,为了用工厂硬生生多出三个文件,没人看得懂为什么要绕一圈。
正解工厂的核心价值是“封装变化”——当创建逻辑复杂、有多个产品可选、客户端不应依赖具体类时才值得用。餐厅不会为了煎一个鸡蛋专门建一条生产线。判断标准很简单:如果 new 的地方从不变化,就别引入工厂。
为何常见:模式教程大多在讲“怎么写工厂”,很少讲“什么时候不该写”,初学者容易把手段当目的,陷入“拿着锤子看什么都像钉子”。
误区“反正全局唯一,我直接把数据库连接、工具类、甚至一个计算器全做成单例,调用多方便!”结果测试时难以替换,模块之间通过单例暗度陈仓地互相依赖,改一处崩一片。
正解单例是有节制的全局访问点,必须满足“全系统确实只需要一份且存在共享状态”的条件。像主厨只有一个,是因为厨房真的只需要一个总指挥,而不是因为这样调用顺手。不共享状态的方法应该写成静态方法,而不是套单例。
为何常见:单例的 API 太“好用”,静态方法随手一调,掩盖了真实的依赖关系,让代码看起来简洁实则耦合。
误区“双重检查都加锁了,肯定线程安全!”于是漏写了 volatile,结果高并发下偶发拿到一个“半初始化”的对象,排查几天找不出原因。
正解JVM 里 new 对象分三步:分配内存、初始化字段、把引用赋值给变量。编译器可能重排后两步,线程 B 判断引用非空直接返回,拿到的对象字段却还没初始化。volatile 的作用是禁止这种重排并保证可见性——就像主厨人到了,但菜谱还没来得及翻开就被叫去炒菜。
为何常见:只背了“双重检查”四个字,不理解底层内存模型,遇到偶发 bug 很难联想到指令重排。
误区把任何“传一个函数进另一个函数”的行为都叫观察者模式,觉得回调就是观察者,观察者就是回调。
正解观察者模式的关键是主题维护一个订阅者集合,状态变化时遍历通知,支持运行时动态增删订阅者,是一对多的广播。回调通常是一对一的约定:A 完成时通知 B。餐厅对讲机是“一个广播台喊全体服务员”,而不是“主厨单独给某位服务员打电话”。
为何常见:回调先学先用,概念边界被模糊,导致写出的“观察者”只有一对一效果,丢失了广播与解耦的价值。
误区一个类里工厂套单例、单例里挂观察者,类图画得密不透风,看起来“很专业”,结果下一个接手的人光理清关系就花了一周。
正解模式是解决问题的药,不是保健品。KISS(保持简单)原则永远优先:能用一个 if 解决的,绝不用一个模式。好代码是让下一个读代码的人少死脑细胞,而不是炫耀设计技巧。模式小馆的菜谱再厚,主厨也不会把每道菜都用上所有调料。
为何常见:面试题和教程不断渲染模式光环,让新手误以为“模式数量 = 技术水平”,忽略了模式背后真正的目标是可读、可维护、可扩展。
面试题
Q1. 在设计模式的三大分类中,工厂模式属于哪一类?
- A.创建型模式← 正确答案
- B.结构型模式
- C.行为型模式
- D.并发型模式
答案:A。工厂模式解决的是“如何创建对象”的问题,所以属于创建型模式。就像餐厅的备餐系统解决“菜怎么做出来”,它不关心菜端上桌怎么摆盘(结构型),也不关心服务员之间怎么协作(行为型)。
Q2. 简单工厂模式最主要的缺点是什么?
- A.扩展新产品需修改工厂类← 正确答案
- B.产品初始化过程开销大
- C.产品对象无法延迟加载
- D.客户端必须继承工厂类
答案:A。简单工厂把所有产品的创建逻辑集中在一个类里,新增产品必须修改这个类的分支判断,违反开闭原则。就像点单台只有一位老员工,菜单每加一道菜,他都要重新背一遍流程。B、C 都不是简单工厂的必然缺点,D 是错误表述。
Q3. 懒汉式单例如果不做任何同步处理会有什么风险?
- A.多线程可能创建多个实例← 正确答案
- B.类加载时占用过多内存
- C.实例无法被垃圾回收
- D.客户端无法调用静态方法
答案:A。懒汉式是“首次使用才创建”,多个线程同时首次访问时,各自判断实例为空、各自创建,就产生多个实例,违背单例的“唯一”承诺。这就像两个帮厨同时听到“开火”的命令,都冲上去当主厨。B 是饿汉式的缺点,C、D 与并发无关。
Q4. 观察者模式中“主题”的核心职责是什么?
- A.维护订阅列表并广播变化← 正确答案
- B.直接调用每个业务方法
- C.负责具体的数据计算
- D.管理所有对象的生命周期
答案:A。主题(被观察者)只需维护订阅者列表,在状态变化时遍历通知,不需要知道订阅者的具体业务逻辑。就像广播台只负责喊“出菜了”,不负责帮服务员端盘子。B、C、D 都是对主题职责的误解。
Q5. 下列哪些场景适合使用单例模式?
- A.全局唯一的配置管理← 正确答案
- B.数据库连接池对象← 正确答案
- C.日志记录器对象← 正确答案
- D.一次性的数学计算
答案:ABC。配置管理、连接池、日志器都是“全系统只需要一份且被多处共享”的资源,适合单例。一次性的数学计算是无状态行为,用静态方法即可,套单例反而画蛇添足,就像为了煎一个鸡蛋专门设一个主厨岗位。
Q6. 双重检查锁中 volatile 关键字的主要作用是什么?
- A.禁止指令重排并保证可见性← 正确答案
- B.提高方法调用的运行速度
- C.使单例类无法被继承
- D.让所有线程都串行执行
答案:A。创建对象的三步(分配内存、初始化、赋值引用)可能被指令重排,导致线程拿到“半初始化”的实例。volatile 禁止重排并保证修改对所有线程可见。就像主厨虽然到位了,但要保证他的菜谱先翻开再开始炒菜,不能倒着来。
Q7. 以下哪种方式可以防御反射机制破坏单例?
- A.使用枚举来定义单例← 正确答案
- B.将构造方法设为私有
- C.把所有字段声明为公有
- D.增加双重检查锁机制
答案:A。私有构造方法只能挡住普通调用,反射通过 setAccessible(true) 照样能创建新实例;双重检查锁管的是并发,管不住反射。枚举天然保证全局唯一,JVM 层面禁止反射创建枚举实例,是攻守兼备的方案。就像把“主厨任命权”收归制度本身,伪造工牌也进不了后厨。
Q8. 观察者模式在实际项目中可能引发哪些问题?
- A.忘记解订阅导致内存泄漏← 正确答案
- B.通知顺序不确定难排查← 正确答案
- C.观察者异常中断通知链路← 正确答案
- D.主题无法感知状态变化
答案:ABC。主题强引用观察者,忘记解订阅会让对象无法被回收;多个观察者的执行顺序依赖注册顺序,排查麻烦;一个观察者抛异常会中断后续通知。D 说反了,主题自己产生状态变化,不需要“感知”自身变化。
Q9. 工厂方法模式与抽象工厂模式的主要区别是什么?
- A.前者面向产品等级后者产品族← 正确答案
- B.前者不能创建具体产品实例
- C.后者只能创建单一类型产品
- D.前者必须使用接口与抽象类
答案:A。工厂方法定义一个接口让子类决定创建哪个具体产品,解决“产品等级”(同一类型的不同实现)的创建;抽象工厂创建一族相关产品,解决“产品族”(配套的一整套产品)的创建。就像前者决定“这桌是川菜还是粤菜”,后者决定“整桌川菜套餐配什么凉菜热菜汤”。
Q10. 设计模式的三个基本要素包括哪些?
- A.模式名称← 正确答案
- B.适用问题← 正确答案
- C.解决方案← 正确答案
- D.代码注释
答案:ABC。一个完整的模式包含三要素:名称(方便交流)、问题(解决什么场景)、方案(怎么落地)。代码注释不是模式的一部分。就像菜谱三要素是菜名、解决什么口味需求、用什么火候做——缺了任何一个,这张菜谱都没法传下去。