
23 种设计模式教程(保姆级)
一、创建型模式
介绍
创建型模式是一类专门用来 解决对象创建问题的设计模式。它关注的是:对象该怎么创建,什么时候创建,由谁来创建,创建后怎么保证系统结构清晰又可扩展。
在我们日常开发中,直接用new去实例化对象虽然很方便,但如果对象的创建过程很复杂,比如需要很多配置参数,或者对象之间有依赖关系,直接new就容易把创建逻辑和业务逻辑耦合在一起,系统变得不好维护。
创建型模式通过抽象出创建对象的方式,把“怎么建造对象”这件事情独立出来,保持了代码结构的单一职责性,同时让我们在需要修改对象创建逻辑时,能很轻松地调整,不破坏已有的系统。
我们按照实用性从高到低的顺序,可以有下面这些创建型设计模式:
- 单例模式
- 工厂方法模式
- 抽象工厂模式
- 建造者模式
- 原型模式
思维导图
1. 工厂方法模式
什么是工厂方法模式?
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,旨在定义一个用于创建对象的接口,但将具体的实例化工作放到子类中去完成。换句话说,工厂方法模式通过让子类决定实例化哪个具体类,从而实现了对象创建的灵活性和可扩展性。
用通俗的话来说就是工厂方法模式就像是一个“定制工厂”,告诉它我们的需求,它根据需求提供合适的产品,而不需要我们关心具体的生产过程。
我们可以把它想象成鱼皮开了一家剧本杀店。作为店长,鱼皮会定期采购不同风格的剧本:恐怖的、悬疑的、欢乐的、情感的……但他并不是自己写剧本,而是对接了很多不同类型的剧本供应商。
每个供应商只负责某一类剧本,比如:
- 恐怖剧本供应商:专门供货《雪乡连环杀人》《死者在幻夜中醒来》
- 情感剧本供应商:提供《海是一场雨》《一座城》
- 欢乐剧本供应商:出品《离婚吧赶紧的》《来电》
鱼皮只要说一声:“我需要 5 本恐怖剧本”,恐怖供应商就负责提供;“换成 3 本情感剧本”,就找情感剧本供应商。剧本怎么选、怎么写,不归鱼皮操心,他只通过一个统一的“订剧本”接口发起请求。
这就是工厂方法模式的本质:你只关心“要什么类型的产品”,具体“怎么造出来”,交给对应的子类工厂来搞定。
为什么要使用工厂方法模式?
它有下列优点:
- 解耦代码:客户端无需知道具体的产品类,只需要依赖抽象的工厂接口,降低了代码之间的耦合度。
- 提高可扩展性:如果以后需要增加新的产品类型,只需新增相应的工厂类,而无需修改现有代码,符合开闭原则。
- 集中管理:产品的创建逻辑集中在工厂中,便于维护和管理。
为了让大家更好地感受到工厂方法模式的作用,以日志记录系统为例,我们需要支持多种日志记录方式(文件日志、数据库日志、控制台日志等)。让我们来看看使用和不使用工厂方法模式的区别:
通过对比可以看出,不使用工厂方法模式时,我们需要在 Logger 类中通过条件判断来处理不同类型的日志记录,这导致了代码耦合度高、违反开闭原则、职责不单一等问题。每当需要添加新的日志类型时,都需要修改现有代码,增加了维护成本。
而使用工厂方法模式后,我们将对象的创建与使用分离,降低了代码耦合度。添加新的日志类型时,只需创建新的工厂类,无需修改现有代码,符合开闭原则。同时,通过工厂接口封装了具体日志记录器的创建细节,使代码结构更加清晰,职责更加单一。
工厂方法模式的应用场景
举一些开发中典型的应用场景:
- 消息通知系统:系统支持短信、邮件、站内信等不同通知方式时,可通过工厂方法屏蔽创建细节,让调用方只负责发消息。
- 支付系统:用户可能选择支付宝、微信、银行卡等支付方式,工厂方法可以根据用户选择生成对应的支付通道对象,方便扩展新方式。
- 文件解析模块:上传的文件可能是 Excel、CSV、JSON、XML 等不同格式,工厂方法可以根据文件类型创建对应的解析器实例,统一解析入口。
- 业务规则引擎:当一个系统支持多个业务规则版本(如不同行业、不同客户),可以通过工厂方法创建对应的规则计算器,灵活支持定制化逻辑。
工厂方法模式的基本结构
工厂方法模式具有的角色和职责:
1)抽象产品(Product):定义产品的公共接口,是所有具体产品的父类。
2)具体产品(ConcreteProduct):实现了抽象产品接口,表示某种具体的产品。
3)抽象工厂(Factory):定义了一个返回产品对象的方法(一般是一个抽象方法)。
4)具体工厂(ConcreteFactory):实现了抽象工厂中的创建产品的方法,生成具体的产品实例。
下面用一张类图帮大家更直观地理解工厂方法模式的结构:
工厂方法模式的代码实现
我们以 “消息通知系统” 为例来实现抽象工厂模式,可以快速创建消息对象,整个流程如下:
1)定义抽象产品接口(比如通知接口)
▼java复制代码// 抽象产品接口:通知消息 public interface Message { void send(String to, String content); }
这个接口是所有“消息产品”的统一标准,无论是短信、邮件还是站内信,都必须实现 send 方法。
2)定义多个具体产品,比如短信、邮件、站内信:
▼java复制代码public class SmsMessage implements Message { @Override public void send(String to, String content) { System.out.println("发送短信给 " + to + ",内容:" + content); } } public class EmailMessage implements Message { @Override public void send(String to, String content) { System.out.println("发送邮件给 " + to + ",内容:" + content); } } public class InAppMessage implements Message { @Override public void send(String to, String content) { System.out.println("发送站内信给 " + to + ",内容:" + content); } }
每个类都实现了 Message 接口,表示各自的发送方式。
3)定义抽象工厂接口
▼java复制代码// 抽象工厂接口:负责生产消息对象 public interface MessageFactory { Message createMessage(); }
这是抽象工
