Abstract Factory with Reflection 持续改进,抽象工厂也反射

持续改进,抽象工厂也反射 http://www.cnblogs.com/shenfx318/archive/2007/01/16/621237.html 在我的上一篇文章(疑惑?改良?从简单工厂到工厂方法)中,详细论述了创建模式中简单工厂到工厂方法的演变过程,并试图结合工厂方法的设计以及.net中的反射机制之所长,改良出一种新型的工厂—反射工厂,这当然不是我的首创,经典的PetShop 中便有此工厂的身影。本文尝试按照前篇文章的思路,借着工厂方法到抽象工厂的演变过程而继续对抽象工厂进行改良,文章中的思想仅代表了作者当时的观点,有欠妥的地方,还请各位不吝赐教。 工厂模式 前面的文章提到了简单工厂和工厂方法其实是一码事,他们完成了将客户对产品功能的使用与创建具体产品职责的分割,不同的只不过是他们实现方式上的差异,工厂方法利用更加优雅的多态性取代了相对ugly的switch case…语句,从而很好的体现了设计原则中的OCP原则,此文章将不再强调这种实现上的差异性,而更多的强调两者之间设计思路上的共性,并将这种共性统称成为工厂模式,从而进一步与抽象工厂进行对比。 工厂的使用,选择的过程 工厂模式的使用,实际上是客户 (产品的消费者) 对于产品选择的过程,对于实现了相同功能的产品来讲,客户更加关心的是产品间的差异性,而工厂的作用则是将产品的生产过程封装,根据客户的要求直接返回客户需要的产品。注意,工厂只是隐藏了产品的生产过程,但是并没有剥夺客户选择的权利,那么客户的这个选择过程又是如何体现的呢?在简单工厂中,客户通过参数的形式告诉工厂需要什么样的产品,而在工厂方法中,客户通过对工厂的选择代替了直接对产品的选择,注意到工厂方法中一个工厂只有一个Create方法,也就是说一个工厂只负责生产一种产品,那么你选择了相应的工厂也就等同于你选择了对应的产品。就连改良后的反射工厂也没有消去对产品的选择,只不过是将这种选择外化 (外化到配置文件中,从而使得对代码的改动最小) 。可以说,正是由于产品间的差异性带给了客户选择的权利,这种权利是不应当被工厂取代的,那么工厂模式的价值又在哪里呢?答案是抽象与封装,工厂模式将由于客户的不同选择而可能导致的对已知事物的影响降到最低,途径是通过抽象产品取代具体产品,使得客户依赖于抽象 (越抽象的东西越稳定) ,同时将客户的选择封装到一处,隔离与具体产品间的依赖。 工厂模式与抽象工厂 前面说了这么多无关的,为得是做好铺垫,更加有益于对下文的理解,OK,终于该说说从工厂模式到抽象工厂的转变了,先来对比两张类图: 工厂方法(Factory Method) ![](http://images.cnblogs.com/cnblogs_com/shenfx318/FactoryMethod3.jpg) 抽象工厂(Abstract Factory) 从图中我们能够看到哪些差异? 最明显的一点就是在工厂方法的类关系图中只有一类产品,他们实现了一个统一的接口,而抽象工厂有多个类别的产品,他们分别实现了不同的功能 (多个接口) 。其次的一点差别就是工厂本身所具有的方法数量不同,这点差异其实也是由第一点所导致的,工厂需要有生产不同类别产品的功能,如果抽象工厂中的产品的功能简化到一个,也便成为了工厂方法。 引出类别的概念,类别是指具有相同功能的一类产品的总称。 再来看选择的过程,在工厂方法中,客户关心的只不过是实现同一样功能的不同产品间的差异,这是一个一维选择的过程。 1 IFactory factory = new FactoryA(); //选择了工厂即选择了产品 2 IProduct productA = factory.Create(); //工厂只有一个Create方法,只能生产一种产品 而在抽象工厂中,客户首先要考虑的是需要哪一样功能,其次要考虑才是产品间的差异。也就是说这是一个二维选择的过程。 1 IFactory factory = new FactoryA(); //选择了某个具体工厂 2 IProduct productA = factory.CreateProductA(); //工厂具有多个Create方法,这里选择了其中的一个 由于产品类别的增加而导致客户在考虑产品间差异的同时还要考虑产品间功能的差异,这种二维选择的过程才是工厂方法与抽象工厂之间的本质区别。 举个肯德基与麦当劳的例子,假设原来只有一家快餐店叫做麦当劳,提供的食物 (具体产品) 有汉堡、可乐、薯条,它们都可以满足你吃东西 (抽象接口) 的需求,那么你想吃快餐的时候,唯一的选择就在于吃什么,是一维选择,现在又开了一家快餐店叫做肯德基,同样供应汉堡、可乐和薯条,那么现在你若打算吃快餐,除了考虑吃什么外,还要考虑去哪里吃-肯德基还是麦当劳?这便是二维的选择。通过横向与纵向的选择才能最终锁定你要的产品。 ...

Created 12126-10-12 · Updated 9096-10-09 · 1 min · 211 words · -

Make Illegal States Unrepresentable:让非法状态不可表达

