装饰者模式
装饰者模式是23种设计模式之一,是指在不改变原来的类和使用继承的方式,动态的扩展这个类的功能。装饰者允许向一个现有的对象添加特定的功能却不改变它的结构。通过一个类来包装原有的类来提供额外的功能。
特点
装饰者模式的结构:
Component:抽象组件,被装饰的原始对象,可以是抽象类或者接口
ConcreatCompoent:实现或者继承了抽象组件,被装饰的具体的实现
Decorator:抽象的装饰者,实现或者继承了Component抽象组件,持有抽象组件的引用
ConcreatDecotarorA、ConcreatDecotarorB:具体的装饰者,实现或者继承了抽象装饰者。
实现
举个简单的例子,假设有一个科沃斯机器人,它的基本功能是扫地,现在要求这个机器人可以边扫地边唱歌,还可以边扫地边跳舞。
那么,就可以将机器定义为一个抽象的类,里面有一个抽象的方法,sweep();
/** * 抽象组件 */ public abstract class Robot { /** * 打扫的方法 */ public abstract void sweep(); }
被装饰的具体对象:
/** * 抽象组件的具体实现类,即被装饰者对象 */ public class ConcreatRobot extends Robot { @Override public void sweep() { System.out.println("打扫卧室"); } }
抽象装饰者:
/** * 抽象的装饰者类 */ public class DecoratorRobot extends Robot { private Robot robot; public DecoratorRobot(Robot robot) { this.robot = robot; } @Override public void sweep() { robot.sweep(); } }
具体的装饰者A:
/** * 装饰者A */ public class ConcreatDecoratorA extends DecoratorRobot { public ConcreatDecoratorA(Robot robot) { super(robot); } /** * 一遍扫地一边唱歌 */ @Override public void sweep() { super.sweep(); this.sign(); } /** * 唱歌 */ public void sign(){ System.out.println("唱一首单身情歌"); } }
具体的装饰者B:
/** * 装饰者B */ public class ConcreatDecoratorB extends RobotDecorator { public ConcreatDecoratorB(Robot robot) { super(robot); } /** * 一遍扫地一边跳舞 */ @Override public void sweep() { super.sweep(); dance(); } /** * 唱歌 */ public void dance(){ System.out.println("跳一支探戈"); } }
客户端调用:
public class DecoratorTest { public static void main(String[] args) { Robot robot = new ConcreatRobot(); RobotDecorator signRobot = new ConcreatDecoratorA(robot); signRobot.sweep(); RobotDecorator danceRobot = new ConcreatDecoratorB(robot); danceRobot.sweep(); } } 控制台输出: 打扫卧室 唱一首单身情歌 打扫卧室 跳一支探戈
从例子中可以看出,装饰的模式是一种组合的概念,所谓装饰,就是要去扩展被装饰者从而扩展功能,而我们客户端在调用的时候,可以选择合适的装饰类来达到我们想要的结果。比如我只想让机器人扫地,那么我只需要调用具体的抽象实现类,我想让机器人又扫地又跳舞,那么就使用包装类。
装饰者模式是为已有功能动态的添加更多功能的一种方式。那么,在什么时候使用呢,当系统需要新的功能的时候,向旧代码中添加新的代码显然增加了原有代码的复杂度,并且违反了开闭原则。这个时候,装饰者提供了一个非常好的机会,我们可以通过包装这个主类,把每个要包装的功能单独的写成一个包装类,从而扩展该类的功能。客户端可以根据需要,调用特定功能的包装类。这样做的好处是把类中的装饰功能或者和核心功能分开,简化原有的类。
装饰者模式在java IO中的应用
装饰者模式在JDK中的典型应用莫过于IO体系了。Java中的IO分为两种,字节流和字符流,其中每一种分别可以分为输入流和输出流,我们来看下IO体系的结构图。
我们以InputStream为例,InputStream是抽象的父类,FilterInputtream和ByteArrayInputStream继承了InputStream,这些类继承了InputStream的基本的字节读取功能,FilterInputtream还持有了InputStream的引用,它的所有的方法,都是调用了这个引用的同名方法,也就是说,他把所有的调用都委托给了InputStream。并且FilterInputtream还有一些子类,比如BufferdInputStream,DataInputStream等。BufferedInputStream提供了缓冲输入流,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。由此可见,BufferedInputStream为我们提供了一个从缓冲区读取数据的功能,就像是科沃斯机器人,经过包装后为我们提供了唱歌的功能一样。我们来看下这几个类的类图。
正如图中所示,他们的结构完全满足装饰者模式的结构图。同理,像DataInputStream也是一个包装类,他提供了与机器无关方式从底层输入流中读取的功能。
我们来看下以BufferdInputStream实现数据读取的例子。
public class DecoratorTest { public static void main(String[] args) { InputStream in = null; BufferedInputStream bi = null; try { in = new FileInputStream("E:\\decorator.txt"); //in 是一个文件输入流,通过BufferedInputStream给它添加从缓冲区读取数据的功能。 //BufferedInputStream就是一个包装类 bi = new BufferedInputStream(in); int count = 0; byte[] buffer = new byte[1024]; while ((count = bi.read(buffer)) != -1) { System.out.println(new String(buffer, 0, count)); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (bi != null) { bi.close(); } if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
我自己之前学习IO流的时候,看到这么多种类的流也是很难理解,当我以设计模式角度去解读这些流时,会更加容易理解。装饰者模式的实现对于使用者是透明的,我们必须了解这个装饰者是什么功能,才能恰当的使用这个类。