装饰模式
Decorator Pattern:属于GoF23种设计模式中结构型设计模式的一种,又被称为装饰者模式。
简单的说,装饰模式/装饰者模式就是给一个现有的类,在不改变这个类的情况下,动态的给这个类进行扩展,添加新的东西,比如说添加新的功能。
动态的意思也就是说不需要这个类创建的时候指定,完全可以等这个类创建完成了之后,已经完全成为了一个“活蹦乱跳”的对象了之后,再给他添加新的东西,不需要写死,而是想要什么时候加就什么时候加,想要加什么就加什么。
结构
-
给出一张简易的装饰者模式的结构图片吧,分析一下下
-
有一个抽象对象,这个是干什么的呢?这个是被装饰者对象,简单的说就是我们需要在这个对象上进行装饰,这样就好理解了吧。
至于为什么要对被装饰者进行抽象,因为要面向接口编程的嘛,也可以留出更大的扩展空间不是。
-
紧接着的就是具体的对象了,也就是真实的被装饰者。
-
装饰者模式主要的还是装饰,那么显然对于装饰的抽象是不可避免的,也不会就有一个功能需要装饰的吧,如果真的有那也要进行抽象以下,毕竟,也需,过不了多久你的PM和你说需求要改了呢?
这个里面需要注意的就是维护了一个被装饰者对象,因为在实际的代码中我们应该对这个被装饰者进行包装,但是前提是我们不能修改这个被装饰者,也就是需要动态两个字的体现,具体的应用可以看后面的代码部分可能会好理解一点。
-
下面的就是所有的具体如何去装饰的实现类了。
-
举例说明
-
对于装饰着模式来说,最容易体会到的,那就是学习JAVA的IO流的时候了,那里面的一层层的包装,真的是眼花缭乱。
比如想要生成一个字符缓冲流,应该如何???
BufferedReader stringBuffer = new BufferedReader(new InputStreamReader(new FileInputStream("demo.txt")));
哈哈,这就是层层装饰啊,从一个文本输入流,变成字符串输入流,变成带缓冲的字符缓冲流。
-
OK,我们也可以面向生活一点儿,有没有小伙伴知道“加码子”是啥意思啊?早餐的时候,首先呢点了一碗素面,发现不行,太过于单调,有点了一个豆干,还是不行,紧接着一个鸡蛋,还是不行,有加了一份牛肉…越来越多,越来越多,原先的一碗素面已经是“五彩缤纷”了啊。
-
再来个小栗子,也是阔以的啊,比如去喝个奶茶,有原味奶茶的吧,但是没意思啊,味道不好,但是呢,我们可以加东西进去啊,珍珠或者椰果?加冰或者不加冰?都是可以的嘛
-
用笔者自己喜欢的方式的话,那就是游戏里面人物的时装了,想当初,毒奶粉的时候,笔者为了黄金天空套,又不想充钱,那是天天搬砖,天天搬砖换代币卷,一件一件凑齐的啊,看着自己的角色一件件穿上时装,嘿嘿装饰模式之下变得金闪闪啊(不是那个金闪闪啊,这里可没有咖喱棒)。
注意
-
其实呢这个装饰模式的结构是可以简化的,上面给出的是一个完整的结构。可以简化的部分很显然嘛,就是两个抽象类可以去掉撒。
也就是说,只有一个被装饰者对象的时候,被装饰者的抽象类咱就不要了,抽象装饰直接继承这个对象就成了啊。
还有一种呢就是只有一个具体的装饰实现类,那就直接不要抽象装饰类就行了呗,子类是可以顶替父类的嘛。
-
装饰着的优点的话,应该就是可以创建五花八门的各种各样的实际对象吧。这样子显得扩展更加的灵活,而且自由性很高很高的。
-
但是捏,东西虽好,也是不可以乱用的,可以看到这个位置有两个大分支线也就是两个抽象类的部分了,如果东西一多,那产生的小小的子类,估计可以看的你头疼…
一个小DEMO
-
场景
一个简单的场景设计吧,记得那是一天下午,压马路的笔者看到了路边的奶茶店,正好口渴准备去点一杯,可是面前的价目表看的真的是眼花缭乱啊,只好准备自己搭配一个,嘿嘿,来一杯原味奶茶,加上椰果,加上冰,珍珠嘛,还是不需要了吧。
用代码模拟一下这个场景的实现吧!
抽象类还是接口,那就看大家的习惯吧。
-
首先定义被装饰者对象——奶茶的话,估摸着算是饮料???
package com.decorator; /** * 装饰者模式——抽象对象——也就是被装饰者对象的抽象对象 * DEMO中的话就是饮料了 * @author WQ */ public abstract class Drinks { public abstract void show(); }
-
来个奶茶类,这就是具体的被装饰者对象了!
package com.decorator; /** * 装饰者模式——具体的被装饰者对象 * DEMO中的话就是奶茶了 * @author WQ */ public class MilkyTea extends Drinks{ @Override public void show() { System.out.println("我是一杯奶茶啊!"); } }
-
定义个抽象的装饰类,这里的话抽象起来,算作配料???
package com.decorator; /** * 装饰者模式——抽象装饰对象 * DEMO中的话就是配料吧 * @author WQ */ public abstract class Batching extends Drinks{ /** * 维护一个被装饰着对象 * (这里是面向接口编程) * 注意权限修饰符是protected 这个类的子类是可以直接使用的 */ protected Drinks drink; /** * 说明一下下 * 抽象类是可以有构造方法的 * 只是不能直接创建抽象类的实例对象而已 * @param drink */ public Batching(Drinks drink) { this.drink = drink; } @Override public void show() { drink.show(); } }
-
真实的配料也需要上场了啊,这部分就是具体的装饰类了。
package com.decorator; /** * 装饰者模式——具体装饰类对象 * 这是珍珠哦 * @author WQ */ public class Pearl extends Batching{ /** * 自然的需要维护一个被装饰者对象的嘛 * @param drink */ public Pearl(Drinks drink) { super(drink); } /** * 这里还需要覆盖掉父类的方法 * 父类里面只是对被装饰着对象调用了一下, * 我们需要装饰啊 */ @Override public void show() { /** * 这里首先是调用了父类的方法的 * 我们是对原有的对象进行包装, * 那么应该保证的是原有对象的功能应该是完善的, * 如果这里不调用的话,那么我们编写出来的结果就没有被装饰的类了。 */ super.show(); decorate(drink); } /** * 这个位置就是具体的被装饰的逻辑了, * 虽然这里只有一个小小的输出, * 但是实现更为复杂的逻辑的时候还是需要抽出来成为一个方法的 */ private void decorate(Drinks drink) { System.out.print("加珍珠~"); } } //-------------------------------------------------------------------- package com.decorator; /** * 装饰者模式——具体装饰类对象 * 这是椰果哦 * @author WQ */ public class Coconut extends Batching{ public Coconut(Drinks drink) { super(drink); } @Override public void show() { super.show(); decorate(drink); } private void decorate(Drinks drink) { System.out.print("加椰果~"); } } //---------------------------------------------------------------- package com.decorator; /** * 装饰者模式——具体装饰类对象 * 这是冰哦 * @author WQ */ public class Ice extends Batching{ public Ice(Drinks drink) { super(drink); } @Override public void show() { super.show(); decorate(drink); } private void decorate(Drinks drink) { System.out.print("加冰~"); } }
-
编个测试类
package com.decorator; /** * 装饰者模式——测试类部分 * @author WQ */ public class Main { public static void main(String[] args) { //被装饰者对象 Drinks drink = new MilkyTea(); //这一次来个全加 Batching batching1 = new Pearl(drink); //注意传进去的参数不是drink了,而是被装饰了以后的对象了啊 //对象已经被打包了,原来的对象穿了一套“衣服”了 Batching batching2 = new Coconut(batching1); //这也是为啥装饰类也要继承被装饰类的原因哦 Batching batching3 = new Ice(batching2); batching3.show(); System.out.println(); //换个我们熟悉的方式的 Batching batching = new Ice(new Pearl(new MilkyTea())); batching.show(); } }
-
测试结果
我是一杯奶茶啊! 加珍珠~加椰果~加冰~ 我是一杯奶茶啊! 加珍珠~加冰~
完成!