“Make illegal states unrepresentable”(让非法状态不可表达)是一句常被引用的设计口号,出自 Yaron Minsky(Jane Street)2011 年的博客文章 Effective ML Revisited,文中专门有一节就叫「Make illegal states unrepresentable」,用一个 OCaml 的 connection_state 类型做 before/after 对比。核心主张是:与其允许构造出错误的状态、再靠运行时检查去堵漏洞,不如设计数据结构本身,让错误状态压根编译不过、或者根本没有对应的值可以构造。 它要解决的问题 一个常见的反模式:用一个共享结构 + 若干标志位/可空字段,去表达"实际上互斥的几种状态"。 // 反例:一个类硬塞下多种状态,靠字段组合和约定表达"当前是哪种情况" public class Order { private String status; // "CREATED" / "PAID" / "SHIPPED" / "CANCELLED" private String paymentId; // 只有 status = PAID 之后才该有值 private String trackingNo; // 只有 status = SHIPPED 之后才该有值 private String cancelReason; // 只有 status = CANCELLED 才该有值 } 这段代码里,“没付款却有 trackingNo““状态是 CANCELLED 但 paymentId 和 cancelReason 同时非空"这些本不该存在的组合,在类型系统眼里都是合法值——编译器完全放行,只能靠人读代码、写注释、加运行时校验去维持"约定”。约定总有被忘记、被破坏的一天:少写一个 if、漏掉一次校验,非法组合就悄悄溜进了生产数据。 ...

Created 1016-07-01 · Updated 1016-07-01 · 2 min · 239 words · -

Fault Tolerance: 容错

Fault tolerance(容错)指的是:系统在部分组件出现故障时,仍能继续提供服务(哪怕是降级的服务),而不是直接整体瘫痪。它的出发点是承认「故障一定会发生」——硬件会坏、网络会断、进程会崩——所以与其指望不出错,不如让系统在出错时还能扛住。 容错与 fail-fast 的关系 这两个概念常被放在一起,但取向不同,而且并不矛盾: fail-fast(快速失败):一检测到不该出现的状态就立即、显眼地失败,把错误暴露在离源头最近的地方。 fault tolerance(容错):出错时尽量不让整个系统垮掉,通过冗余、隔离、降级等手段继续提供服务。 实际系统往往两者同时用、用在不同层面:单个组件内部对非法状态 fail-fast(尽早抛错、不带病运行),而系统整体对组件级故障容错(一个实例挂了还有别的顶上)。换句话说,「让局部快速失败」恰恰是「让整体可容错」的前提——故障被快速隔离出来,才不会扩散成全局问题。 常见的容错手段 容错不是单一技术,而是一组配合使用的做法: 冗余 / 副本(redundancy / replication):关键组件多部署几份,单点坏了还有备份。是容错的基础。 失效转移(failover):主组件故障时自动切到备用组件,对外尽量无感。 优雅降级(graceful degradation):非核心功能出问题时,关掉或简化它,保住核心链路,而不是整体报错。 重试与超时(retry / timeout):对偶发、可恢复的失败(网络抖动等)做有限重试,并用超时避免无限等待。 熔断与隔离(circuit breaker / bulkhead):下游故障时快速失败、暂时切断调用,防止故障沿调用链扩散、拖垮上游;用隔离把故障限制在局部。 拜占庭容错(BFT):更强的一类容错,能在部分节点发送错误甚至恶意信息时仍达成一致,多见于分布式共识、区块链。 微服务语境下的「服务容错」(熔断、隔离、限流、降级、超时)可参考 微服务 一文;Actor 模型的「任其崩溃(let it crash)」则是另一种思路——不在每处做防御式校验,而是让监督者在崩溃后重建。 容错 ≠ 高可用,但服务于它 容错是手段,高可用(high availability)是目标之一。通过容错让单点故障不影响整体服务,正是为了达成更高的可用性(更短的中断、更小的影响范围)。衡量容错切换效果常用的 RTO / RPO,见 failover。 边界:容错不是无限兜底 容错有成本:冗余要多花机器,复制要付出一致性 / 性能代价(参见 CAP 里 C 与 A 的取舍)。不是所有系统都需要、也不是所有故障都值得容错。 别把错误吞掉:容错是「继续提供服务」,不是「假装没出错」。被容错处理掉的故障仍要记录、告警、可观测,否则就成了「静默失败」——问题被盖住、没人知道,反而更糟。 区分该容忍的和该暴露的:对可预期、可恢复的外部故障(网络、下游不可用)做容错;对「程序员的错误 / 不该发生的内部状态」则应 fail-fast,越早暴露越好。 一句话:fault tolerance 关心「出错时系统还能不能用」,靠的是冗余、隔离、降级这些手段把故障的影响范围压下去;它和 fail-fast「尽早暴露错误」在不同层面配合,共同支撑系统的可用性。

Created 30306-06-30 · Updated 30306-06-30 · 1 min · 63 words · -

Failover / Failfast / Failback / Failsafe: 四个故障处理术语

