装饰器模式与开闭原则 本系列将持续更新「设计模式」相关内容 从核心设计原则到设计模式帮你真正用起来 觉得有帮助欢迎点个关注 一起交流学习共同进步前文本章可以称为“为爱用继承的家伙打开设计之眼”。我们即将再度探讨典型的继承滥用问题。你将学到如何在运行时使用对象组合的形式装饰你的类。为什么一旦知道装饰的技巧你将能给你的或别人的对象一个新的责任而不需要对底层类的代码做任何改变。本文的灵感与知识骨架主要来自神书《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}}