什么是设计模式
Java设计模式(Design Patterns)
大家好!今天我们一起来聊聊设计模式。很多同学第一次听到这个词,可能会觉得它很高深、很抽象,仿佛是一些大牛才能掌握的秘籍。其实,大家完全不用把它想得太复杂------简单来说,设计模式就是软件工程中,针对一些反复出现的问题而总结出的最佳实践和通用的解决方案。
你可以把它理解成程序员前辈们留下来的"经验锦囊"。想象一下,你在写代码的时候遇到了一个坑,正愁不知道怎么优雅地解决,这时你打开锦囊,前辈的经验告诉你:"遇到这种场景,你可以这样设计......" 于是你就能轻松避开坑,写出更健壮、更易维护的代码。这就是设计模式的意义。
设计模式的作用?我能用它做什么?
那设计模式具体能帮我们做什么呢?换句话说,学了设计模式到底有什么好处?如果你只是写一个简单的"Hello World"程序,当然完全不需要设计模式。但是,当我们去构建一个大型、复杂的项目时,设计模式就能帮我们解决下面这些实实在在的痛点:
- 可维护性:项目做大后,需求总是在变。如果没有良好的设计,改一个功能可能会牵扯出一堆"连锁反应",甚至需要重构整个项目。设计模式教会我们如何解耦、如何分层,让修改变得像搭积木一样------抽掉一块,不影响其他模块。
- 代码复用性:很多场景其实都有相似的解决方案。比如你需要保证一个类在整个系统里只有一个实例(比如配置文件管理器),这时候单例模式就是现成的"轮子",你直接拿来用就好,不用自己再去琢磨怎么实现。这就避免了重复造轮子,提高了开发效率。
- 沟通效率:开发是团队协作的事,如果你跟同事说"我在这个地方用了一个观察者模式",对方脑海中立刻就会浮现出"一对多依赖、事件通知、松耦合"这些结构,而不用再一行行去读你的代码。设计模式成为了程序员之间的"行话",让沟通变得简洁高效。
设计模式都有哪些?是如何分类?
前面我们聊了设计模式的基本概念,知道了它就像前辈们留下的"锦囊妙计"。那么,这些锦囊妙计到底有哪些呢?按照经典的 GoF(Gang of Four,四人组) 分类,设计模式分为三大类:创建型模式、结构型模式和行为型模式。
创建型模式(Creational Patterns)
核心目的:解决"如何创建对象"的问题,让对象的创建和使用分离,从而让系统更独立、更灵活。
创建型模式说白了就是帮你把对象"生"出来 ,而且生得巧妙,不让你直接 new 一个完事。它们关注的是怎样创建对象,使得创建过程不会耦合到具体的类上。
1. 单例模式(Singleton)
- 核心定义:保证一个类只有一个实例,并提供一个全局访问点。
- 实战场景:配置管理器、数据库连接池、线程池。这些对象我们通常只需要一个实例,避免重复创建造成资源浪费或数据不一致。
- 讲法:想象一下,整个公司只有一台咖啡机,所有人要用都得去那儿接咖啡------这就是单例。你不能自己随便买一台放工位上,否则管理混乱,咖啡因超标。在代码里,单例模式就保证了类似"咖啡机"这样的全局唯一对象。
2. 工厂方法模式(Factory Method)
- 核心定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
- 实战场景 :日志记录器。假设我们有一个日志系统,可能需要输出到文件(FileLogger)、数据库(DatabaseLogger)或控制台(ConsoleLogger)。客户端不想知道具体用哪个类,只想要一个日志记录器。这时我们可以定义一个
LoggerFactory接口,里面有一个createLogger()方法,然后让FileLoggerFactory、DatabaseLoggerFactory分别实现这个接口,各自负责创建对应的日志对象。客户端只需要根据配置选择不同的工厂,就能得到想要的日志记录器。 - 生活中:工厂方法模式就像是一家总店(接口)规定每家分店(子类)都要有自己的招牌菜(创建产品的方法)。你走进不同的分店,就能吃到该店的特色菜。这样,总店不用关心每家店具体怎么做菜,只约定"有这道菜"就行。在代码里,父类定义创建对象的接口,子类负责具体实现,这样新增产品时只需增加新的工厂子类,符合"开闭原则"。
3. 抽象工厂模式(Abstract Factory)
- 核心定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
- 实战场景 :跨平台的 UI 组件库。假设我们要开发一个支持 Windows 和 Mac 风格的界面,每个风格下都有按钮(Button)、文本框(TextField)等组件。这些组件是相关的,属于同一产品族。我们不能让 Windows 下出现 Mac 风格的按钮。抽象工厂模式就可以定义一个
UIFactory,包含createButton()和createTextField()等方法,然后提供WindowsUIFactory和MacUIFactory两个具体工厂,分别创建 Windows 风格的按钮和文本框,以及 Mac 风格的按钮和文本框。客户端只需要选择对应的工厂,就能得到一整套风格一致的 UI 组件。 - 生活中:抽象工厂就像是一个家具品牌,它承诺提供一套风格统一的家具,比如现代风格的沙发、茶几、电视柜。如果你选择"宜家现代系列"工厂,你得到的沙发、茶几、电视柜都是现代风格的;如果你选择"北欧复古系列"工厂,得到的全是复古风格。你不能把不同风格的家具混搭,因为它们是成套的。抽象工厂就是为了创建一整组相关对象而设计的,确保它们属于同一产品族。
3. 建造者模式(Builder)
- 核心定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 实战场景:构造一个包含很多可选参数的复杂对象,比如一个 Computer 对象,有 CPU、内存、硬盘、显卡......你可以一步步地指定要什么配置,最后 build 出来。
- 生活中:就像去 KFC 点套餐,你可以自由组合汉堡、薯条、可乐,最后收银员给你配齐。建造者模式就是让你一步步"定制"一个复杂对象,而不是写一个巨长的构造方法,把所有参数都塞进去。
4. 原型模式(Prototype)
- 核心定义:通过复制(克隆)现有的实例来创建新的实例。
- 实战场景 :游戏里要生成大量相同类型的小怪,如果用 new 然后一个个赋值,性能很差。不如先造一个原型小怪,然后克隆它,又快又方便。Java 里的
Cloneable接口就是原型模式的应用。 - 生活中:孙悟空拔根毫毛一吹,变出一堆猴子------这就是原型模式。原型模式让我们通过复制现有对象来创建新对象,特别适合那些创建成本高、但又需要大量相似对象的场景。
结构型模式(Structural Patterns)
核心目的:类和对象的组合,关注如何将类或对象组合成更大的结构,就像搭积木一样,把功能灵活地组织起来。
1. 适配器模式(Adapter)
- 核心定义:将一个类的接口转换成客户希望的另一个接口,让原本接口不兼容的类可以协同工作。
- 实战场景 :读卡器------你把 SD 卡插进读卡器,读卡器插进 USB 口,电脑就能读取 SD 卡里的照片了。在代码里,比如老系统的日志接口是
log(String msg),新系统需要info(String msg),你可以写一个适配器,把老接口包装一下,适配到新接口上。 - 生活中:去国外旅游,插座孔型不一样,你就需要一个转换插头。适配器模式就是这个转换插头,让原本不兼容的接口能配合使用。
2. 桥接模式(Bridge)
- 核心定义:将抽象部分与实现部分分离,使它们可以独立变化。
- 实战场景:JDBC 驱动------你写的 SQL 是抽象部分,而具体连接的是 MySQL 还是 Oracle 是实现部分。JDBC 桥接了这两者,让你换数据库时不用改上层代码。
- 生活中:遥控器(抽象)和电视(实现)是分开的,你可以用同一个遥控器控制不同品牌的电视。桥接模式就是把抽象和实现解耦,让两者可以独立扩展。
3. 装饰模式(Decorator)
- 核心定义:动态地给一个对象添加额外的职责,比继承更灵活。
- 实战场景 :Java IO 流------
new BufferedInputStream(new FileInputStream("test.txt"))。FileInputStream 负责读文件,BufferedInputStream 给它加上缓冲功能,一层套一层,功能随意组合。 - 生活中:就像买了一杯咖啡,你可以加糖、加奶、加巧克力,每加一种配料,咖啡的功能(口味)就增强一点。装饰模式就是让你能动态地给对象"加料",而且可以叠加。
4. 组合模式(Composite)
- 核心定义:将对象组合成树形结构,以表示"部分-整体"的层次,让客户端可以统一对待单个对象和组合对象。
- 实战场景:文件系统------文件夹里可以包含文件或子文件夹。无论是文件还是文件夹,都有"删除""重命名"等操作。组合模式让客户端可以把文件夹和文件同等对待。
- 生活中:公司的组织架构------部门里有员工,部门下还有子部门。无论是对员工还是对部门,老板都可以发出"工作"指令,这就是组合模式的体现。
5. 外观模式(Facade)
- 核心定义:为子系统中的一组接口提供一个一致的界面,定义一个高层接口,让子系统更容易使用。
- 实战场景:电脑开机------你只要按一下开机键,背后 CPU、内存、硬盘、风扇等等一系列复杂操作就自动完成了。外观模式把复杂的子系统封装起来,给客户端一个简单的接口。
- 生活中:你只需要按开机键,不需要知道硬件是怎么自检的、怎么加载系统的。外观模式就是给你一个"一键启动"的按钮。
6. 享元模式(Flyweight)
- 核心定义:运用共享技术有效地支持大量细粒度的对象。
- 实战场景:Java 中的 String 常量池、Integer 缓存池。如果你创建多个内容相同的字符串,它们会共享同一个内存地址,避免重复创建。
- 生活中:就像共享单车------大家共用同一批单车,而不是每人买一辆。享元模式通过共享对象来减少内存消耗,特别适合大量相似对象的场景。
7. 代理模式(Proxy)
- 核心定义:为其他对象提供一种代理以控制对这个对象的访问。
- 实战场景:Spring AOP------在方法执行前后添加日志、权限检查;远程调用(RPC)------客户端调用代理对象,就像调用本地方法一样,代理负责网络通信。
- 生活中:明星的经纪人------要找明星演出,得先跟经纪人谈,经纪人帮你处理合同、档期,最后才让明星上场。代理模式就是那个经纪人,控制着对真实对象的访问。
行为型模式(Behavioral Patterns)
核心目的:对象之间的通信和责任分配,关注对象之间如何交互、如何划分职责。
1. 模板方法模式(Template Method)
- 核心定义:定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变算法结构即可重定义某些特定步骤。
- 实战场景 :Servlet 中的
doGet()/doPost()------ 整体流程由容器控制(接收请求、响应),你只需要重写特定的方法;JDBC 模板类,把获取连接、关闭连接等固定步骤封装好,你只需写 SQL。 - 生活中:就像做菜,流程是固定的:备菜、热锅、下锅、出锅。但具体做什么菜,备什么菜、怎么炒,由你自己决定。模板方法模式把固定流程定好,把可变的部分留给子类实现。
2. 命令模式(Command)
- 核心定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录日志,以及支持可撤销的操作。
- 实战场景:遥控器按钮------每个按钮对应一个命令对象,按下按钮就执行命令;事务回滚------把操作封装成命令,可以 undo。
- 生活中:去餐厅吃饭,你拿着菜单点菜,服务员把菜单传给厨师。菜单就是命令对象,它封装了你的需求,厨师只管做菜。命令模式把请求发送者和执行者解耦。
3. 迭代器模式(Iterator)
- 核心定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
- 实战场景 :Java Collection 框架中的
iterator()方法。不管底层是数组、链表还是其他数据结构,你都可以用统一的迭代器遍历元素。 - 生活中:就像翻一本书,你不需要知道书页是怎么装订的,你只需要一页一页翻下去就行。迭代器模式给你一个统一的方式遍历集合,不用关心集合内部结构。
4. 观察者模式(Observer)
- 核心定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
- 实战场景:事件监听机制------按钮被点击,所有注册的监听器都会收到通知;消息队列(MQ)------发布者发布消息,订阅者都能收到。
- 生活中:就像微信公众号,你关注了某个号,当它发文章时,你就能收到推送。观察者模式实现了一对多的通知机制,让被观察者(公众号)和观察者(读者)解耦。
5. 中介者模式(Mediator)
- 核心定义:用一个中介对象来封装一系列的对象交互,使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- 实战场景:MVC 中的 Controller ------ 它作为中介,处理用户请求,协调 Model 和 View;聊天室------用户通过聊天室发送消息,而不是直接私聊每个人。
- 生活中:就像飞机塔台,飞机之间不直接沟通,都跟塔台联系,塔台统一调度。中介者模式把多对多的关系简化为一对多,减少对象间的耦合。
6. 备忘录模式(Memento)
- 核心定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后恢复。
- 实战场景:编辑器的 Undo(撤销)功能------每操作一步,就保存一个快照,撤销时恢复到上一个快照。
- 生活中:就像游戏存档,你打 BOSS 前存个档,万一挂了可以读档重来。备忘录模式就是为了实现这种"后悔药"功能。
7. 解释器模式(Interpreter)
- 核心定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
- 实战场景 :正则表达式解析、数学表达式计算(比如计算
1+2*3)。 - 生活中:相当于我们定义了一套简单的规则,然后写个程序来解释符合这套规则的语句。实际开发中用得较少,但在编译器、规则引擎中很有用。
8. 状态模式(State)
- 核心定义:允许一个对象在其内部状态改变时改变它的行为,让对象看起来似乎修改了它的类。
- 实战场景:订单状态流转------待支付、已支付、已发货、已完成,不同状态下订单的行为不同(比如待支付才能取消,已支付不能取消)。状态模式把每个状态的行为封装到独立的类中。
- 生活中:就像红绿灯,不同颜色灯亮,车辆行为不同。状态模式把状态和对应的行为封装在一起,状态变了,行为也跟着变。
9. 策略模式(Strategy)
- 核心定义:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换,让算法的变化独立于使用算法的客户。
- 实战场景:聚合支付------根据用户选择,调用微信支付、支付宝或银联支付的算法;排序算法切换------你可以根据需要选择冒泡排序、快速排序。
- 讲法:去旅游,你可以选择坐飞机、坐火车或自驾。每一种出行方式就是一种策略。策略模式让你可以灵活切换算法,而不影响客户端。
10. 责任链模式(Chain of Responsibility)
- 核心定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
- 实战场景:Filter 过滤器------请求经过一系列过滤器,每个过滤器决定是否继续传递;OA 系统多级审批------请假单从组长传到经理传到总监,直到有人批准。
- 生活中:就像公司里的报销审批,你先提交给组长,组长批不了就转给经理,经理批不了再转给总监......责任链模式让请求沿着链传递,直到被处理。
11. 访问者模式(Visitor)
- 核心定义:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
- 实战场景:编译器的语法树分析------对抽象语法树中的不同节点做类型检查、代码生成等操作,这些操作可以封装成访问者;数据报表统计------对不同类型的数据进行统计。
- 生活中:就像一个体检中心,不同科室的医生(访问者)对同一个体检者(元素)进行检查,体检者本身不用改变。访问者模式让你在不修改元素类的情况下,为它们添加新的操作。
总结
设计模式并不是什么高深莫测的"魔法",而是前辈们从无数次实践中提炼出的套路 和经验。它们就像工具箱里的各种工具,每种工具都有自己擅长的场景:
- 创建型模式 帮你优雅地创建对象 ,避免硬编码
new; - 结构型模式 帮你灵活地组合类和对象,构建更大的结构;
- 行为型模式 帮你清晰地分配职责和通信,让对象协作更顺畅。
学设计模式,最重要的是理解每种模式的意图 和适用场景,然后在实际开发中自然而然地运用它们。记住,不要为了用模式而用模式,那样反而会让代码变得复杂。当你发现某个问题反复出现时,就可以翻翻"锦囊",看看有没有现成的模式能帮你优雅地解决。