设计模式

设计模式

设计模式之命令模式

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,命令模式也支持可撤销的操作。 来解析这个定义: 1. “将一个请求封装为一个对象”,请求原本是一个方法,现在要封装成一个对象,说明要新增类来完成。 2. “可以用不同的请求对客户进行参数化”,说明是将命令对象作为参数进行传递。 3. “队列”说明需要维护命令多个命令的列表队列。 4. “撤销”说明有命令对象有undo撤销方法。 命令模式在设计模式中,算是一个比较不好理解的模式,很重要的原因是不清楚设计意图,不清楚不用这个模式前有何问题,这个模式带了哪些好处,能解决什么问题。 上一篇状态模式中,看到了状态模式抽离的是状态(属性),向上提成状态对象。有了这个基础,再来理解命令模式就相对简单了。命令模式抽离的是行为(方法),向上提成命令对象。 两者都通过“对象化”来解耦和扩展系统,但解决的问题不同: * 状态模式:处理对象内部状态驱动的行为变化。 * 命令模式:处理行为请求的封装与调度。 💡智能家居遥控器,假设我们有一个智能家居遥控器,可以控制 灯(Light) 和

By 李浩

设计模式

设计模式之状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 来解析这个定义: 1. “内部状态”表明对象内部有一个属性来表示状态。 2. “内部状态改变时改变它的行为,对象看起来好像修改了它的类”说明状态改变后对象的行为发生了非常大的变化,不像是同一类的行为。 从目前的分析中似乎无法推导出状态模式的类图结构。 从实际的例子出来,来看看状态模式是如何演进而来。 💡我们有一个文档审批系统,文档有以下状态和转换: 1. 草稿(Draft) → 提交 → 待审批(PendingReview) 2. 待审批 → 批准 → 已发布(Published) 3. 待审批 → 拒绝 → 草稿 4. 已发布 → 撤回 → 草稿 从直觉出发,会使用条件语句实现需求逻辑。 public class Document { private String state = "DRAFT"; // 初始状态为草稿 public void submit(

By 李浩

设计模式

设计模式之代理模式

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。 解析这个定义: 1. “替身”表明在客户端看来,代理类与被代理类是同一类别,对客户端来说看上去没什么区别,依然能够满足诉求。如此可以看出代理类与被代理类来自同一个超类。 2. “控制对这个对象的访问”,能够控制访问,说明前提是能够访问,才能在访问之前做这个限制,即代理持有对真实对象的引用(或能创建它)。 当然可能会质疑,用继承的方式不是也能完成目标吗,用代理类去继承被代理类,然后重写方法,加入控制逻辑。但这违背了"组合优于继承"原则,代理类与被代理类强耦合。 💡我们要开发一个图片查看器,需求如下: 1. 图片加载开销大(从磁盘或网络加载耗时),希望首次显示时才加载(延迟加载)。 2. 某些图片需要权限校验,只有授权用户才能查看。 3. 客户端代码应统一接口,无需关心是直接加载图片还是通过代理。 // 1. 抽象接口(Subject) interface Image { void display(); } // 2. 真实对象(RealSubject)

By 李浩

设计模式

设计模式之组合模式

组合模式是一种结构型设计模式,它允许你将对象组合成树形结构来表示“部分-整体”的层次关系,使得客户端可以统一处理单个对象和组合对象,而无需关心具体的对象类型。 解析这个定义: 1. “结构型”&“层次关系”,说明此模式针对的对象结构十分典型,对象间的关系有明显的结构特征。 2. “树形结构”,说明此模式就是针对树形结构的对象关系。 3. “部分-整体”,说明组合对象包含了单个对象。 4. “客户端可以统一处理”,客户端可以不关心对象是属于单个对象还是组合对象,表明单个对象和组合对象有相同的超类,而且实现了相同的接口。 在设计业务场景中,有部分整体关系,成树形结构的,有许多,比如: * 文件 vs 文件夹 * 员工 vs 部门 * 评论 vs 回复 从对定义的解析,到树形结构的实现,可以推导出组合模式的结构图。 但,组合模式的关键点,不在于结构图,而在于真正理解这个模式解决的问题,已经为什么要如此设计。 组合模式的核心本质之一,是将递归逻辑从客户端转移到“组合对象”内部。

By 李浩

设计模式

设计模式之迭代器模式

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。 解析这个定义: 1. “聚合对象”,表示对象中有数组、集合、字典等数据类型属性。 2. “顺序访问”,表示模式需提供一个遍历的方式。 3. “不暴露其内部的表示”,表明不想让客户端知道太多内部细节。 综合来看,此模式的设计意图非常明确,将聚合对象的遍历访问职责封装抽离出来。 直接抛出 Iterator 接口和 next()、hasNext() 方法有些“上帝视角”了,让我们抛开预设的实现,从零开始推导如何设计这个“抽离聚合对象访问”的模式,一步步探索最终如何自然演化到经典的迭代器结构。 第一步:明确核心问题 假设我们有一个聚合对象(比如一个自定义集合 MyCollection),内部用数组存储数据。现在需要让外部能遍历它的元素,但不允许直接暴露内部数组。如何设计? 初始代码(暴露内部实现,不符合封装原则): class MyCollection { private String[] data = {"A"

By 李浩

设计模式

设计模式之模板方法模式

模板方法模式定义一个操作中的算法骨架,而将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。 解析这个定义: 1. “算法骨架”,表明是定好了调用多个方法调用的框架顺序。 2. “某些步骤延迟到子类”,延迟到子类说明框架由超类定义,“某些步骤”表明只是部分交由子类自定义,而还有一部分是由超类已经定义好了,由此可以表明超类必须是抽象类,而非接口,“某些步骤”相应的就是抽象方法。 💡假设我们有一个电商平台的支付系统,支付流程通常包含以下固定步骤:验证支付参数、调用支付网关、记录支付日志、通知支付结果,其中,第2步"调用支付网关"因支付方式不同而实现不同,其他步骤则可以复用。 // 抽象支付类 - 定义支付流程模板 public abstract class PaymentProcessor { // 模板方法(final防止子类修改流程) public final void processPayment(double amount, String orderId) { validate

By 李浩

设计模式

设计模式之外观模式

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。 解析这个定义: 1. “统一的接口,用来访问子系统中的一群接口”,如果不这么做之前,当多个客户端时,每个客户端都需要实现调用一群接口的逻辑,实现重复且复杂。且客户端耦合每个子系统的具体实现,不符合开闭原则。用一个统一接口封装一群接口,客户端只需要依赖一个类的一个接口即可。 2. “高层接口”,说明外观类是在子系统之上,这是一个架构结构上的变化。 💡现代计算机的启动过程涉及多个硬件组件(如CPU、内存、硬盘等)的协同工作,每个组件都有自己的初始化逻辑。 没有外观模式时的实现: // 子系统类(与之前相同) class CPU { public void start() { System.out.println("CPU启动"); } } class Memory { public void load() { System.out.println("内存加载&

By 李浩

设计模式

设计模式之适配器模式

适配器模式将一个类的接口,转换成客户期待的另一个接口。适配器让原本接口不兼容的类可以合作无间。 解析这个定义: 1. 客户端依赖的接口TargetInterface,无法用现有的类adaptee实现,需要增加一个新类adapter来包装实现,但客户对此应透明,感知不到,如何才能感知不到,就需要adapter类继承或实现客户端依赖的类或接口,这样依赖抽象编程,就可以在子类去完成适配转换。 2. 适配类adapter要能实现客户要求的能力,如何做到,有两种方式,一种是直接关联一个adaptee对象,调用对象的目标方法;另一种方式是直接通过继承,自动拥有父类的目标方法。这两种实现方式称为对象适配器和类适配器。对象适配器思维更加自然,更为常用,特别是对于需要适配多个类接口时,一些语言是不支持多重继承的,无法用类适配器完成,只能应用对象适配器。 对象适配器还是类适配器,只是如何获取被适配对象的目标功能的两种不同方式而已,很多人常常采用强记的方式学习设计模式,死记类图,其实完全没有必要,更重要的事,理解模式本身的意图本质,和实现目标的思考过程。类图会依据继承或实现画法发生变化,往往让初学

By 李浩

设计模式

设计模式之单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点。 咋一看这个定义,先解决一个疑惑,什么时候需要保证类只有一个实例。在实际生产场景中,有许多这样的实例,大多出于两个目的,防止资源浪费和保证数据一致性。比如常见的数据库连接池、线程池、日志记录器、缓存实例、配置信息实例等通常只需要一个实例来管理资源,能够节省开销,避免多实例带来的数据不一致。 明确了实际用处后,再来解析定义: 1. “确保一个类只有一个实例”,类是如何实例化的,通过调用构造函数,谁调动构造函数谁就能创建实例,确保只有一个实例,就是要控制构造函数被调用的权限,防止外部代码通过构造函数或其他方式创建多个实例。 1. 私有化构造函数:将类的构造函数设为私有(或受保护),禁止外部直接调用构造函数创建实例。构造函数私有了,构造出的唯一实例也必须是本类自身维护了。 2. 控制实例化过程:在类内部管理实例的创建,确保只有一个实例存在。 2. “全局访问点”,为了让外部代码能够方便地获取这个唯一的实例,使用静态方法即可。 💡日志记录器(Logger),应用程序的多个模块需要记录日志。日志记录器应该是全局唯

By 李浩

设计模式

设计模式之工厂模式

工厂方法模式定义了创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。 来解析这个定义: 1. “创建对象”这个点非常关键,表明了工厂模式的职责就是为了创建对象的,而不是干其他委托、代理什么的。同时也将创建对象这个职责从Client迁移出去了。 2. “子类决定要实例化的类是哪一个”,说明创建对象的方法被子类继承,而这个方法的返回值是目标类,并且隐含着一个更重要的点,父类中定义的这个方法的返回值一定是一个目标抽象类,而子类返回是目标具体类。 3. “实例化推迟到子类”,表明是在客户端调用时,通过选用不同的子类,来决定目标对象的具体类。 学习设计模式,一个关键的自我校验,是能够自己给自己出题,构建场景,并通过目标设计模式来解决。 💡假设我们有一个汽车制造系统,系统需要生产不同类型的汽车(如电动车、燃油车)。我们可以使用工厂方法模式来实现这个系统,使得不同类型的汽车由不同的工厂子类来创建。 // 1. 定义抽象产品类(汽车) abstract class Car { public abstract void drive(); } /

By 李浩

设计模式

设计模式之装饰者模式

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的方案。 来解析这个定义: 1. “动态”指的在运行时进行的,而不是在编译时,也即客户端调用时可以临时决定增加责任到对象上。 2. “责任附加”意味着可以在不修改原有对象的情况下,增加新的行为或功能。 3. “比继承更有弹性”,说明使用的是组合而不是继承,通过组合可以在运行时动态地添加或移除功能。弹性意味可以组合多个能力,而不需要创建大量子类。 装饰者最重要的核心是装饰者和被装饰者来自于同一个超类。相比较于策略模式、观察者模式,难以理解一点,并不是一个自然就能想到的结构。我们从源头来看看它的设计脉络。 💡假设我们正在开发一个文档编辑器,用户可以在编辑器中输入文本,并对文本应用各种格式(如加粗、斜体、下划线等)。编辑器需要支持以下功能:用户可以动态地为文本添加或移除格式,格式可以叠加(例如,文本可以同时加粗和斜体)。 先来一个自然能想到的简单实现: // Text 类 public class PlainText { private String content;

By 李浩

设计模式

设计模式之观察者模式

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到并自动更新。 来解析这个定义: 1. “一对多依赖”说明被观察的主题需要维护一个观察者列表,当然要提供加入列表和从列表删除的能力,可以命名为attach、detach方法,来实现观察者的添加和移除。 2. “对象改变状态”说明了被观察者有一个被关注的参数属性。 3. “一个对象改变状态时,它的所有依赖者都会收到并自动更新”说明当主题发生变化时,依赖者有一个方法可以被触发执行更新操作,所谓“自动”指依赖者不必要主动调用,而是被动调用,因此依赖者需要有一个被调用的方法,可以命名为update。 4. 结合关联抽象的经验,所有依赖者应该有一个共同的抽象,给到主题维护 可以初步得到以下类图。 学习设计模式,一个关键的自我校验,是能够自己给自己出题,构建场景,并通过目标设计模式来解决。 💡新闻订阅(RSS 订阅)是一个日常生活中经常碰到的场景,这个场景中新闻发布者(NewsPublisher) 是被观察者(Subject),而 订阅者(Subscriber) 是观察者(Observer

By 李浩