设计模式之装饰模式(Decorator Pattern)
-
装饰模式定义
- 官方定义:Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionally. 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
- 自己的理解:1️⃣在不改变原类文件以及不使用继承的情况下,动态地将责任附加到对象上,从而实现动态拓展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。2️⃣通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,这也是装饰器模式设计的初衷。3️⃣符合开闭原则:类应该对拓展开放,对修改关闭。
-
装饰模式通用类图
装饰模式主要有四个角色:- Component是基类。通常是一个抽象类或者一个接口,定义了属性或者方法,方法的实现可以由子类实现或者自己实现。通常不会直接使用该类,而是通过继承该类来实现特定的功能,它约束了整个继承树的行为。比如说,如果Component代表人,即使通过装饰也不会使人变成别的动物。
- ConcreteComponent是Component的子类,实现了相应的方法,它充当了“被装饰者”的角色。
- Decorator也是Component的子类,它是装饰者共同实现的抽象类(也可以是接口)。比如说,Decorator代表衣服这一类装饰者,那么它的子类应该是T恤、裙子这样的具体的装饰者。
- ConcreteDecorator是Decorator的子类,是具体的装饰者,由于它同时也是Component的子类,因此它能方便地拓展Component的状态(比如添加新的方法)。每个装饰者都应该有一个实例变量用以保存某个Component的引用,这也是利用了组合的特性。在持有Component的引用后,由于其自身也是Component的子类,那么,相当于ConcreteDecorator包裹了Component,不但有Component的特性,同时自身也可以有别的特性,也就是所谓的装饰。
-
装饰模式通用代码
抽象构件
public abstract class Component {
public abstract void operate();
}
具体构件
public class ConcreateComponent extends Component{
@Override
public void operate() {
System.out.println("do something...");
}
}
抽象装饰者
public abstract class Decorator extends Component {
private Component component = null;
public Decorator(Component _component) {
this.component = _component;
}
@Override
public void operate() {
this.component.operate();
}
}
具体装饰类
public class ConcreteDecorator1 extends Decorator{
public ConcreteDecorator1(Component _component) {
super(_component);
}
private void method1() {
System.out.println("method1 装饰");
}
public void operate() {
this.method1();
super.operate();
}
}
public class ConcreteDecorator2 extends Decorator{
public ConcreteDecorator2(Component _component) {
super(_component);
}
private void method2() {
System.out.println("method2 装饰");
}
public void operate() {
this.method2();
super.operate();
}
}
场景类
public class Client {
public static void main(String[] agrs) {
Component component = new ConcreateComponent();
// first
component = new ConcreteDecorator1(component);
// second
component = new ConcreteDecorator2(component);
// run
component.operate();
}
}
- 装饰模式实例类图
装饰模式在Java中经常出现的地方就是JavaIO。提到JavaIO,脑海中就冒出了大量的类:InputStream、FileInputStream、BufferedInputStream……等。
- 装饰模式实例代码
完成一个将读取到的英文字符全切换成大写的转换流。
public class ToUpperCaseInputStream extends FilterInputStream {
InputStream inputStream;
protected ToUpperCaseInputStream(InputStream in) {
super(in);
this.inputStream = in;
}
/**
* 读取单个字节
*
* @return
* @throws IOException
*/
@Override
public int read() throws IOException {
// 获取父类读取的结果
int result = super.read();
// 如果读取到字符a,就抛出异常
if (result == 'a') {
throw new ToUpperException();
}
// 如果等于-1,说明无内容
// 否则,将字节转成char,再将char转换成大写的后返回
// 返回值类型是int类型,这里返回一个字符会被自动转型
return (result == -1 ? result : Character.toUpperCase(Character.toChars(result)[0]));
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
for (int i = off; i < off + result; i++) {
// 将字节转成大写字符后再转成字节
b[i] = (byte) Character.toUpperCase((char) b[i]);
}
return result;
}
/***
* 这里是一个内部类,自定义异常
*/
class ToUpperException extends IOException {
@Override
public void printStackTrace() {
System.out.println("不好意思我遇到异常了,向上转型失败啦");
}
}
}
public class Client {
public static void main(String[] args) {
int result = 0;
InputStream inputStream = null;
try {
inputStream = new ToUpperCaseInputStream(new BufferedInputStream(new FileInputStream("file path")));
while ((result = inputStream.read()) >= 0) {
System.out.print((char) result);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 装饰模式总结
- 装饰者模式可以非常的贴合六大原则之一的开闭原则。
- 继承虽然也可以实现扩展,但是继承的扩展需要已经扩展需求,在编译期扩展,而装饰者模式可以实现在运行期扩展。
- 使用装饰者模式,类的数量会增长很多,不能过度使用。
参考书籍:设计模式之禅
实例代码放在这里。