装饰模式
本文参考:《修炼Java开发技术:在架构中体验设计模式和算法之美 于广编著》。
装饰模式是指给一个类添加一些额外的职责,并且在添加这些额外的职责时不会控制该类的执行逻辑。
装饰模式能够在不必改变原类文件盒使用继承的情况下,动态扩展一个对象的功能。装饰模式是通过创建一个包装对象来实现,也就是用装饰来包裹真实的对象。
装饰模式的特点:
1、装饰对象和真实对象有相同的接口,这样客户端对象就能够以与真实对象相同的方式同装饰对象交互。
2、.装饰对象包含一个真实对象的索引(reference)。
3、装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象。
4、装饰对象可以在转发这些请求以前或者以后增加一些附加的功能。
装饰模式与类继承的主要区别如下:
1、装饰模式是一种动态行为,对已经存在的类进行随意组合,而类的继承是一种静态行为,一个类定义成什么样子,其对象便具有什么功能,无法动态的改变。
2、装饰模式扩展的是对象的功能,不需要增加类的数量。而类继承扩展是类的功能,在继承的关系中,如果我们想增加一个对象的功能,我们只能通过继承关系,在子类中增加方法。
3、装饰模式是在不改变原类文件和使用继承的情况下,动态的扩展一个对象的功能,它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
4、装饰模式把对客户端的调用委派给被装饰的类,装饰模式的关键在于这种扩展是完全透明的。
举例:
制作蛋糕,蛋糕种类有是奶酪蛋糕、巧克力蛋糕、插花的巧克力蛋糕、贺卡的冰激凌蛋糕等等,在这里我们只需要关注蛋糕,而贺卡、插花只不过是起到了装饰的作用。现在我们想要什么就能生成什么。
package decorator.demo; /** * 蛋糕基类 * */ public abstract class Cake { String remark = "蛋糕"; public String getRemark() { return remark; } public abstract String getImpression();//用来描述吃蛋糕的感觉。。。。 }
实现装饰器抽象类:
package decorator.demo; /** * 其他用来添加蛋糕的东西 * @author Nicholas * */ public abstract class OtherDecorator extends Cake{ Cake cake; /** * 引用一个Cake. * 让被装饰者进入装饰者之中。这里用的是构造方法注入。 * 这样就可以调用Cake实例的方法了。 * @param cake */ public OtherDecorator(Cake cake){ this.cake=cake; } /** * 让装饰器的子类都去实现getRemark方法。业务需要每个装饰器都要有描述。 */ public abstract String getRemark(); }
实现一个乳酪蛋糕:
package decorator.demo; /** * 乳酪蛋糕 */ public class CheeseCake extends Cake{ /** * 乳酪蛋糕的构造方法 */ public CheeseCake(){ super.remark="乳酪蛋糕";//修改乳酪蛋糕的描述。 } /** * 实现了Cake抽象类的getImpression * 吃乳酪蛋糕的感觉。。 */ public String getImpression() { return "香甜感受"; } }
巧克力蛋糕
package decorator.demo; /** * 巧克力蛋糕 */ public class ChocolateCake extends Cake{ public ChocolateCake(){ super.remark="巧克力蛋糕"; } public String getImpression() { return "丝一般的润滑"; } }
冰淇淋蛋糕
package decorator.demo; /** * 冰淇淋蛋糕 * */ public class IceCake extends Cake{ public IceCake(){ super.remark="冰淇淋蛋糕"; } public String getImpression() { return "冰爽无比"; } }
贺卡装饰:
package decorator.demo; public class CardDecorator extends OtherDecorator { public CardDecorator(Cake cake) { super(cake); super.remark = "贺卡"; } public String getImpression() { return cake.getImpression()+","+"吃起来口感不错啊!!"; } public String getRemark() { return cake.getRemark()+"+"+super.remark; } }
鲜花装饰
package decorator.demo; /** * 给蛋糕添加的花 */ public class FlowerDecorator extends OtherDecorator{ /** * 构造函数 * 传入一个cake实例,也就是前面所实现的Cake的子类,如奶酪蛋糕,巧克力蛋糕等等。 * @param cake */ public FlowerDecorator(Cake cake){ super(cake);//调用父类的构造方法,可以获取Cake的实例了。就可以调用Cake实例的方法. super.remark="一朵玫瑰花"; } /** * 实现了装饰器抽象类的getImpression方法。 */ public String getImpression() { //这是重点。我们通过构造方法传入的cake实例。对cake进行了装饰,增加了新的功能。 return cake.getImpression()+","+"看到一朵花真是happy"; } public String getRemark() { return cake.getRemark()+"+"+super.remark; } }
坚果装饰
package decorator.demo; /** * 给蛋糕添加的坚果 * */ public class NutsDecorator extends OtherDecorator{ public NutsDecorator(Cake cake){ super(cake); super.remark = "果仁"; } public String getImpression() { return cake.getImpression()+","+"吃起来口感不错啊!!"; } public String getRemark() { return cake.getRemark()+"+"+super.remark; } }
美女1喜欢:乳酪蛋糕+一朵玫瑰花+果仁
package decorator.demo; public class MyGirl { public static void main(String[] args){ Cake cheeseCake = new NutsDecorator(new FlowerDecorator(new CheeseCake())); System.out.println("remark "+cheeseCake.getRemark()); System.out.println("impression "+cheeseCake.getImpression()); } }
结果:
remark 乳酪蛋糕+一朵玫瑰花+果仁
impression 香甜感受,看到一朵花真是happy,吃起来口感不错啊!!
美女2喜欢用果仁,花包装巧克力蛋糕。
package decorator.demo; public class MyGirlB { public static void main(String[] args){ //用果仁,花包装巧克力蛋糕。 Cake nutsFlowerChocolateCake = new NutsDecorator(new FlowerDecorator(new ChocolateCake())); System.out.println("remark "+nutsFlowerChocolateCake.getRemark()); //吃蛋糕的感受已经发生了改变。 System.out.println("impression "+nutsFlowerChocolateCake.getImpression()); } }
结果:
remark 巧克力蛋糕+一朵玫瑰花+果仁
impression 丝一般的润滑,看到一朵花真是happy,吃起来口感不错啊!!
在Java中流接口和装饰模式的关系
InputStream就是输入流的抽象接口,而FilterInputStream就相当于装饰器。
举例说明:
对英文进行移动2位加密,a->c y->a z->b
方法一
package decorator.demo; import java.io.IOException; import java.io.OutputStream; public class EncryptOutputStream extends OutputStream { private OutputStream os = null; public EncryptOutputStream(OutputStream os) { this.os = os; } @Override public void write(int a) throws IOException { a += 2; if (a >= (97 + 26)) { a -= 26; } this.os.write(a); } }
方法二
package decorator.demo; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; public class EncryptOutputStream2 extends FilterOutputStream { public EncryptOutputStream2(OutputStream os) { super(os); } @Override public void write(int a) throws IOException { a += 2; if (a >= (97 + 26)) { a -= 26; } super.write(a); } }
测试:
package decorator.demo; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; import org.junit.Test; public class TestDecorator { @Test public void test() throws IOException { DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(new EncryptOutputStream( new FileOutputStream("mytest.txt")))); dout.write("abcdxyz".getBytes()); dout.close(); } @Test public void test2() throws IOException{ DataOutputStream dout = new DataOutputStream(new EncryptOutputStream2 (new BufferedOutputStream( new FileOutputStream("mytest.txt")))); dout.write("abcdxyz".getBytes()); dout.close(); } }
test1和test2的区别在于BufferedOutputStream和EncryptOutputStream2可以在位置上互换,而EncryptOutputStream则不行,因为EncryptOutputStream2继承的是装饰器的父类,使用装饰器提供的功能协助完成想要装饰的功能。而EncryptOutputStream仅仅只是outputStream的子类。
方法二更合理。