设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
经过汇总的23种设计模式它是总结了面向对象设计当中最有价值的经验。对之前来讲可能是对其中部分设计模式还是相对来说熟悉的但仔细琢磨还是会有些疑问,正好在目前相对来说有更多的业余时间,可以来一次重新学习设计模式!
本篇内容关于结构型设计模式中的装饰器模式的设计与实现。
装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。——百度百科
在不改变原类文件下,去扩展功能其实大家都知道就是通过继承实现。这里相当于继承之外的另一种方式。百科定义说到扩展是通过用装饰来包裹真实的对象,这好像和前一篇适配器模式是比较相似的.
假设你最初去买碗面,那么通过选择的一个早餐店的获取一份早餐。最初早餐店只有面店只能创建面条
后来你发现不行一点都不丰富多彩,你有可能早餐是选包子,或者是选一碗粥,豆饼等等。
扩展功能我们要怎么做就就很清楚了,就是继承。通过子类来对抽象的早餐店进行扩展,就有了如下各式各样的具体早餐店:
看这不就解决了,通过子类有各式各样的早餐店,你选择面店
你的获得早餐()
就是使用的面店的造一碗面()
,你选择包子店
你的获取早餐实际上就是包子店的造包子()
。
有人又说了,大早上吃这么单调,我吃一碗面加一笼包子加各式各样不行不。也就是说还要创建各种组合的店。这肯定不合理。
这样的情况(豆浆油条、胡辣汤、酸辣粉、豆汁儿、肠粉、烤馕、灌汤包、沙茶面、抄手)这排列组合子类数量直接爆炸。
采用继承的方式是直接写死,因为排列组合的情况很多,要在最初写死产生各种子类。你想选任意一个组合都有一个
子类(店)的造早餐()
能够都提供。
既然这样做子类数量爆炸,那有没有一个方法可以动态的去组合呢。不在一开始写死所有的组合功能的子类。而是可以动态添加上,这样其他的组合都可以在使用时动态获取拼装成最终想要的功能。
那就是组合,通过组合的方式我们也可以实现将对象1
放到对象2
当中。那么对象2
可以 在触发对象1
的功能的情况下,再加入自己的内容。
我们有1、2、3这三种,那么就可能会有7种需求(1、2、3、1+2、1+3、2+3、1+2+3),如果就这样都实现子类会很多
通过组合进行扩展,1可能扩展2的部分,那么它们都是属于同一接口它们之间可以任意扩展,那么就可以有个基础产品0。1、2、3都可以对原先产品进行包裹扩展,也就是可以给基础0扩展个1得到新产品,因此1、2、3都可以随意选择且还可以继续包装,包装1再包装一个3,即得到了1+3的组合。在使用时灵活加一件或者减一件也就是装饰器如同衣服一样。
顶级早餐产品接口,之下具有两个实现一个是基础早餐,一个是对早餐进行装饰的抽象装饰器它和产品组件同属于一个抽象类型下。装饰器下有各种具体装饰器(馄饨、面条、粥)
// 产品顶级接口
public interface Breakfast {
/**
* 造早餐
*/
public void createBreakfast();
}
// 基础产品
public class MustBreakfast implements Breakfast{
@Override
public void createBreakfast() {
System.out.println("喝一杯水");
}
}
// 基础装饰器
public abstract class BaseDecorator implements Breakfast{
Breakfast breakfast;
public BaseDecorator(Breakfast breakfast){
this.breakfast = breakfast;
}
}
public class NoodlesDecorator extends BaseDecorator{
public NoodlesDecorator(Breakfast breakfast){
super(breakfast);
}
@Override
public void createBreakfast() {
breakfast.createBreakfast();
// 完成原产品功能,之后加上额外功能
System.out.println("搞碗面条");
}
}
public class CongeeDecorator extends BaseDecorator{
public CongeeDecorator(Breakfast breakfast){
super(breakfast);
}
@Override
public void createBreakfast() {
breakfast.createBreakfast();
System.out.println("搞碗粥");
}
}
public class WontonDecorator extends BaseDecorator{
public WontonDecorator(Breakfast breakfast){
super(breakfast);
}
@Override
public void createBreakfast() {
breakfast.createBreakfast();
System.out.println("搞碗馄饨");
}
}
如此,可以灵活拿到各种扩展早餐
public class Customer {
public void getBreakfast() {
Breakfast base = new MustBreakfast();
Breakfast a = new NoodlesDecorator(base);
Breakfast b = new WontonDecorator(a);
// 当前组件创建拿到的是(面条+馄饨)的早餐
b.createBreakfast();
}
}
在Java I/O库的设计当中就很好的使用装饰器这种设计模式,我们知道通过输入输出流可以去可以将源字节数据从一个地方导到另一个地方。那么实际我们需要使用的是有结构的数据比如字符、对象、数组等等。因此IO库的设计当中提供了叫做链接的机制。可以将原始流类的输出作为源再进行流处理得到需要的。原始流处理器都是直接对数做源,而链接流处理器则是在原始流处理器的基础上再做处理,以原始流处理器处理后的数据做源。
上面只列举了几个InputStream的例子,下面三个都同属于InputStream但只有FileInputStream是原始流处理器,在装饰器模式里扮演的角色则是组件下的基础组件,而装饰器和基础组件是同属于一个组件下。ObjectInputStream以及FilterInputStream都属于装饰器。
也就是和上面实现一样它们是内嵌了组件类型(InputStream)进去,可以传入各种基础组件。
FilterInputStream内嵌InputStream且read()方法就是掉原组件方法,等于啥都没干。也就是扮演了基础装饰器的角色,而它的各个子类重写达到对原组件的不同扩展
以下是BufferedInputStream
它对原本不具备缓存功能的InputStream提供了缓存读取,实际上就是通过基础组件的read(byte b[], int off, int len)
加以实现。
平时使用FileInputStream从文件读取数据,一次一个字节效率低通过给FileInputStream加上一层BufferedInputStream即得到缓存读取功能的InputStream组件。
业务代码使用一个组件功能需要扩展,确实可以在业务代码进行,但耦合性差不满足单一职责原则。因此组件功能要单独封装提供出来,各式各样的功能各种扩展也就需要组件产生各式各样的子类。且很多新功能整个过程其实包含其他已经存在的功能,使用组合代替继承将功能划分最小,将许多不同行为的大类拆成各个基本基础行为的装饰器小类。即可在使用时动态灵活的获得想要的最终组件。妙,一般能够抽出没有先后影响的装饰行为可以适用。
往期推荐