这四个词长得像、也容易混,但讲的是「系统出故障时怎么应对」的不同侧面。下面把它们放在一起做个简单区分。 Failover:失效转移 / 故障转移 当正在工作的主组件出现异常时,把它的职责切换到备用组件,让对外服务尽量不受影响。要点是有主有备,主故障时备能顶上、并被提升为新的主。 典型例子是数据库主从:正在使用的主库挂了,就把备库切为主库继续提供服务。理想情况下用户完全感知不到这次切换。衡量切换效果常用两个指标: RTO(Recovery Time Objective):从故障到服务恢复需要多长时间。这段时间里服务通常是不可用的,用户感知到的是中断而不只是变慢,所以越接近 0 越好。 RPO(Recovery Point Objective):能恢复到故障前哪一刻,也就是最多会丢多少数据。它不是一个固定的量级,取决于数据保护方式:同步复制可做到 RPO ≈ 0(不丢数据,但成本高);异步复制一般是秒级到分钟级;定期快照 / 备份则是分钟级到小时级(每天备一次,最坏丢近一天)。换句话说,秒级和小时级都可能,取决于你用哪种复制 / 备份策略以及业务能容忍丢多少。 Failfast:快速失败 一旦检测到不该出现的状态,就立即、显眼地失败,把错误暴露在离源头最近的地方,而不是带着错误继续运行、之后在别处以奇怪的方式崩掉。它和 fault-tolerant(容错)是相对的取向。 一个常见例子是 Java 集合的 fail-fast 迭代器:当一个线程在遍历集合时,集合内容被其他线程改动,遍历线程会立刻抛出 ConcurrentModificationException,而不是返回不可预期的结果。 fail-fast 作为一条通用的软件工程原则(不止集合迭代器),单独展开在 Fail-Fast 快速失败 一文。 Failback:失效自动恢复 failover 之后的「回切」。当原来的主组件修复、重新可用后,把网络资源和服务从备用系统切回原主系统的过程。常见于需要临时维护主机、把流量先转到备用系统、维护完再切回的场景。 Failsafe:失效安全 即使发生故障,也尽量不造成伤害、或把伤害降到最低。一个形象的例子是红绿灯的冲突监测模块:当检测到错误或冲突的信号时,让整个路口进入闪烁警示模式,而不是把所有方向都显示为绿灯。 一句话区分 failover:主挂了,备顶上,继续服务。 failback:主修好了,再切回去。 failfast:出错就立刻停下报错,别硬撑。 failsafe:出错也要保证安全、把伤害降到最小。 failover / failback 关注的是「服务不中断」(可用性、容错),failfast / failsafe 关注的是「出错时的行为」——一个偏向尽早暴露,一个偏向兜底保护。 参考 失效转移、快速失败等概念(CSDN) https://blog.csdn.net/u013699827/article/details/73251649 维护记录 时间 修改内容 原因 2026-06-30 从 inbox/ 移到 pattern/;改写为自有表述、重排四个术语并加「一句话区分」;分类由 inbox 改为 development,标签更新为 fault-tolerance/high-availability/Pattern/remix/AI-assisted;新增到 fail-fast 的内链 原在未分类 inbox、内容为转载片段,整理归类并提升可读性 2026-06-30 RTO 说明改为强调「服务中断」而非延迟;RPO 补充其量级取决于复制 / 备份策略(同步 ≈0、异步秒/分钟级、备份分钟/小时级),不是固定小时级 澄清 RTO/RPO 含义,纠正「RPO 固定某量级」的误解

Created 6066-07-06 · Updated 30306-06-30 · 1 min · 83 words · -

Fail-Fast 快速失败:尽早暴露错误

Fail-fast(快速失败)是一种设计原则:当系统检测到一个不该发生的状态时,应当立即、显眼地失败,而不是带着错误继续运行下去。它的核心主张是——把问题暴露在离源头最近的地方,让错误尽早被发现,而不是拖到后面变成难以排查的怪现象,或者悄无声息地产出错误结果。 Jim Shore 在 Martin Fowler 主持的 IEEE Software「Design」专栏上写过一篇就叫《Fail Fast》的短文(2004 年 9/10 月号),把这个原则讲得很直接:带着错误「慢慢失败」(failing slowly)的程序会在后面以奇怪的方式出问题,而 fail-fast 的系统则在问题发生的当下就立即、显眼地失败(fail immediately and visibly)——看似更脆弱,实则更健壮,因为缺陷更容易被发现和修复。原文可读 Fowler 站点上的 Fail Fast (PDF),作者本人也在 jamesshore.com 上做了简介。 它要解决的问题 很多 bug 难查,不是因为错误本身复杂,而是因为错误发生的位置和错误暴露的位置离得很远。 设想一个函数收到了一个非法参数(比如本不该为 null 的值),它没有检查,而是默默接受、继续往下传。这个坏值可能流过好几层调用、被写进缓存、存进数据库,直到很久以后在某个完全不相干的地方抛出一个莫名其妙的异常。这时你拿到的堆栈和真正的出错点已经隔了十万八千里。 fail-fast 的思路是反过来:在边界上就把关,一发现状态不对就立刻停下来报错。错误在源头炸掉,定位成本最低。 几个常见的例子 fail-fast 不是某一种具体技术,而是一类做法,散落在很多地方: 参数 / 前置条件校验:方法一进来就检查参数,非法立刻抛异常,而不是带病运行。 断言(assertions):用断言把「不该发生」的假设显式写出来,一旦被打破马上崩。 构造即校验:对象在构造时就保证自己处于合法状态,构造不出来就抛错,而不是造出一个半残对象。 Java 集合的 fail-fast 迭代器:遍历过程中集合被并发修改,立刻抛 ConcurrentModificationException,而不是返回不可预期的结果(这一面在 failover / failfast / failback / failsafe 一文里也有提到)。 编译器:源码有语法错误就停止编译并报错,绝不会「跳过有问题的函数、把剩下的编译出来」。 构建工具 / 静态站点生成器:比如 Hugo,只要有一篇文档的 front matter 解析失败,就会中止整站构建——这正是我这个博客踩过的坑,记录在 一次构建失败导致的「假性停更」。 下面是一个最朴素的 fail-fast 校验: // fail fast: reject the bad input right at the boundary public void transfer(Account from, Account to, long amount) { if (from == null || to == null) { throw new IllegalArgumentException("account must not be null"); } if (amount <= 0) { throw new IllegalArgumentException("amount must be positive: " + amount); } // ... safe to proceed; state is known-good from here on } 为什么这么设计 fail-fast 背后的判断是:带着错误状态继续运行,比直接失败更危险。 ...

Created 30306-06-30 · Updated 30306-06-30 · 1 min · 189 words · -

YAGNI: You Aren't Gonna Need It 你不需要它

