1. 是什么——定义
装饰者模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
2. 为什么——特点
1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
2) 装饰对象包含一个真实对象的引用。
3) 装饰对象接受所有来自客户端的请求,它把这些请求转发给真实的对象。
4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
3. 什么时候用——适用性
1) 需要扩展一个类的功能,或给一个类添加附加职责。
2) 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
3) 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
4) 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
4. UML图
5. 怎么用——使用方法
需求:
模拟人穿衣服
5.1 没有任何原则的初始设计
public class Demo1 { public static void main(String[] args) { Person person = new Person(); person.setName("二哈"); person.wearTshirt(); person.wearJeans(); person.wearShoes(); }}class Person { private String name; public void wearTshirt() { System.out.println(name + "穿了一件T恤"); } public void wearSweater() { System.out.println(name + "穿了一件毛衣"); } public void wearJeans() { System.out.println(name + "穿了一条裤子"); } public void wearShoes() { System.out.println(name + "穿了一双鞋"); }// public ..... -- 违反开放-封闭原则 public String getName() { return name; } public void setName(String name) { this.name = name; }}
可以看到,如果要增加新的衣服,就需要扩展方法,这就违背了“开放-封闭原则”。
5.2 将“服装”抽象
public class Demo2 { public static void main(String[] args) { Person person = new Person(); Clothes Tshirt = new TShirt(); Clothes shoes = new Shoes(); Clothes superman = new SupermanClothes(); person.wear(superman); person.wear(Tshirt); person.wear(shoes); }}class Person { private String name; public void wear(Clothes clothes) { System.out.println(name + "穿了" + clothes.getName()); } public String getName() { return name; } public void setName(String name) { this.name = name; }}abstract class Clothes { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}class TShirt extends Clothes { }class Shoes extends Clothes { }class SupermanClothes extends Clothes { }
这样确实达到效果了,但每次穿衣服的时候总感觉是当着众人的面穿,而且逻辑也不对:每次都是人在穿,但实际情况是:穿了一件后,下一件衣服是套在原来那件衣服上!需要改进!
5.3 使用装饰者模式
public class Demo3 { public static void main(String[] args) { Person person = new Person(); person.setName("二狗"); Clothes tshirt = new Tshirt(); person.wear(tshirt); Clothes coat = new Coat(); tshirt.wear(coat); coat.show(); }}class Person { private String name; public void show() { System.out.println(name); } public void wear(Clothes clothes) { clothes.setWornClothes(this); } public String getName() { return name; } public void setName(String name) { this.name = name; }}abstract class Clothes extends Person { private Person wornClothes; //要被穿的衣服/人 private String clothesName; @Override public void show() { System.out.println(clothesName + "\t"); wornClothes.show(); } public String getClothesName() { return clothesName; } public void setClothesName(String clothesName) { this.clothesName = clothesName; } public Person getWornClothes() { return wornClothes; } public void setWornClothes(Person wornClothes) { this.wornClothes = wornClothes; }}class Tshirt extends Clothes { public Tshirt () { this.setClothesName("T恤"); }}class Coat extends Clothes { public Coat() { this.setClothesName("大衣"); }}class Jeans extends Clothes { public Jeans() { this.setClothesName("裤子"); }}
6. 真实案例
需求:
a) 扩展BufferedReader类,使其每次读取文本信息时可以在文本前打印行号
b) 扩展BufferedReader类,使其每次读取文本信息时可以在文本后打印省略号
c) 扩展BufferedReader类,使其每次读取文本信息时可以在文本前和文本后打印引号
6.1 没有使用装饰者模式
import java.io.BufferedReader;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.Reader;public class Demo1 { public static void main(String[] args) throws Exception { BufferedReader br = new ReaderLineNumAndSusp(new InputStreamReader(new FileInputStream("D:/test.txt"), "GBK")); String readline = null; while ((readline = br.readLine()) != null) { System.out.println(readline); } br.close(); }}//需求1:增强BufferedReader类,使每次读取的内容前面加上行号class ReaderLineNum extends BufferedReader { private int lineNum = 1; public ReaderLineNum(Reader in) { super(in); } @Override public String readLine() throws IOException { String readline = super.readLine(); if (readline == null) { return null; } readline = lineNum + readline; lineNum++; return readline; }}//需求2:增强BufferedReader类,使每次读取的内容后面加上省略号class ReaderSusp extends BufferedReader { public ReaderSusp(Reader in) { super(in); } @Override public String readLine() throws IOException { String readline = super.readLine(); if (readline == null) { return null; } readline = readline + "……"; return readline; }}//需求3:增强BufferedReader类,使每次读取的内容前后加上双引号class ReaderQuot extends BufferedReader { public ReaderQuot(Reader in) { super(in); } @Override public String readLine() throws IOException { String readline = super.readLine(); if (readline == null) { return null; } readline = "“" + readline + "”"; return readline; }}//需求4:增强BufferedReader类,使每次读取的内容前加行号,内容后加省略号class ReaderLineNumAndSusp extends BufferedReader { private int lineNum = 1; public ReaderLineNumAndSusp(Reader in) { super(in); } @Override public String readLine() throws IOException { String readline = super.readLine(); if (readline == null) { return null; } readline = lineNum + readline + "……"; lineNum++; return readline; }}//需求5:。。。
6.2 使用装饰者模式
import java.io.BufferedReader;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.Reader;public class Demo2 { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("D:/test.txt"), "GBK")); BufferedReader brLineNum = new ReaderLineNum(br); BufferedReader brSusp = new ReaderSusp(brLineNum); String readline = null; while ((readline = brSusp.readLine()) != null) { System.out.println(readline); } brSusp.close(); }}class ReaderLineNum extends BufferedReader { private int lineNum = 1; private BufferedReader baseReader; public ReaderLineNum(Reader in) { super(in); this.baseReader = (BufferedReader) in; } @Override public String readLine() throws IOException { String readline = baseReader.readLine(); if (readline == null) { return null; } readline = lineNum + readline; lineNum++; return readline; }}class ReaderSusp extends BufferedReader { private BufferedReader baseReader; public ReaderSusp(Reader in) { super(in); this.baseReader = (BufferedReader) in; } @Override public String readLine() throws IOException { String readline = baseReader.readLine(); if (readline == null) { return null; } return readline + "……"; }}
可以实现三个类完成7种功能。
7. 装饰者模式与建造者模式的区别
建造者模式需要有一套完整的建造过程和顺序
装饰者模式更随意,灵活(哪怕内裤外穿。。。)
8. 装饰者模式与代理模式的区别
装饰者模式可以互相装饰,而代理模式有很明确的主从关系。