【设计模式学习笔记】之 装饰者模式
作用:
装饰者设计模式的作用是使被装饰者的功能更强大,比如一把ak47装上消声器,装上瞄准镜,枪的使用方式不变,改变的是这把枪的功能更加强大,功能更多。
举例1:
女人可以通过化妆、受教育、礼貌 变身成化妆的女人(颜值+)、高智商的女人(IQ+)、有礼貌的女人(礼貌用语用的更多)
注意:为了显示出视频中代码演进过程,这里的代码都是一步步演进的,请注意包名
这样我们先定义一个Women类,她有颜值、IQ、名字 属性、说话的say()方法:
1 package com.mi.decorator.decorator1; 2 3 public class Women { 4 5 //颜值 6 private int beautyIndex; 7 //智商 8 private int iq; 9 //名字 10 private String name; 11 12 //构造方法初始化成员变量 13 public Women(int beautyIndex,int iq,String name){ 14 this.beautyIndex = beautyIndex; 15 this.iq = iq; 16 this.name = name; 17 } 18 19 public String getName() { 20 return name; 21 } 22 23 public int getBeautyIndex() { 24 return beautyIndex; 25 } 26 27 public int getIq() { 28 return iq; 29 } 30 31 //说话方法 32 public void say() { 33 System.out.println("我的名字叫"+name); 34 } 35 }
此时,我们创建一个化妆的女人类,她拥有Women类的所有属性,但是她的颜值更高(+20),如果每个方法都写一次,那么会有大量与Women类相同的代码,此时最先想到的应是继承,然后只重写getBeautyIndex()方法,因为Women类的属性是使用有参构造方法初始化的,继承则必须写有参构造方法,初始化该类内部的Women对象,使用代码如下:
1 package com.mi.decorator.decorator1; 2 3 public class DressupWomen extends Women{ 4 5 public DressupWomen(int beautyIndex, int iq, String name) { 6 super(beautyIndex, iq, name); 7 } 8 9 @Override 10 public int getBeautyIndex() { 11 //获取原颜值并+20返回 12 return super.getBeautyIndex()+20; 13 } 14 15 }
同样使用继承创建受教育的女人类,她同样拥有Women类的所有属性,但是她的IQ更高(+20),同样需要有参构造方法
1 package com.mi.decorator.decorator1; 2 3 public class EducatedWomen extends Women { 4 5 public EducatedWomen(int beautyIndex, int iq, String name) { 6 super(beautyIndex, iq, name); 7 } 8 9 @Override 10 public int getIq() { 11 //返回原iq +20 12 return super.getIq()+20; 13 } 14 15 }
同样使用继承创建一个礼貌的女人,同样拥有Women的所有属性,但是她的说话(say)更礼貌
1 package com.mi.decorator.decorator1; 2 3 public class PoliteWomen extends Women { 4 5 public PoliteWomen(int beautyIndex, int iq, String name) { 6 super(beautyIndex, iq, name); 7 } 8 9 @Override 10 public void say() { 11 System.out.println("大家好,"); 12 super.say(); 13 System.out.println("请多多指教!"); 14 } 15 }
写个测试类Test
1 package com.mi.decorator.decorator1; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 7 Women sisterFeng = new Women(50, 80, "凤姐"); 8 System.out.println("---------------原始女人-----------------"); 9 // 打印颜值 10 System.out.println(sisterFeng.getBeautyIndex()); 11 // 打印iq 12 System.out.println(sisterFeng.getIq()); 13 sisterFeng.say(); 14 System.out.println("---------------化妆女人-----------------"); 15 Women sisterFeng1 = new DressupWomen(50, 80, "凤姐"); 16 // 打印颜值 17 System.out.println(sisterFeng1.getBeautyIndex()); 18 // 打印iq 19 System.out.println(sisterFeng1.getIq()); 20 sisterFeng1.say(); 21 System.out.println("---------------智慧女人-----------------"); 22 Women sisterFeng2 = new EducatedWomen(50, 80, "凤姐"); 23 // 打印颜值 24 System.out.println(sisterFeng2.getBeautyIndex()); 25 // 打印iq 26 System.out.println(sisterFeng2.getIq()); 27 sisterFeng2.say(); 28 System.out.println("---------------礼貌女人-----------------"); 29 Women sisterFeng3 = new PoliteWomen(50, 80, "凤姐"); 30 // 打印颜值 31 System.out.println(sisterFeng3.getBeautyIndex()); 32 // 打印iq 33 System.out.println(sisterFeng3.getIq()); 34 sisterFeng3.say(); 35 } 36 37 }
输出:
---------------原始女人----------------- 50 80 我的名字叫凤姐 ---------------化妆女人----------------- 70 80 我的名字叫凤姐 ---------------智慧女人----------------- 50 100 我的名字叫凤姐 ---------------礼貌女人----------------- 50 80 大家好, 我的名字叫凤姐 请多多指教!
此时我们看到输出已经看出不同属性加强的属性都发生了变化,看起来也没什么问题,但是现实中很多女人都是兼有多种属性的,如果再去继承Women类差不多上要重写了整个类,
这样的代码不如不继承Women类,我们可以继承已经实现了某种属性增强的Women子类,比如:
1 package com.mi.decorator.decorator1; 2 3 /** 4 * 会打扮有礼貌的女人 5 */ 6 public class DressupPoliteWomen extends PoliteWomen { 7 8 public DressupPoliteWomen(int beautyIndex, int iq, String name) { 9 super(beautyIndex, iq, name); 10 } 11 12 @Override 13 public int getBeautyIndex() { 14 return super.getBeautyIndex()+20; 15 } 16 17 }
1 package com.mi.decorator.decorator1; 2 3 /** 4 * 即受过教育又会打扮的女人 5 */ 6 public class DressupEducateWomen extends DressupWomen { 7 8 public DressupEducateWomen(int beautyIndex, int iq, String name) { 9 super(beautyIndex, iq, name); 10 } 11 12 @Override 13 public int getIq() { 14 return super.getIq()+20; 15 } 16 17 }
1 package com.mi.decorator.decorator1; 2 3 /** 4 * 受教育有礼貌的女人 5 */ 6 public class EducatedPoliteWomen extends PoliteWomen { 7 8 public EducatedPoliteWomen(int beautyIndex, int iq, String name) { 9 super(beautyIndex, iq, name); 10 } 11 12 @Override 13 public int getIq() { 14 return super.getIq()+20; 15 } 16 }
1 package com.mi.decorator.decorator1; 2 3 /** 4 * 会打扮、受过教育、有礼貌的女人 5 */ 6 public class DressupEducatedPoliteWomen extends DressupPoliteWomen { 7 8 public DressupEducatedPoliteWomen(int beautyIndex, int iq, String name) { 9 super(beautyIndex, iq, name); 10 } 11 12 @Override 13 public int getIq() { 14 return super.getIq()+20; 15 } 16 17 }
写个测试:
1 package com.mi.decorator.decorator1; 2 3 /** 4 * 多属性女人测试 5 */ 6 public class Test2 { 7 8 public static void main(String[] args) { 9 10 DressupPoliteWomen fengjie1 = new DressupPoliteWomen(50, 80, "凤姐"); 11 System.out.println(fengjie1.getBeautyIndex()); 12 System.out.println(fengjie1.getIq()); 13 fengjie1.say(); 14 System.out.println("---------------------------------------"); 15 DressupEducateWomen fengjie2 = new DressupEducateWomen(50, 80, "凤姐"); 16 System.out.println(fengjie2.getBeautyIndex()); 17 System.out.println(fengjie2.getIq()); 18 fengjie2.say(); 19 System.out.println("---------------------------------------"); 20 EducatedPoliteWomen fengjie3 = new EducatedPoliteWomen(50, 80, "凤姐"); 21 System.out.println(fengjie3.getBeautyIndex()); 22 System.out.println(fengjie3.getIq()); 23 fengjie3.say(); 24 System.out.println("---------------------------------------"); 25 DressupEducatedPoliteWomen fengjie4 = new DressupEducatedPoliteWomen(50, 80, "凤姐"); 26 System.out.println(fengjie4.getBeautyIndex()); 27 System.out.println(fengjie4.getIq()); 28 fengjie4.say(); 29 } 30 31 }
输出:
70 80 大家好, 我的名字叫凤姐 请多多指教! --------------------------------------- 70 100 我的名字叫凤姐 --------------------------------------- 50 100 大家好, 我的名字叫凤姐 请多多指教! --------------------------------------- 70 100 大家好, 我的名字叫凤姐 请多多指教!
看到这里,有个问题出现了,如果我们添加一个属性,那我们先需要一个继承Women类的子类,然后我们还需要继续扩充多种属性并存的类,这样看来,单纯使用继承会使子类的数量成指数增长,这时候怎么办呢?我们可不可以让每个导出类都可以互相包装,这样代码量和子类数量不就减少了么?答案是肯定的!
为了不混淆视听,新建一个包decorator2,将decorator1包中的单属性增强女人类和Women类复制到这个2包中
新建一个抽象类WrapperWomen,继承自Women类,她的内部持有一个Women对象的引用,重写所有Women的方法
1 package com.mi.decorator.decorator2; 2 3 public abstract class WrapperWomen extends Women { 4 5 private Women women; 6 7 public WrapperWomen(Women women) { 8 super(women.getBeautyIndex(), women.getIq(), women.getName()); 9 this.women = women; 10 } 11 12 @Override 13 public String getName() { 14 return women.getName(); 15 } 16 17 @Override 18 public int getBeautyIndex() { 19 return women.getBeautyIndex(); 20 } 21 22 @Override 23 public int getIq() { 24 return women.getIq(); 25 } 26 27 @Override 28 public void say() { 29 System.out.println("我的名字叫" + women.getName()); 30 } 31 32 }
让所有单属性增强的Women子类继承WrapperWomen,重写构造方法为接收一个Women对象的引用,在构造方法内部调用父类WrapperWomen的构造方法
1 package com.mi.decorator.decorator2; 2 3 public class DressupWomen extends WrapperWomen { 4 5 public DressupWomen(Women women) { 6 super(women); 7 } 8 9 @Override 10 public int getBeautyIndex() { 11 return super.getBeautyIndex()+20; 12 } 13 }
1 package com.mi.decorator.decorator2; 2 3 4 public class EducatedWomen extends WrapperWomen { 5 6 public EducatedWomen(Women women) { 7 super(women); 8 } 9 10 @Override 11 public int getIq() { 12 return super.getIq() + 20; 13 } 14 15 }
1 package com.mi.decorator.decorator1; 2 3 4 public class PoliteWomen extends WrapperWomen{ 5 6 public PoliteWomen(Women women) { 7 super(women); 8 } 9 10 @Override 11 public void say() { 12 System.out.println("大家好,"); 13 super.say(); 14 System.out.println("请多多指教!"); 15 } 16 }
测试类:
1 package com.mi.decorator.decorator2; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 7 System.out.println("-------------------打扮-------------------"); 8 //因为这些类都是间接的继承自Women类,都可以看作Women类型 9 Women sisterFeng = new Women(50,80,"凤姐"); 10 Women dressup = new DressupWomen(sisterFeng); 11 System.out.println(dressup.getBeautyIndex()); 12 System.out.println(dressup.getIq()); 13 dressup.say(); 14 System.out.println("--------------打扮&受教育----------------"); 15 Women dressupEducated = new EducatedWomen(dressup); 16 System.out.println(dressupEducated.getBeautyIndex()); 17 System.out.println(dressupEducated.getIq()); 18 dressupEducated.say(); 19 System.out.println("--------------打扮&有礼貌----------------"); 20 Women dressupPolite = new PoliteWomen(dressup); 21 System.out.println(dressupPolite.getBeautyIndex()); 22 System.out.println(dressupPolite.getIq()); 23 dressupPolite.say(); 24 System.out.println("--------------受教育&有礼貌--------------"); 25 Women EducatedPolite = new PoliteWomen(new EducatedWomen(sisterFeng)); 26 System.out.println(EducatedPolite.getBeautyIndex()); 27 System.out.println(EducatedPolite.getIq()); 28 EducatedPolite.say(); 29 System.out.println("----------------完美女人------------------"); 30 Women perfectWomen = new PoliteWomen(dressupEducated); 31 System.out.println(perfectWomen.getBeautyIndex()); 32 System.out.println(perfectWomen.getIq()); 33 perfectWomen.say(); 34 System.out.println("-------------------------------------------"); 35 } 36 37 }
输出:
------------------打扮------------------ 70 80 我的名字叫凤姐 --------------打扮&受教育---------------- 70 100 我的名字叫凤姐 --------------打扮&有礼貌---------------- 70 80 大家好, 我的名字叫凤姐 请多多指教! --------------受教育&有礼貌-------------- 50 100 大家好, 我的名字叫凤姐 请多多指教! ---------------完美女人----------------- 70 100 大家好, 我的名字叫凤姐 请多多指教! --------------------------------------—
以上的方式在JDK中的I/O中最为常见,所以说JDK的I/O是装饰者设计模式的典型实现,也可以如下写:
1 Women baby = new PoliteWomen(new DressupWomen(new EducatedWomen(new Women(90,100,"baby")))); 2 System.out.println(baby.getBeautyIndex()); 3 System.out.println(baby.getIq()); 4 baby.say();
输出:
110 120 大家好, 我的名字叫baby 请多多指教!
以上代码中的Women类其实可以用接口实现,这样会更灵活,代码还能少一些
举例2:
一把ak47可以装上消声器,可以装上瞄准镜,枪的使用方式不变,改变的是这把枪的功能更加强大,功能更多。
为了区分代码,这里新建一个包decorator3
新建一个Gun接口,具有枪应有的方法
1 package com.mi.decorator.decorator3; 2 3 /** 4 * 枪接口 5 */ 6 public interface Gun { 7 8 //瞄准 9 void aim(); 10 //射击 11 void shoot(); 12 //装弹 13 void load(); 14 //卸弹 15 void unload(); 16 }
新建上例同理的包装类实现Gun接口的包装抽象类,持有父类的引用,在构造方法中初始化父类引用,实现Gun接口的所有方法,使用父类引用调用接口方法(多态机制会运行中指向实现类方法)
题外话:实现多态的条件是什么?有重写有继承,有父类类型指向子类对象?其实还有一种,接口类型指向实现类对象
1 package com.mi.decorator.decorator3; 2 3 public abstract class WrapperGun implements Gun { 4 5 private Gun gun; 6 public WrapperGun(Gun gun){ 7 this.gun = gun; 8 } 9 @Override 10 public void aim() { 11 gun.aim(); 12 } 13 14 @Override 15 public void shoot() { 16 gun.shoot(); 17 } 18 19 @Override 20 public void load() { 21 gun.load(); 22 } 23 24 @Override 25 public void unload() { 26 gun.unload(); 27 } 28 29 }
新建实现Gun接口的Ak47(那个包装类作为枪上的配件使用,看完代码就能明白了)
1 package com.mi.decorator.decorator3; 2 3 public class Ak47 implements Gun { 4 5 @Override 6 public void aim() { 7 System.out.println("------ak47瞄准ing-------"); 8 } 9 10 @Override 11 public void shoot() { 12 System.out.println("------ak47开火ing-------"); 13 } 14 15 @Override 16 public void load() { 17 System.out.println("------ak47装弹ing-------"); 18 } 19 20 @Override 21 public void unload() { 22 System.out.println("------ak47卸弹ing-------"); 23 } 24 25 }
瞄准镜类,继承自WrapperGun抽象类
1 package com.mi.decorator.decorator3; 2 3 public class AimGlassGun extends WrapperGun{ 4 5 public AimGlassGun(Gun gun) { 6 super(gun); 7 } 8 9 @Override 10 public void aim() { 11 System.out.println("瞄准中,因为装配了瞄准镜,射击精度提高80%"); 12 super.aim(); 13 } 14 15 }
消声器类,继承自WrapperGun抽象类
1 package com.mi.decorator.decorator3; 2 3 public class SilentGun extends WrapperGun{ 4 5 public SilentGun (Gun gun){ 6 super(gun); 7 } 8 9 @Override 10 public void shoot() { 11 System.out.println("因为佩戴了消音器,枪声减小80%"); 12 super.shoot(); 13 } 14 15 }
写个测试类:
1 package com.mi.decorator.decorator3; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 //被装饰对象在包装的最内部 7 Gun ak47 = new AimGlassGun(new SilentGun(new Ak47())); 8 ak47.aim(); 9 ak47.shoot(); 10 } 11 12 }
输出:
瞄准中,因为装配了瞄准镜,射击精度提高80% ------ak47瞄准ing------- 因为佩戴了消音器,枪声减小80% ------ak47开火ing-------
以上这个例子可以说是很清晰的看出装饰者模式的优点了,装饰类是不能直接new出来的,它内部必须拥有一个Gun的实现类引用;
如果我需要一把带瞄准带消音器的M4A1,我只需要实现Gun接口,然后把M4A1对象的引用交给瞄准镜和消音器的层层包装,就可以得到。相比最开始的例子那样使用继承每种属性都要继承一次导出一个新的类,这简直是简单极了!
总结:
装饰者设计模式特点:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。比如我想给一把枪添加属性,我无需修改枪这个类,只需要对他进行包装,就能获得需要的Gun,相比于继承产生子类而言,更加灵活,代码更少,实现类也更少。
何时使用:在不想增加很多子类的情况下扩展类。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。