YAGNI 是 “You Aren’t Gonna Need It” 的缩写,意思是「你(现在)不会需要它」。它是极限编程(XP)提出的一条实践原则,由 Kent Beck、Ron Jeffries 等人总结,核心主张是:只在真正需要某个功能时才去实现它,不要因为「以后可能用得上」就提前把它做出来。 它要解决的问题 开发中有一种很常见的冲动:在写当前功能时,顺手把「将来或许会用到」的扩展点、配置项、抽象层一起加上。这类提前实现往往带来几种代价: 做了用不上的功能:付出了开发和测试成本,最终却从未被使用。 增加复杂度:每一个为「未来」准备的抽象、参数、分支,都会让代码更难读、更难改。 猜错方向:未来的真实需求常常和当初设想的不一样,提前写好的代码反而成了包袱,删也不是、改也不是。 维护负担:哪怕没用上,这些代码也要被阅读、被编译、被测试、跟着一起重构。 Martin Fowler 把这种「为了未必到来的需求而提前增加的灵活性」称为 speculative generality(投机性通用化),并将其列为一种代码坏味道。YAGNI 正是针对它的解药。 一个例子 假设要做一个把数据导出为 CSV 的功能。 违反 YAGNI 的做法:考虑到「以后可能还要导出 JSON、Excel、PDF」,于是先设计一套 Exporter 接口、工厂、注册表、插件机制,再让 CSV 成为其中一个实现。结果当下只用到 CSV,那一整套扩展架构全是空转的成本。 遵循 YAGNI 的做法:现在只需要 CSV,那就先写一个直接、清晰的 CSV 导出函数。等真的要支持第二种格式时,再基于那时已经明确的两个真实场景去抽象接口——这时的抽象建立在事实之上,而不是猜测之上。 与 KISS、DRY 的关系 这几条原则常被一起提及,但侧重点不同: YAGNI:不要做现在不需要的功能(针对「范围」)。 KISS(Keep It Simple, Stupid):用尽量简单的方式解决问题(针对「实现」)。 DRY(Don’t Repeat Yourself):消除重复的知识(针对「重复」)。 三者方向一致,都指向「不要引入不必要的复杂度」。值得注意的是 YAGNI 和 DRY 偶尔会拉扯:为了「将来复用」而过早抽取公共逻辑,可能正是一种 YAGNI 想避免的投机性抽象。经验法则是先容忍少量重复,等重复出现第三次、模式真正清晰时再抽象。 边界:YAGNI 不是什么 YAGNI 容易被误读为「不要做任何设计」或「怎么糙怎么来」,这是误解。它有明确的适用边界: 它针对的是「功能/特性」,不是「质量」。该有的测试、错误处理、清晰命名、合理结构都不能省——这些是当前就需要的,不是「未来」。 它偏好可逆决策。YAGNI 之所以成立,前提是「以后再加也不难」。对于难以回退的决策(如数据库选型、对外 API 契约、安全模型、数据格式),仍需要提前认真设计,因为它们不满足「需要时再做」的低成本假设。 它不反对抽象,只反对过早抽象。抽象应当由重复的、已经发生的真实需求来驱动,而不是由想象中的需求来驱动。 实践建议 写代码前问一句:这个能力是当前这个需求现在就要的吗?如果不是,先不做。 把「以后可能要做」的想法记进 backlog 或 TODO,而不是写进代码。 优先保证当前实现简单、清晰、可改;保留把改动控制在小范围内的能力,比预先把扩展点全部铺好更有价值。 对不可逆的决策慎用 YAGNI,对可逆的决策大胆用。 YAGNI 的本质是一种关于「时机」的纪律:把实现推迟到信息最充分的那一刻。需求在「需要时」往往比「设想时」清晰得多,那时再动手,既省去了无用功,也避免了基于错误猜测的设计。 ...

Created 28286-06-28 · Updated 28286-06-28 · 1 min · 83 words · -

Conflict Check(2024)

此文用于记录在 AI 时代到来之前我手搓的最后一个复杂模块. 这个 conflict check 模块的重写是在 2024 年,历时约 3 个月,当时我还没有开始大规模使用 AI 辅助编程。重写的时候注意力都在功能需求上,并没有想「要用哪种设计模式」,只是在纠结怎么把问题拆清楚、用面向对象的方式把责任分开、把可能变化的部分封装起来。Redis 里的缓存数据是嵌套的树形结构,解析时写了几个递归调用——当时认为是最复杂的几个函数。 这个文档写于此后。某次和一个朋友聊天,对方问起对设计模式有没有了解,当时长期没有专门研究过,没说出个所以然。后来那个问题偶尔还会浮上来,然后有一天和 conflict check 关联到了起来, 然后用 Copilot + Claude Sonnet 4.6 分析了一下 — 有些设计模式接近标准实现,有的因业务结构或 Python 语言特性而有所偏差,还有两个(Builder 和 State)目前只是符合模式意图、需要重构才能达到标准结构。整理的过程中还发现了有性能优化空间的地方。 背景 防火墙策略在应用到设备之前,需要先在缓存中进行冲突检测。“网络编排器” 将每台设备的数据缓存在 Redis 中,每份 profile 包含三类数据:地址对象和分组、服务对象和分组,以及策略列表。 每条策略的关键字段: src:源地址数组,每个元素是一个 address object name 或 address group name dst:目标地址数组,结构与 src 相同 service:服务数组,每个元素是一个 service object name 或 service group name 地址对象和服务对象共用同一种数据结构,各自包含两种角色: object:基础对象,value 是实际 IP 地址/CIDR 网段(address)或协议/端口(service)的数组 group_object:分组对象,value 是 object name 或另一个 group_object name 的数组 group 和 object 形成嵌套结构:一个 group 的 value 列表里,既可以直接引用 object,也可以引用其他 group,形成递归树。地址对象和服务对象结构相同,因此 PolicyObject 类可以统一表达这两种类型,同时处理 src、dst、service 的解析。 ...

Created 14146-05-14 · Updated 5056-06-05 · 6 min · 1181 words · w1100n

代理模式, proxy pattern

