装饰模式
一、概念
装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
定义一个抽象的装饰类,将具体的装饰类作为其子类,然后继承具体的装饰类。
二、使用场景
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。
使用装饰模式的步骤:
在装饰类的内部维护一个被装饰类的引用。
让装饰类有一个共同的父类或者是父接口。
三、UML结构图
装饰模式UML.png
四、代码示例
案例一:
Component:
public interface Component { public void operation(); }
Decorator:
public class Decorator implements Component{ //维持一个对抽象构件对象的引用 private Component component; //注入一个抽象构件类型的对象(通过构造方法或者set方法) public Decorator(Component component) { this.component = component; } @Override public void operation() { //调用原有业务方法,此处没有真正的实现operation方法,具体的装饰过程交由子类完成 component.operation(); } }
ConcreateDecorator:
public class ConcreateDecorator extends Decorator{ public ConcreateDecorator(Component component) { super(component); } public void operation(){ //调用原有业务方法 super.operation(); //调用新增业务方法 addedBehavior(); } //新增业务方法 public void addedBehavior(){ } }
案例二:
使用继承的方式增强一个类的功能:
package com.hcx.pattern;import java.io.BufferedReader;import java.io.File;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;import java.io.Reader;/** * 拓展BufferedReader的功能, 增强readLine方法,返回的字符串带有行号。 * @author hcx * */class BufferedLineNum extends BufferedReader{ //行号 int count = 1; public BufferedLineNum(Reader in) { super(in); } @Override public String readLine() throws IOException { String line = super.readLine(); if(line==null) { return null; } line = count+" "+line; count++; return line; } }/** * 带分号的缓冲输入字符流 * @author hcx * */class BufferedSemi extends BufferedReader{ public BufferedSemi(Reader in) { super(in); } @Override public String readLine() throws IOException { String line = super.readLine(); if(line==null) { return null; } line = line+";"; return line; } }/** * 带双引号的缓冲输入字符流 * @author hcx * */class BufferedQuto extends BufferedReader{ public BufferedQuto(Reader in) { super(in); } @Override public String readLine() throws IOException { String line = super.readLine(); if(line == null) { return null; } line = "\""+line+"\""; return line; } }public class Demo1 { public static void main(String[] args) throws IOException { File file = new File("F:\\Demo1.java"); //建立数据的输入通道 FileReader fileReader = new FileReader(file); //建立带行号的缓冲输入字符流 BufferedLineNum bufferedLineNum = new BufferedLineNum(fileReader); //带有分号的缓冲输入字符流 BufferedSemi bufferedSemi = new BufferedSemi(fileReader); //带有双引号的缓冲输入字符流 BufferedQuto bufferedQuto = new BufferedQuto(fileReader); String line = null; while((line = bufferedLineNum.readLine())!=null) { System.out.println(line); } } }
使用装饰模式增强一个类的功能:
package com.hcx.pattern;import java.io.BufferedReader;import java.io.File;import java.io.FileReader;import java.io.IOException;import java.io.Reader;/** * 带行号的缓冲输入字符流 * @author hcx * */class BufferedLineNum2 extends BufferedReader{ //在内部维护一个被装饰类的引用。 BufferedReader bufferedReader; int count = 1; public BufferedLineNum2(BufferedReader bufferedReader) { // 注意: 该语句没有任何的作用,只不过是为了让代码不报错。 super(bufferedReader); this.bufferedReader = bufferedReader; } public String readLine() throws IOException { String line = bufferedReader.readLine(); if(line==null) { return null; } line = count+" "+line; count++; return line; } }/** * 带分号缓冲输入字符流 * 继承的原因:为了让这些装饰类的对象可以作为参数进行传递,达到互相装饰的效果。 * @author hcx * */class BufferedSemi2 extends BufferedReader{ //在内部维护一个被装饰类的引用。 BufferedReader bufferedReader; public BufferedSemi2(BufferedReader bufferedReader) { //BufferReader没有无参的构造方法,继承了BufferedReader,所以要指定调用父类的带参的构造方法 super(bufferedReader);// 注意: 该语句没有任何的作用,只不过是为了让代码不报错。 this.bufferedReader = bufferedReader; } public String readLine() throws IOException{ //创建该类对象时,如果传入的是buffereLineNum,则这里的ReadLine方法是调用了buffereLineNum的readLine方法. String line = bufferedReader.readLine(); if(line==null){ return null; } line = line +";"; return line; } }/** * 带双引号缓冲输入字符流 * @author hcx * */class BufferedQuto2 extends BufferedReader{ //在内部维护一个被装饰的类 BufferedReader bufferedReader; public BufferedQuto2(BufferedReader bufferedReader){ //new BufferedSemi2(); super(bufferedReader) ; //只是为了让代码不报错 this.bufferedReader = bufferedReader; } public String readLine() throws IOException{ //创建该类对象时,如果传入的是bufferedSemi2,则这里的ReadLine方法是调用了bufferedSemi2的readLine方法. String line = bufferedReader.readLine(); if(line==null){ return null; } line = "\""+line +"\""; return line; } }public class Demo2 { public static void main(String[] args) throws IOException { File file = new File("F:\\Demo1.java"); FileReader fileReader = new FileReader(file); //建立缓冲输入字符流 BufferedReader bufferedReader = new BufferedReader(fileReader); //建立带行号的缓冲输入字符流 BufferedLineNum2 bufferedLineNum = new BufferedLineNum2(bufferedReader); //带分号的缓冲输入字符流 BufferedSemi2 bufferedSemi2 = new BufferedSemi2(bufferedLineNum); //带双引号的缓冲输入字符流 BufferedQuto2 bufferedQuto2 = new BufferedQuto2(bufferedSemi2); String line = null; while((line = bufferedQuto2.readLine())!=null){ System.out.println(line); } } }
案例三:
一家三口每个人都会工作,儿子的工作就是画画,母亲的工作就是在儿子的基础上做一个增强,不单止可以画画,还可以上涂料。爸爸的工作就是在妈妈基础上做了增强,就是上画框。
interface Work{ public void work(); }class Son implements Work{ @Override public void work() { System.out.println("画画"); } }class Mather implements Work{ //需要被增强的类。 Work worker; public Mather(Work worker){ this.worker = worker; } @Override public void work() { worker.work(); System.out.println("给画上颜色"); } }class Father implements Work{ //需要被增强的类的引用 Work worker; public Father(Work worker){ this.worker = worker; } @Override public void work() { worker.work(); System.out.println("上画框"); } }public class Demo { public static void main(String[] args) { Son s = new Son();// s.work(); Mather m = new Mather(s);// m.work(); Father f = new Father(s); f.work(); } }
总结:
继承实现的增强类和装饰模式实现的增强类有何区别?
继承实现的增强类:
优点:代码结构清晰,而且实现简单.
缺点:对于每一个的需要增强的类都要创建具体的子类来帮助其增强,这样会导致继承体系过于庞大。
装饰模式实现的增强类:
优点:内部可以通过多态技术对多个需要增强的类进行增强,可以使这些装饰类达到互相装饰的效果。使用比较灵活。
缺点:需要内部通过多态技术维护需要被增强的类的实例。进而使得代码稍微复杂。
五、装饰模式的优点
对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增长。
可以通过一种动态的方式来扩展一个对象的功能
可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合。
作者:JS_HCX
链接:https://www.jianshu.com/p/65f56a4d982b