设计模式--结构模式--装饰模式
一、基本概念
有一次面试时,面试官问了一个问题,怎么做类增强,当时心里想,这是什么鬼问题,通过实现接口,继承类不就可以了吗,当时不知道面试官的想法或者要解决什么问题。后续带着这个问题做项目,碰到了装饰模式,才明白,面试官可能是让我理解一下设计模式的应用。
Java中三个基本特性:继承、接口、反射是实现扩展功能基础手段。其他模式的本质也无非是这些。
装饰模式(Decorator Pattern) ,也称为包装模式(wapper Pattern):动态地给一个对象增加一些额外的职责。它最明显的区别就是,不是继承一个类,而是通过组合的方式,增强了功能。
普通共识:装饰模式比生成子类实现更为灵活,装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,可以在不需要创建更多子类的情况下,让对象的功能得以扩展。是一种用于替代继承的技术,它通过一种无须定义子类的方式给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。
如果从方便认知上可以这样理解:继承,认为是爷爷->父亲->儿子,辈分不一样;而装饰模式呢,可以认为是辈分是一致,只做增强和特性化需求。
二、简单理解
这个小段,用一个小例子说明一下装饰模式(Decorator Pattern) /包装模式(wapper Pattern)。
1、定义枪的接口,枪可以装刺刀和射击
package comm.pattern.struct.decorator.wapper; import java.security.PublicKey; /** * * @Title: IGun.java * @Package: comm.pattern.struct.decorator.wapper * @Description: 定义枪的类型 * @author yangzhancheng * @2022年5月25日:下午11:02:52 * */ public interface IGun { /** * 装刺刀,尽量不这么定义,一个类定义一个方法 */ public void bayonet(); /** * 射击 */ public void shut(); }
2、定义步枪(非全自动步枪,只能一发一射)
package comm.pattern.struct.decorator.wapper; /** * * @Title: Rifle.java * @Package: comm.pattern.struct.decorator.wapper * @Description: 步枪 * @author yangzhancheng * @2022年5月25日:下午11:04:58 * */ public class Rifle implements IGun { /** * 装刺刀 */ public void bayonet() { System.out.println("步枪装刺刀...."); } /** * 射击 */ @Override public void shut() { System.out.println("步枪射击,点射"); } }
3、定义自动步枪(在步枪的基础上增强,或者说是包装一下)
package comm.pattern.struct.decorator.wapper; /** * * @Title: AutoRifleMapper.java * @Package: comm.pattern.struct.decorator.wapper * @Description: 自动步枪 * @author yangzhancheng * @2022年5月25日:下午11:08:14 * */ public class AutoRifleMapper implements IGun{ private IGun iGun; public AutoRifleMapper(IGun iGun){ this.iGun = iGun; } /** * 装刺刀 */ @Override public void bayonet(){ iGun.bayonet(); } /** * 射击 */ @Override public void shut(){ iGun.shut(); System.out.println("自动步枪射击,连续射击"); } }
4、测试类
package comm.pattern.struct.decorator.wapper; /** * * @Title: Client.java * @Package: comm.pattern.struct.decorator.wapper * @Description: 客户端演示 * @author yangzhancheng * @2022年5月25日:下午11:18:50 * */ public class Client { public static void main(String[] args) { IGun rifle = new Rifle(); rifle.bayonet(); rifle.shut(); System.out.println("\n---------------------------------"); AutoRifleMapper autoRifleMapper = new AutoRifleMapper(rifle); autoRifleMapper.bayonet(); autoRifleMapper.shut(); } }
测试结果
步枪装刺刀.... 步枪射击,点射 --------------------------------- 步枪装刺刀.... 步枪射击,点射 自动步枪射击,连续射击
三、按设计模式思路理解
(一)基本概念
1、装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。
2、装饰对象包含一个真实对象的引用,非常关键。
3、角色:
A:抽象构件角色(Component):定义一个对象接口或抽象类,可以给这些对象动态地添加职责。
B:具体构件角色(ConcreteComponent):实际被动态地添加职责的对象。
C:抽象装饰者角色(Decorator):实现了Component接口,用来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。
D:具体装饰者角色(ConcreteDecorator):动态地添加职责的对象。
E:客户端(Client)
(二)例子(为简单实现,把C省略了)
1:抽象构件角色(Component):
package comm.pattern.struct.decorator; /** * * @Title: Component.java * @Package: comm.pattern.struct.decorator * @Description: 抽象构建,模拟杨过 * @author yangzhancheng * @2020年3月16日:上午12:00:38 * */ public interface Component { public void operation(); }
2:具体构件角色(ConcreteComponent):
package comm.pattern.struct.decorator; public class ConcreteComponent implements Component { public void operation() { System.out.println("小张说:'杨过,名过,字改之,是金庸武侠小说《神雕侠侣》的主人公';"); } }
3:抽象装饰者角色(Decorator):
省略。
4:具体装饰者角色(ConcreteDecorator):
4-1:
package comm.pattern.struct.decorator; /** * * @Title: Decorator.java * @Package: comm.pattern.struct.decorator * @Description: 装饰类 * @author yangzhancheng * @2020年3月16日:上午12:05:54 * */ class DecoratorA implements Component { private Component component; public DecoratorA(Component component) { this.component=component; } public void operation() { component.operation(); System.out.println("小李补充:‘他在活死人墓待过,练过玉女心经’;"); } }
4-2:
package comm.pattern.struct.decorator; /** * * @Title: DecoratorB.java * @Package: comm.pattern.struct.decorator * @Description: 装饰类 * @author yangzhancheng * @2020年3月16日:上午12:10:12 * */ class DecoratorB implements Component { private Component component; public DecoratorB(Component component) { this.component = component; } public void operation() { component.operation(); System.out.println("小黄补充:‘他有一只雕兄’。"); } }
5:客户端(Client)\测试类
package comm.pattern.struct.decorator; /** * * @Title: Client.java * @Package: comm.pattern.action.templateMethod * @Description: 描述该文件做什么 * @author yangzhancheng * @2020年3月1日:下午4:47:17 * */ public class Client { public static void main(String[] args) { /** * 定义基础信息,他是杨过 */ Component component = new ConcreteComponent(); component.operation(); /** * 增强一次,或者说是包装/装饰一下,使得人物信息更完美 */ System.out.println("---------------------------------"); Component partyComponentDec = new DecoratorA(component); partyComponentDec.operation(); /** * 再次增强一次,或者说是包装/装饰一下,使得人物信息更完美,还生动 */ System.out.println("---------------------------------"); Component allComponent = new DecoratorB(new DecoratorA(new ConcreteComponent())); allComponent.operation(); } }
运行输出
小张说:'杨过,名过,字改之,是金庸武侠小说《神雕侠侣》的主人公'; --------------------------------- 小张说:'杨过,名过,字改之,是金庸武侠小说《神雕侠侣》的主人公'; 小李补充:'他在活死人墓待过,练过玉女心经'; --------------------------------- 小张说:'杨过,名过,字改之,是金庸武侠小说《神雕侠侣》的主人公'; 小李补充:'他在活死人墓待过,练过玉女心经'; 小黄补充:'他有一只雕兄'。
四、优缺点
装饰与外观模式的比较:装饰模式是对同一类接口进行功能增强;外观模式,是把子类集中到一起,让另外一个类统一展示。
优点:
1、装饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
2、通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
3、装饰者完全遵守开闭原则。
缺点:
1、会出现更多的代码,更多的类,增加程序复杂性。
2、动态装饰时,多层装饰时会更复杂
五、源代码分析
(一)springBoot 中 wapper
(二)IO 工作流
1、用Reader\BufferedReader\InputStreamReader\FileReader 说明,其中InputStreamReader\FileReader 看成是一个,因为他们是通过继承实现的。
A、BufferedReader 与 Reader关系,定义了read()方法为输入。
B、InputStreamReader\FileReader 和reader关系
2、用角色模型分析设计模式
A:抽象构件角色(Component):
public abstract class Reader implements Readable, Closeable { //读出流处理 public int read() throws IOException { char cb[] = new char[1]; if (read(cb, 0, 1) == -1) return -1; else return cb[0]; }
//读出流处理 public int read(char cbuf[]) throws IOException { return read(cbuf, 0, cbuf.length); } abstract public int read(char cbuf[], int off, int len) throws IOException; }
B:具体构件角色(ConcreteComponent):
package java.io; public class FileReader extends InputStreamReader { public FileReader(String fileName) throws FileNotFoundException { super(new FileInputStream(fileName)); } public FileReader(File file) throws FileNotFoundException { super(new FileInputStream(file)); } public FileReader(FileDescriptor fd) { super(new FileInputStream(fd)); } }
package java.io; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import sun.nio.cs.StreamDecoder; public class InputStreamReader extends Reader { private final StreamDecoder sd; public int read() throws IOException { return sd.read(); } public int read(char cbuf[], int offset, int length) throws IOException { return sd.read(cbuf, offset, length); } }
C:具体装饰者角色(ConcreteDecorator):
package java.io; public class BufferedReader extends Reader { //构造 注意Reader类型就可以 public BufferedReader(Reader in) { this(in, defaultCharBufferSize); } //重新增强了read方法 public int read() throws IOException { synchronized (lock) { ensureOpen(); for (;;) { if (nextChar >= nChars) { fill(); if (nextChar >= nChars) return -1; } if (skipLF) { skipLF = false; if (cb[nextChar] == '\n') { nextChar++; continue; } } return cb[nextChar++]; } } } //重新增强了read方法 public int read(char cbuf[], int off, int len) throws IOException { synchronized (lock) { ensureOpen(); if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int n = read1(cbuf, off, len); if (n <= 0) return n; while ((n < len) && in.ready()) { int n1 = read1(cbuf, off + n, len - n); if (n1 <= 0) break; n += n1; } return n; } } //read方法不是特别好用,增强了readLine方法,这个就是比较巧妙的用法 public String readLine() throws IOException { return readLine(false); } //readLine的具体实现方法 String readLine(boolean ignoreLF) throws IOException { StringBuffer s = null; int startChar; synchronized (lock) { ensureOpen(); boolean omitLF = ignoreLF || skipLF; bufferLoop: for (;;) { if (nextChar >= nChars) fill(); if (nextChar >= nChars) { /* EOF */ if (s != null && s.length() > 0) return s.toString(); else return null; } boolean eol = false; char c = 0; int i; /* Skip a leftover '\n', if necessary */ if (omitLF && (cb[nextChar] == '\n')) nextChar++; skipLF = false; omitLF = false; charLoop: for (i = nextChar; i < nChars; i++) { c = cb[i]; if ((c == '\n') || (c == '\r')) { eol = true; break charLoop; } } startChar = nextChar; nextChar = i; if (eol) { String str; if (s == null) { str = new String(cb, startChar, i - startChar); } else { s.append(cb, startChar, i - startChar); str = s.toString(); } nextChar++; if (c == '\r') { skipLF = true; } return str; } if (s == null) s = new StringBuffer(defaultExpectedLineLength); s.append(cb, startChar, i - startChar); } } } }
D:客户端/测试端(Client)
package com.yang.springBoot010; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; public class IOtest { public static void main(String[] args) throws Exception { BufferedReader bufferedReader = null; FileReader fReader = null; try { fReader = new FileReader(new File("/工作日志.txt")); bufferedReader = new BufferedReader(fReader); String strline = null; while (null != (strline = bufferedReader.readLine())) { System.out.println(strline); } } catch (Exception e) { System.out.println(e); } finally { if (bufferedReader != null) { bufferedReader.close(); } if (fReader != null) { fReader.close(); } } } }