代理模式, proxy pattern 为其他对象提供一种代理以控制对这个对象的访问。 代理模式,即Proxy,它和 Adapter 模式很类似。我们先回顾Adapter模式,它用于把A接口转换为B接口: Adapter 模式 public BAdapter implements B { private A a; public BAdapter(A a) { this.a = a; } public void b() { a.a(); } } Proxy模式 而Proxy模式不是把A接口转换成B接口,它还是转换成A接口: public AProxy implements A { private A a; public AProxy(A a) { this.a = a; } public void a() { this.a.a(); } } 看起来 Proxy 只是把 A 接口又包了一层,这有什么意义呢? ...

Created 16166-04-16 · Updated 15156-05-15 · 1 min · 185 words · -

创建者模式, 建造者模式, Builder

创建者模式, 建造者模式, Builder pattern 定义: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 使用场景 多个部件或零件,都可以装配到一个对象中,但产生的结果又不相同时。 当初始化一个对象特别复杂的时候,比如参数多,而且很多参数都有默认值。 GoF Builder 模式包含四个角色: 抽象建造者 (Builder):给出一个抽象接口,以规范产品对象各个组成成分的建造。 具体建造者 (ConcreteBuilder):实现抽象建造者声明的接口,一步一步完成创建产品实例的操作;建造完成后提供产品实例。 指挥者 (Director):调用具体建造者以创建产品对象。 产品 (Product):建造中的复杂对象。 GoF Builder 示例:RTF 文档转换器 RTF 背景 RTF(Rich Text Format,富文本格式)是微软 1987 年推出的一种文档格式,用纯文本加控制命令描述带格式的文档。一段 RTF 内容长这样: {\rtf1 Hello {\b World}\par} {\b ... } 表示加粗 \par 表示段落结束 RTFReader(Director)的工作是逐个扫描这些 token:遇到 \b 就调 convertFontBold(true),遇到普通字符就调 convertCharacter(c),遇到 \par 就调 convertParagraph()。它自己不生产任何输出,只负责解析结构、驱动 Builder。虽然 RTF 现在已经很少用了,但"解析器解析 → 转换器输出不同格式"这个模型在现代依然普遍(如 Markdown 解析器输出 HTML / PDF / DOCX)。 这是 GoF 原书使用的经典案例。同一份 RTF 文档,Director(RTFReader)负责解析 token 流,每遇到字符、加粗标记、段落标记就调用 Builder 对应的方法。Director 只管读懂结构,Builder 只管决定输出,两者通过接口解耦:parse() 接收的是 TextConverter 接口,不需要知道背后的具体类型,也不需要知道最终输出格式——它只是把 RTF 语义事件翻译成接口调用,由各 ConcreteBuilder 自己决定如何响应:ASCIIConverter 忽略格式标记只保留字符,HTMLConverter 将加粗转为 <b> 标签,TeXConverter 转为 \textbf{} 命令。 ...

Created 16166-04-16 · Updated 15156-05-15 · 4 min · 830 words · -

Martin Fowler 主要设计模式

Martin Fowler 是《重构》《企业应用架构模式》《领域特定语言》等经典书籍的作者,也是 ThoughtWorks 首席科学家。他提出或系统整理了大量在工业界广泛应用的设计模式,涵盖对象建模、企业架构和分布式系统三个层面。 本文选取其中最具代表性的几个模式进行介绍。关于企业应用架构模式中的 Repository、Unit of Work、Front Controller 等,见 企业应用架构模式。 《企业应用架构模式》 Patterns of Enterprise Application Architecture(PoEAA) Special Case(特殊情况) 来源: Patterns of Enterprise Application Architecture(2002) 问题 方法在遇到无法正常处理的输入时,通常有两种选择:返回 null 或抛出异常。两者都把处理责任推给调用方,导致 null 判断或 try/catch 散布在代码各处。 // 调用方被迫到处写 null 判断 Customer customer = customerRepository.findById(id); if (customer == null) { return "unknown"; } return customer.getName(); 解决方案 为"无法正常处理"的情况创建一个实现相同接口的特殊对象,让它封装默认行为。调用方无需判断,直接调用即可。 public interface Customer { String getName(); boolean isNull(); } public class UnknownCustomer implements Customer { @Override public String getName() { return "Unknown"; } @Override public boolean isNull() { return true; } } // 仓储层返回特殊对象,而非 null public Customer findById(long id) { Customer c = db.find(id); return c != null ? c : new UnknownCustomer(); } // 调用方无需 null 判断 String name = customerRepository.findById(id).getName(); Special Case vs Null Object Null Object 是 Special Case 的一个特例——当特殊行为就是"什么也不做"时,就是 Null Object。Special Case 更通用:特殊对象可以有具体的默认行为,甚至代表一个有意义的领域概念(如"匿名用户"“未知账户"“空购物车”)。 ...

Created 15156-05-15 · Updated 15156-05-15 · 3 min · 541 words · -

中介者模式, Mediator Pattern

中介者模式, Mediator Pattern 中介者模式(Mediator Pattern)定义一个中介对象来封装一系列对象之间的交互,使各对象不需要显式地相互引用,从而降低耦合度,并可以独立地改变它们之间的交互。 核心思想 当系统中多个对象之间存在复杂的多对多交互时,对象之间的依赖关系会形成网状结构,难以维护。中介者模式将这种网状结构转化为星形结构——所有对象只与中介者通信,由中介者协调各对象之间的交互。 不使用中介者:网状结构 graph LR A <--> B A <--> C B <--> C B <--> D C <--> D 使用中介者:星形结构 graph LR A <--> M[Mediator] B <--> M C <--> M D <--> M 角色 Mediator(抽象中介者):定义各同事对象通信的接口。 ConcreteMediator(具体中介者):实现协调逻辑,持有所有同事对象的引用。 Colleague(同事类):每个同事只知道中介者,通过中介者与其他同事交互。 示例:聊天室 聊天室是中介者模式的典型场景:用户之间不直接通信,而是通过聊天室(中介者)转发消息。 抽象中介者: public interface ChatRoom { void sendMessage(String message, User sender); void addUser(User user); } 具体中介者: import java.util.ArrayList; import java.util.List; public class ChatRoomImpl implements ChatRoom { private final List<User> users = new ArrayList<>(); @Override public void addUser(User user) { users.add(user); } @Override public void sendMessage(String message, User sender) { for (User user : users) { if (user != sender) { user.receive(message, sender.getName()); } } } } 同事类: ...

