模拟场景:张三今天有个约会,但是他很苦恼,因为他在纠结今天不知道要穿什么衣服去约会。假如张三现在身上只穿着内衣裤。那他在挑选衣服进行装饰自己的时候,要怎么实现呢?
分析:不用说,肯定会有一个Person类。现在我们用最简单的方式来完成。
Person
1 package com.zqz.dp.decorator; 2 /** 3 * @author Qin person类,来保存张三的属性 4 */ 5 public class Person { 6 private String name; // person name 7 public Person(String name) { // 构造方法赋值 8 this.name = name; 9 } 10 public void show() { // 展示方法 11 System.out.print(" 装扮的" + this.name); 12 } 13 public void wearA(){ 14 System.out.print("A服饰 "); 15 } 16 public void wearB(){ 17 System.out.print("B裤衩 "); 18 } 19 }
Client
1 package com.zqz.dp.decorator; 2 /** 3 * @author Qin 4 * 场景类 5 */ 6 public class Client { 7 public static void main(String[] args) { 8 Person per=new Person("张三"); 9 System.out.println("------第一种装扮-------"); 10 per.wearA(); //张三装饰自己 11 per.wearB(); //张三装饰自己 12 per.show(); //张三展示自己 13 } 14 }
以上确实完成了功能,但是耦合度很高。现在假设张三在去约会之前要先打电话给MM,跟她更改会面的时候,要怎么做呢?
很明显,以现在的模式的话肯定要修改Person类和Client端。每次添加一个操作就要修改Person,明显违背了开闭原则,要记住,在开发中,允许对类进行拓展,但是对修改时关闭的。当然所有类要做到全无修改是不实际的。但是要让类的修改达到最少。
先考虑下,我们现在是要对类进行装饰,也就是说要在类前后进行一些操作。咦,这不是可以用静态代理来实现吗?
其实装饰模式就是代理模式的一个扩展,只是代理和装饰两者负责的范畴是不一样的。简单的说,代理模式专注的是对对象访问的控制,在AOP中添加权限处理的操作就是进行控制,只有有权限的才可以进行执行,没有权限的无法执行。而装饰模式专注的是为对象进行装饰,也就是说为对象添加逻辑。
其实现在就涉及了一个问题:添加逻辑的方法。
也就是说现在需要对某个类添加一些操作,那该如何做呢?有这么几种方法:
1、 在原来的代码中补充,违背了开闭原则,如上面的操作(不建议使用)。
2、 继承原来的类,在类执行之前添加操作。这就是我们要说的装饰模式。
3、 使用接口,和继承是一样的道理,不过接口实现会方便点。
现在我们修改下需求,现在张三正在哼着歌,准备要去约会。突然间他发现自己的装扮有点乱,还发现要去约会的地点人满为患;所以这个时候张三先去打扮下,出门后给MM打电话更改约会地点。要如何进行装饰呢?
既然装饰是代理的一种体现,那在这里也不废话了,只是这个用的是继承的操作。
Person
1 package com.zqz.dp.decorator; 2 /** 3 * @author Qin 4 * person类,来保存张三的属性 5 */ 6 public class Person { 7 @SuppressWarnings("unused") 8 private String name; // person name 9 public Person() { 10 }; // 提供无参构造器 11 public Person(String name) { // 构造方法赋值 12 this.name = name; 13 } 14 public void show() { // 展示方法 15 System.out.println("我要去约会。。。"); 16 } 17 }
Decorator
1 package com.zqz.dp.decorator; 2 /** 3 * @author Qin 4 * 装饰类,因为要对对象进行装饰,所以需要传递对象的引用,而且调用对象的具体方法 5 */ 6 public abstract class Decorator extends Person { 7 private Person per; //person对象 8 public Decorator(Person per) { //构造方法表示对哪个对象进行装饰 9 this.per = per; 10 } 11 @Override 12 public void show() { 13 if(per!=null){ 14 per.show(); 15 } 16 } 17 }
WearDecorator
1 package com.zqz.dp.decorator; 2 /** 3 * @author Qin 4 * 第一个装饰类,继承自装饰类。 5 */ 6 public class WearDecorator extends Decorator { 7 public WearDecorator(Person per) { //指明被装饰的类 8 super(per); //调用父类的操作 9 } 10 @Override 11 public void show() { //覆写父类方法 12 this.ware(); //出门前进行操作 13 super.show(); //调用父类方法 14 } 15 public void ware(){ //出门前进行打扮操作 16 System.out.println("打扮中。。。。"); 17 } 18 }
CallDecorator
1 package com.zqz.dp.decorator; 2 /** 3 * @author Qin 4 * 第一个装饰类,继承自装饰类。 5 */ 6 public class CallDecorator extends Decorator { 7 public CallDecorator(Person per) { //指明被装饰的类 8 super(per); //调用父类的操作 9 } 10 @Override 11 public void show() { //覆写父类方法 12 super.show(); //调用父类方法 13 this.call(); //出门后添加逻辑 14 } 15 public void call(){ //出门口打电话 16 System.out.println("打电话改见面地点。。。"); 17 } 18 }
Client
1 package com.zqz.dp.decorator; 2 /** 3 * @author Qin 4 * 场景类 5 */ 6 public class Client { 7 public static void main(String[] args) { 8 Person per=new Person("张三"); //实例化person对象 9 System.out.println("------第一种装扮-------"); 10 Decorator wear=new WearDecorator(per); //用装饰类1对person对象进行装饰 11 Decorator call=new CallDecorator(wear); //用装饰类2对装饰类1对象进行装饰,递归 12 call.show(); //调用装饰类中的方法 13 } 14 }
装饰模式的定义:动态的给一个对象添加一些额外的逻辑操作。
装饰模式有如下四个角色:
1、 Component抽象构件:相当于所有的person都应该有一个父类,这里因为只有张三一个对象,所有被我省略了。该类一般为抽象类或接口。
2、 ConcreteComponent:具体构件,相当于上面的person。
3、 Decorator装饰角色:装饰对象的统一进口,一般为抽象类,但方法不一定为抽象方法。
4、 ConcreteDecorator具体装饰角色:表示具体的装饰类,CallDecorator等。
装饰模式的优点:
1、 装饰类与被装饰类可以独立发展,相互不耦合。
2、 装饰对象可拓展实现类的功能。(目的)
装饰模式的缺点:
如果存在多重装饰的话,就会变得非常复杂。一般尽量减少装饰的量。
装饰模式的使用场景:
1、 需要对一个类的功能进行拓展。
2、 需要动态的给一个对象添加功能,并动态的撤销。
3、 需要对一批兄弟类进行添加功能。
装饰模式与代理模式的异同:
其实装饰模式就是静态代理的一种体现,但不能把代理和装饰混为一谈。
在装饰模式中,我们都需要显示的指明我们要进行装饰的对象,这和静态代理是很相似的,但是代理模式的功能是实现对对象访问的控制,而装饰模式主要是对实现类功能的添加。
还有的是,代理类中注重的是对对象访问的控制,在client端,一般我们只需要知道代理类的存在即可,不需要知道实现类的存在,也就是说一般使用的是普通代理或者虚拟代理,代理中最重点的是动态代理,实际开发中很少使用静态代理。
在jdk中,代理模式使用的特别多,如动态代理Proxy。而装饰模式使用的较少,向java.io包中使用的就是装饰模式。
InputStream in=new DataInputStream(new FileInputStream("text.txt")); |
举两个例子来说明分别使用代理和装饰的情况
1、西游记中,孙悟空和唐僧路过高老庄时,悟空代替了高家三小姐去见八戒,这个时候八戒并不知道来的“高家三小姐”是悟空,而这个时候如果八戒只是对说说话,牵牵手,那么“高家三小姐”还是同意的,而如果八戒想亲她,那就不能让他得逞了。
2、悟空学会了七十二变之后,如果他变成鱼,那么他就具有了游泳的能力;如果他变成了鸟,那他就具有了飞翔的能力;也就是说悟空变成了什么,就具有了什么的功能。但是不管悟空怎么变,他还是一只猴子,这是变不了的。
相必大家应该都很清楚吧,第一个是代理,因为对对象的访问进行了控制;第二个是装饰,因为为对象添加了功能,进行了扩展。