装饰器模式与开闭原则

发布时间:2026/10/23 10:29:23
装饰器模式与开闭原则 本系列将持续更新「设计模式」相关内容 从核心设计原则到设计模式帮你真正用起来 觉得有帮助欢迎点个关注 一起交流学习共同进步前文本章可以称为“为爱用继承的家伙打开设计之眼”。我们即将再度探讨典型的继承滥用问题。你将学到如何在运行时使用对象组合的形式装饰你的类。为什么一旦知道装饰的技巧你将能给你的或别人的对象一个新的责任而不需要对底层类的代码做任何改变。本文的灵感与知识骨架主要来自神书《Head First 设计模式》中的装饰者模式章节。为了更易读笔者用自己的“文件读取与后缀装饰”案例进行了重构并加入了个人理解。水平有限如有错漏欢迎指正~本文讲解逻辑简单继承实现 → 发现问题 → 引入开闭原则 → 装饰器模式。1.一个简单的文件读取程序1.1 程序介绍假设公司要求我们开发一个文件读取的程序该程序可以读取数字字母还有特殊符号三个功能基础类图如下:DataReaderread() : StringNumberReaderread() : StringLetterReaderread() : StringSpecialCharReaderread() : String1.2新的需求现在老板需要我们给读取到的数据都增加一个后缀.data。2. 第一种实现方式有误导2.1使用继承来复用代码使用继承的复用功能我们可以在父类的读取方法中实现后缀data功能然后子类的方法中在自己的逻辑基础上调用父类的读取方法即可代码如下:abstractclassDataReader{publicStringread(){return.data;}}classNumberReaderextendsDataReader{OverridepublicStringread(){return12345super.read();}}classLetterReaderextendsDataReader{OverridepublicStringread(){returnabcdesuper.read();}}classSpecialCharReaderextendsDataReader{OverridepublicStringread(){return!#$%super.read();}}测试一下:publicclassClient{publicstaticvoidmain(String[]args){DataReaderreadernewNumberReader();System.out.println(reader.read());// 12345.datareadernewLetterReader();System.out.println(reader.read());// abcde.datareadernewSpecialCharReader();System.out.println(reader.read());// !#$%.data}}2.2又有新的需求上面的代码运行得似乎很完美但现在老板需要我们给除了数字之外的数据都添加上后缀 NotNumber。为了让两个子类复用同一段后缀逻辑我们很自然地想到把它放进父类的 read() 里代码如下abstractclassDataReader{publicStringread(){return.dataNotNumber;}}此时继承复用的问题就暴露了1.为了复用只能改父类。想让 LetterReader 和 SpecialCharReader 共享 NotNumber 逻辑就得动 DataReader若不想动父类就只能在这两个子类里各编码 .data 和 NotNumber重复且难维护——两条路都不好走。2.数字被误伤。NumberReader 通过 super.read() 被动继承了父类的返回值明明不在需求范围内却被加上了 NotNumber。3.继承是编译期绑定。.data、NotNumber 都写死在 read() 的返回值里运行时既无法切换后缀也无法决定这个对象要不要加更无法叠加多种后缀。3.开闭原则3.1为什么要开闭原则分析2.2的例子我们可以发现一个类的职责功能是会变化的所以我们应该对外提供扩展的功能同时不能修改原有的代码例子2.2中当增加扩展功能的时候我们不得不修改原有父类代码同时子类中的逻辑也会受到改变并且数字类还出错了这就是为什么需要对修改关闭~3.2开闭原则定义软件实体应当对扩展开放但对修改关闭。4.装饰器模式4.1装饰器模式原理定义和类图有个模式是比较符合开闭原则的就是装饰器模式我们来看看它的原理:装饰器模式的本质是动态地给一个对象添加一些额外的职责。它通过组合的方式把核心功能和附加功能一层层包裹起来形成类似“洋葱”或“俄罗斯套娃”的结构。定义:动态地给一个对象添加一些额外的职责。就增加功能而言装饰者模式比生成子类更为灵活。类图:4.2让我们的程序使用装饰器模式类图:«abstract»DataReaderread() : StringNumberReaderread() : StringLetterReaderread() : StringSpecialCharReaderread() : String«abstract»DataReaderDecorator#DataReader readerDataReaderDecorator(DataReader reader)read() : StringDataSuffixDecoratorDataSuffixDecorator(DataReader reader)read() : StringNotNumberDecoratorNotNumberDecorator(DataReader reader)read() : String代码:// 抽象构件只负责定义读取行为不再承载任何后缀逻辑abstractclassDataReader{publicabstractStringread();}// 具体构件各自只关心自己的核心数据classNumberReaderextendsDataReader{OverridepublicStringread(){return12345;}}classLetterReaderextendsDataReader{OverridepublicStringread(){returnabcde;}}classSpecialCharReaderextendsDataReader{OverridepublicStringread(){return!#$%;}}// 抽象装饰者持有被装饰对象并保持与构件相同的接口abstractclassDataReaderDecoratorextendsDataReader{protectedDataReaderreader;publicDataReaderDecorator(DataReaderreader){this.readerreader;}OverridepublicStringread(){returnreader.read();}}// 具体装饰者追加 .data 后缀classDataSuffixDecoratorextendsDataReaderDecorator{publicDataSuffixDecorator(DataReaderreader){super(reader);}OverridepublicStringread(){returnreader.read().data;}}// 具体装饰者追加 NotNumber 后缀classNotNumberDecoratorextendsDataReaderDecorator{publicNotNumberDecorator(DataReaderreader){super(reader);}OverridepublicStringread(){returnreader.read()NotNumber;}}代码测试:publicclassClient{publicstaticvoidmain(String[]args){// 1. 老板的需求一所有数据都加 .dataDataReadernumbernewDataSuffixDecorator(newNumberReader());System.out.println(number.read());// 12345.data// 2. 老板的需求二数字之外的再加 NotNumberDataReaderletternewNotNumberDecorator(newDataSuffixDecorator(newLetterReader()));System.out.println(letter.read());// abcde.dataNotNumberDataReaderspecialnewNotNumberDecorator(newDataSuffixDecorator(newSpecialCharReader()));System.out.println(special.read());// !#$%.dataNotNumber// 3. 数字不在 NotNumber 的需求范围内就不加这层装饰按需组合// 此处 number 已经是 12345.data没有被 误伤// 4. 想调整顺序装饰顺序由客户端自由决定DataReaderanothernewDataSuffixDecorator(newNotNumberDecorator(newLetterReader()));System.out.println(another.read());// abcdeNotNumber.data// 5. 想不加任何后缀也行直接用具体构件DataReaderrawnewLetterReader();System.out.println(raw.read());// abcde// 6. 甚至可以把两个装饰者同时叠加到同一个对象上DataReadertwicenewDataSuffixDecorator(newDataSuffixDecorator(newNumberReader()));System.out.println(twice.read());// 12345.data.data}}

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询