Created 16166-04-16 · Updated 15156-05-15 · 2 min · 276 words · -

责任链模式, Chain of Responsibility Pattern

责任链模式, Chain of Responsibility Pattern 责任链模式(Chain of Responsibility)将请求沿着一条处理者链传递,每个处理者决定是否处理该请求,或将其传递给链上的下一个处理者。请求的发送者无需知道最终由哪个处理者来处理,从而实现了发送者与接收者的解耦。 核心思想 不使用责任链时,请求方往往需要硬编码逻辑来判断应该由哪个对象处理,导致紧耦合和大量条件判断。责任链将这些处理者串联起来,请求沿链传递,直到被某个处理者处理(或到达链尾)。 角色 Handler(抽象处理者):定义处理请求的接口,通常包含设置下一个处理者的方法。 ConcreteHandler(具体处理者):实现处理逻辑,决定自己是否处理请求,如果不处理则转发给下一个处理者。 Client(客户端):构建责任链并向链头发起请求。 示例:Java Servlet Filter 链 Servlet 规范中,请求在到达 Servlet 之前会依次经过一组 Filter,每个 Filter 可以对请求/响应做处理,然后调用 chain.doFilter() 继续传递,或直接中断返回响应。 Request → LogFilter → AuthFilter → Servlet ↓ Response ← ← ← 抽象处理者(Servlet 规范定义,无需自己写): public interface Filter { void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; } 具体处理者: public class LogFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("→ 请求到达:" + ((HttpServletRequest) request).getRequestURI()); chain.doFilter(request, response); // 继续传递给下一个 Filter System.out.println("← 响应返回"); // 响应回传时也会经过这里 } } public class AuthFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String token = ((HttpServletRequest) request).getHeader("Authorization"); if (token == null || !isValid(token)) { ((HttpServletResponse) response).sendError(401, "Unauthorized"); return; // 中断链,不调用 chain.doFilter() } chain.doFilter(request, response); // 鉴权通过,继续传递 } private boolean isValid(String token) { return token.startsWith("Bearer "); } } FilterChain 的内部实现(容器负责构建,这里展示其原理): ...

Created 16166-04-16 · Updated 15156-05-15 · 2 min · 250 words · -

Builder 模式与 Factory Method 模式的对比

原文:http://www.cnblogs.com/shenfx318/archive/2007/01/28/632724.html 代码示例为 C#。 Builder 与 Factory Method 同属 GoF 创建型模式,初学者容易混淆。本文通过将 Builder 模式一步步演化为 Factory Method,探讨两者的本质关系。 注:本文讨论的 Factory 指工厂方法(Factory Method),不是抽象工厂(Abstract Factory)。 Builder 模式的标准结构 Builder 模式由四个角色构成:抽象 Builder、具体 Builder、Director(指导者)、Product。 public interface IBuilder { void BuildPart1(); void BuildPart2(); Product GetResult(); } public class BuilderA : IBuilder { private Product product; public void BuildPart1() { product = new Product(); product.Add("Part1 build by BuilderA"); } public void BuildPart2() { product.Add("Part2 build by BuilderA"); } public Product GetResult() { return product; } } // Director 封装构建顺序 public class Director { public void Construct(IBuilder builder) { builder.BuildPart1(); builder.BuildPart2(); } } 客户端调用: ...

Created 12126-10-12 · Updated 15156-05-15 · 2 min · 221 words · -

POSA1 架构模式

POSA1 是什么 《Pattern-Oriented Software Architecture, Volume 1: A System of Patterns》(简称 POSA1),1996 年出版,作者为 Frank Buschmann、Regine Meunier、Hans Rohnert、Peter Sommerlad、Michael Stal。 POSA1 关注的是架构模式(Architectural Patterns),粒度比 GoF(1994)的 23 种设计模式更大——GoF 解决的是类与对象之间如何协作,POSA1 解决的是整个系统应该如何组织。 GoF(1994) POSA1(1996) 模式级别 对象/类设计 系统/架构 典型模式 Strategy、Observer、Chain of Responsibility Pipes & Filters、Layers、Broker 解决问题 类之间如何协作 整个系统如何组织 POSA1 中的主要模式 Layers(分层) 将系统分解为若干层,每层只依赖下层,对上层屏蔽实现细节。典型实现:OSI 网络模型、TCP/IP 协议栈、三层架构(表现层 / 业务层 / 数据层)。 Pipes and Filters(管道-过滤器) 将数据处理拆分为一系列独立的 Filter,每个 Filter 负责单一转换,通过 Pipe 传递数据。整个系统是可组合的处理管道。 特征: 每个 Filter 独立,不依赖其他 Filter 的内部状态 Filter 可以任意组合、复用、替换 任何一个 Filter 可以短路,中断后续处理 经典实现: ...

Created 12126-05-12 · Updated 12126-05-12 · 1 min · 141 words · -

Command Dispatcher / Command Bus

Command Dispatcher(命令分发器)和 Command Bus(命令总线)是架构模式,属于应用层架构设计的范畴,不在 GoF 23 种设计模式之列。 注意:“Command Dispatcher” 是开发者社区的口语化称呼,没有权威模式书籍正式定义。对应的正式模式名是 EIP(企业集成模式)中的 Content-Based Router(见下文)。 两者不是同一个模式,Command Bus 是在 Content-Based Router 基础上增加了中间件管道的演化版本,核心差异在于中间件管道。 Command Dispatcher Command Bus 核心机制 ID → Handler 直接路由 ID → 中间件链 → Handler 中间件支持 无 有(日志、验证、事务等) 复杂度 简单 较复杂 典型场景 IoT 协议解析、游戏指令 CQRS、DDD 应用层 Command Dispatcher 核心结构: 收到消息 → 解析 ID → registry.get(id) → handler.Handle(data) 服务初始化时将每种指令的处理类注册到一个 Map。运行时收到消息后,解析出消息类型 ID,从注册表查找对应处理器并直接调用。 // Handler 接口,每种指令实现一个 type Handler interface { Handle(data []byte) error } // Dispatcher 维护 id -> handler 的注册表 type Dispatcher struct { handlers map[uint8]Handler } func NewDispatcher() *Dispatcher { return &Dispatcher{handlers: make(map[uint8]Handler)} } func (d *Dispatcher) Register(id uint8, h Handler) { d.handlers[id] = h } func (d *Dispatcher) Dispatch(id uint8, data []byte) error { h, ok := d.handlers[id] if !ok { return fmt.Errorf("unknown command id: 0x%02X", id) } return h.Handle(data) } // --- 具体指令处理类 --- type LoginHandler struct{} func (h *LoginHandler) Handle(data []byte) error { fmt.Printf("处理登录指令: %x\n", data) return nil } type LocationReportHandler struct{} func (h *LocationReportHandler) Handle(data []byte) error { fmt.Printf("处理位置上报: %x\n", data) return nil } // --- 初始化注册 --- func main() { d := NewDispatcher() d.Register(0x01, &LoginHandler{}) d.Register(0x02, &LocationReportHandler{}) // 收到一条原始消息,解析出 id=0x02 d.Dispatch(0x02, []byte{0x01, 0x02, 0x03}) } Command Bus 在 Dispatcher 的基础上增加了中间件管道,每条命令在到达 Handler 之前会依次经过所有中间件。 ...

Created 28286-04-28 · Updated 28286-04-28 · 3 min · 574 words · -

原型模式, Prototype Pattern

原型模式, Prototype Pattern 原型模式(Prototype Pattern)是一种创建型模式,通过复制(克隆)一个已有对象来创建新对象,而不是通过 new 重新实例化。 核心思想 当创建一个对象的代价较大(如需要复杂初始化、数据库查询、网络请求等)时,可以先创建一个原型对象,后续通过克隆该原型来快速获得新对象。 角色 Prototype(抽象原型):声明克隆方法 clone()。 ConcretePrototype(具体原型):实现克隆方法,返回自身的副本。 Client(客户端):通过调用原型的克隆方法来创建新对象。 浅克隆与深克隆 浅克隆(Shallow Clone) 深克隆(Deep Clone) 基本类型字段 复制值 复制值 引用类型字段 复制引用(共享对象) 递归复制,独立对象 修改互不影响 ❌ 引用字段会互相影响 ✅ 完全独立 示例:细胞克隆 Java 通过实现 Cloneable 接口来支持浅克隆: public class Cell implements Cloneable { private String cellWall; // 细胞壁 private String cellMembrane; // 细胞膜 private String cellularTissue; // 细胞组织 public String getCellWall() { return cellWall; } public void setCellWall(String cellWall) { this.cellWall = cellWall; } // 其他 getter/setter 省略 @Override public Cell clone() { try { return (Cell) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.getMessage()); } } } 客户端使用: ...

Created 16166-04-16 · Updated 16166-04-16 · 1 min · 195 words · -

策略模式, Strategy Pattern

策略模式, Strategy Pattern 策略模式(Strategy Pattern)定义了一系列算法,把这些算法一个个封装成单独的类,使它们可以相互替换,且算法的改变不会影响使用算法的客户。 策略模式重点是封装不同的算法和行为,不同的场景下可以相互替换。策略模式是开闭原则的体现——对扩展开放,对修改关闭:新增策略时不影响其他类,且场景类只依赖抽象而不依赖具体实现。 示例:字符串替换策略 这里以字符串替换为例:读取一个文件后,需要替换其中的变量再输出。替换方式可能有多种,我们用策略模式来支持运行时切换。 首先,建立抽象策略类 RepTempRule,定义公用变量和方法: public abstract class RepTempRule { protected String oldString = ""; public void setOldString(String oldString) { this.oldString = oldString; } protected String newString = ""; public String getNewString() { return newString; } public abstract void replace() throws Exception; } 现在有两个具体策略:将文本中的 aaa 替换成 bbbb,或替换成 ccc: public class RepTempRuleOne extends RepTempRule { @Override public void replace() throws Exception { newString = oldString.replaceFirst("aaa", "bbbb"); System.out.println("this is replace one"); } } public class RepTempRuleTwo extends RepTempRule { @Override public void replace() throws Exception { newString = oldString.replaceFirst("aaa", "ccc"); System.out.println("this is replace two"); } } 算法解决类,提供运行时自由选择和切换算法的能力: ...

Created 16166-04-16 · Updated 16166-04-16 · 1 min · 213 words · -

设计模式 – 模板方法, Template Method

模板方法, Template Method 模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。 模式结构 模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。 模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。 模板方法所代表的行为称为顶级行为,其逻辑称为顶级逻辑。 这里涉及到两个角色: 抽象模板(Abstract Template) 角色的责任: 定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。 定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。 具体模板(Concrete Template) 角色的责任: 实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。 每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。 基础示例 抽象模板角色类,abstractMethod()、hookMethod() 等基本方法是顶级逻辑的组成步骤,这个顶级逻辑由 templateMethod() 方法代表。 public abstract class AbstractTemplate { /** * 模板方法 */ public void templateMethod() { // 调用基本方法 abstractMethod(); hookMethod(); concreteMethod(); } /** * 基本方法的声明(由子类实现) */ protected abstract void abstractMethod(); /** * 基本方法(空方法) */ protected void hookMethod() {} /** * 基本方法(已经实现) */ private final void concreteMethod() { // 业务相关的代码 } } 具体模板角色类实现了父类所声明的基本方法,abstractMethod() 方法所代表的就是强制子类实现的剩余逻辑,而 hookMethod() 方法是可选择实现的逻辑。 ...

Created 16166-04-16 · Updated 16166-04-16 · 3 min · 484 words · -

SOLID 面向对象设计原则

SOLID 是面向对象设计的五大基本原则的首字母缩写,由 Robert C. Martin(Uncle Bob)整理归纳。这五个原则是编写可维护、可扩展代码的基础。 S — 单一职责原则 (Single Responsibility Principle, SRP) 一个类应该只有一个引起它变化的原因。 一个类只做一件事。如果一个类承担了多个职责,那么每个职责的变化都可能影响这个类,导致它越来越难以维护。 反例: 简单工厂模式中的工厂类 Driver 同时负责判断车型和创建所有车对象,违反了单一职责。 O — 开闭原则 (Open-Closed Principle, OCP) 软件实体应该对扩展开放,对修改封闭。 添加新功能时,应该通过新增代码实现,而不是修改已有代码。详见开闭原则。 反例: 简单工厂的 if/else 判断链,每新增一种产品都要修改工厂方法。 L — 里氏替换原则 (Liskov Substitution Principle, LSP) 子类必须能够替换其父类,且程序行为不变。 任何使用父类的地方,换成子类后程序应该仍然正确运行。子类不应该破坏父类的契约。 示例: Benze 和 Bmw 都实现了 Car 接口,客户端代码使用 Car 类型,无论实际是哪种车,drive() 都能正确调用。 I — 接口隔离原则 (Interface Segregation Principle, ISP) 客户端不应该被迫依赖它不使用的方法。 接口应该尽量细化,不要把不相关的方法放在同一个接口里。大接口应该拆分成多个小接口,让实现类只需要关心自己用到的方法。 反例: 一个包含 fly()、swim()、run() 的 Animal 接口,鱼类被迫实现 fly() 和 run()。 ...

Created 16166-04-16 · Updated 16166-04-16 · 1 min · 87 words · -

设计模式 — 工厂模式, Factory

设计模式 — 工厂模式, Factory 一、引子 话说十年前,有一个老板,他家有三辆汽车 —— Benz奔驰、Bmw宝马、Audi奥迪,还雇了司机为他开车。不过,老板坐车时总是怪怪的: 上Benz车后跟司机说"开奔驰车!",坐上Bmw后他说"开宝马车!",坐上Audi说"开奥迪车!"。你一定说: 这人有病!直接说开车不就行了?! 而当把这个老板的行为放到我们程序设计中来时,会发现这是一个普遍存在的现象。幸运的是,这种有病的现象在 OO (面向对象) 语言中可以避免了。下面就以 Java 语言为基础来引入我们本文的主题: 工厂模式。 二、分类 工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。 工厂模式在《Java与模式》中分为三类: 简单工厂模式 (Simple Factory) 工厂方法模式 (Factory Method) 抽象工厂模式 (Abstract Factory) 这三种模式从上到下逐步抽象,并且更具一般性。 GOF在《设计模式》一书中将工厂模式分为两类: 工厂方法模式 (Factory Method) 与抽象工厂模式 (Abstract Factory) 。将简单工厂模式 (Simple Factory) 看为工厂方法模式的一种特例,两者归为一类。 两者皆可,在本文使用《Java与模式》的分类方法。下面来看看这些工厂模式是怎么来"治病"的。 简单工厂模式, 简单工厂 (Simple Factory) 简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单: 定义一个用于创建对象的接口。 先来看看它的组成: 工厂类角色: 这是本模式的核心,含有一定的业务逻辑和判断逻辑。在 java 中它往往由一个具体类实现。 抽象产品角色: 它一般是具体产品继承的父类或者实现的接口。在 java 中由接口或者抽象类来实现。 具体产品角色: 工厂类所创建的对象就是此角色的实例。在 java 中由一个具体类实现。 来用类图来清晰的表示下的它们之间的关系 (如果对类图不太了解,请参考我关于类图的文章) : 那么简单工厂模式怎么来使用呢?我们就以简单工厂模式来改造老板坐车的方式——现在老板只需要坐在车里对司机说句: “开车"就可以了。 // 抽象产品 interface Car { void drive(); } // 具体产品 class Benze implements Car { public void drive() { System.out.println("Driving Benz"); } } // 具体产品 class Bmw implements Car { public void drive() { System.out.println("Driving Bmw"); } } // 工厂类 class Driver { // 工厂方法,注意返回类型为抽象产品 public static Car createCar(String s) throws Exception { // 判断逻辑,返回具体的产品角色给 Client if (s.equalsIgnoreCase("Benz")) { return new Benze(); } else if (s.equalsIgnoreCase("Bmw")) { return new Bmw(); } else { throw new Exception(); } } } // 客户端 public class Magnate { public static void main(String[] args) throws Exception { // 告诉司机我今天坐奔驰 Car car = Driver.createCar("benz"); // 下命令: 开车 car.drive(); car = Driver.createCar("bmw"); car.drive(); } } 将本程序空缺的其他信息填充完整后即可运行。如果你将所有的类放在一个文件中,请不要忘记只能有一个类被声明为public。本程序在jdk1.4 下运行通过。 ...

Created 16166-04-16 · Updated 16166-04-16 · 3 min · 440 words · -