前言

装饰模式也是一种结构型模式,主要是目的是相对于类与类之间的继承关系来说,使用装饰模式可以降低耦合度。JDK中有不少地方都使用到了装饰模式,例如Java的各种I/O流,javax.swing包中一些图形界面构件功能的增强等地方都运用了装饰模式。

装饰模式

定义

装饰模式的定义是:在不改变原类文件以及不使用继承的情况下,动态的扩展一个对象的功能。装饰模式是通过创建一个包装对象来实现的,也就是用装饰来包裹真实的对象。

举例

还是老规矩,举例说明,在给亲朋好友过生日时会买生日蛋糕,然后生日蛋糕又有各种各样的辅料来进行装饰,例如:奶油,水果,芝士,巧克力等等。如果没有这些辅料来进行装饰,就是普通的鸡蛋糕。所以我们可以把做蛋糕的这个过程来用代码实现出来:

首先定义蛋糕接口

/**
* 蛋糕
*/
public interface CakeGoods { /**
* 展示蛋糕
*/
void showCake(); /**
* 展示价格
*/
BigDecimal showPrice();
}

具体的鸡蛋糕

/**
* 鸡蛋糕
*/
@Data
public class SpongeCake implements CakeGoods{ //蛋糕名称
private String name;
//蛋糕价格
private BigDecimal price; public SpongeCake(String name,BigDecimal price){ this.name = name;
this.price = price;
} /**
* 展示蛋糕
*/
@Override
public void showCake() { System.out.println("的"+this.name+"蛋糕"); } /**
* 展示价格
*/
@Override
public BigDecimal showPrice() { return this.price;
}
}

辅料的抽象类

/**
* 辅料的抽象类
*/
public abstract class Decorator implements CakeGoods{ private CakeGoods cakeGoods; public void setCakeGoods(CakeGoods cakeGoods){
this.cakeGoods = cakeGoods;
} /**
* 展示蛋糕
*/
@Override
public void showCake() {
cakeGoods.showCake();
} /**
* 展示价格
*/
@Override
public BigDecimal showPrice() {
return cakeGoods.showPrice();
}
}

水果

/**
* 水果
*/
public class Fruits extends Decorator {
/**
* 展示蛋糕
*/
@Override
public void showCake() {
System.out.print("加水果");
super.showCake();
} /**
* 展示价格
*/
@Override
public BigDecimal showPrice() {
return new BigDecimal(20.00).add(super.showPrice());
}
}

奶油

/**
* 奶油
*/
public class Cream extends Decorator {
/**
* 展示蛋糕
*/
@Override
public void showCake() {
System.out.print("加奶油");
super.showCake();
} /**
* 展示价格
*/
@Override
public BigDecimal showPrice() {
return new BigDecimal(15.00).add(super.showPrice());
}
}

测试

public class Tests {

    public static void main(String[] args) {

        //制造鸡蛋糕
CakeGoods cakeGoods = new SpongeCake("生日祝福",new BigDecimal(50)); Decorator cream = new Cream();
Decorator fruits = new Fruits(); //加奶油
cream.setCakeGoods(fruits);
//加水果
fruits.setCakeGoods(cakeGoods); //展示
cream.showCake();
System.out.println("的价格是:"+cream.showPrice()+"元");
} }

运行结果:

加奶油加水果的生日祝福蛋糕
的价格是:85元

上面的这个蛋糕制造的例子使用的就是装饰模式,在为鸡蛋糕SpongeCake进行扩展的时候并没有影响它原来的类的结构,也没有使用继承的关系,最终却达到了装饰的目的。下面我们来分析一下装饰模式具体是由那几部分组成。

装饰模式的结构

装饰模式的结构图如上所示,主要由以下几个角色组成。

抽象构件角色:(上图的CakeGoods)定义一个抽象接口,以规范准备接受装饰的对象,想当于Java中的IO流里的InputStram/OutputStream和Reader/Writer。

具体的构件角色:(上图的SpongeCake)定义一个将要接受装饰的类,相当于I/O流里面的FileOutputStream和FileInputStream。

装饰角色:(上图的Decorator)定义一个持有抽象构件角色的引用,并定义一个与抽象构件一直的接口。相当于I/O里面的FilterOutputStream和FilterInputStream。

具体的装饰角色:(上图的Fruits和Cream)负责各构件角色对象加上相应的装饰品,相当于I/O流里的BufferedOutputStream、BufferedInputStream。

在使用的时候,必须扩展CakeGoods的功能,但是我们是通过Fruits和Cream来对CakeGoods进行扩展的,所以相对于CakeGoods来说无需知道Decorator的存在,就能扩展功能了。

总结

装饰模式的优点

  1. 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。例如上面的例子如果想增加一个枣糕或是一个巧克力辅料,无需修改现有代码,只需将枣糕作为CakeGoods的一个子类,以及Decorator的一个子类即可。
  2. 可以动态的扩展一个对象的功能。
  3. 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。

装饰模式的缺点

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。
  2. 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

适用场景

在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类已定义为不能被继承(如Java语言中的final类)。

加油,给自己打气,克服惰性!!!

想了解更多的设计模式请查看Java设计模式学习记录-GoF设计模式概述

Java设计模式学习记录-装饰模式的更多相关文章

  1. Java设计模式学习记录-迭代器模式

    前言 这次要介绍的是迭代器模式,也是一种行为模式.我现在觉得写博客有点应付了,前阵子一天一篇,感觉这样其实有点没理解透彻就写下来了,而且写完后自己也没有多看几遍,上次在面试的时候被问到java中的I/ ...

  2. Java设计模式学习记录-模板方法模式

    前言 模板方法模式,定义一个操作中算法的骨架,而将一些步骤延迟到子类中.使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤. 模板方法模式 概念介绍 模板方法模式,其实是很好理解的,具体 ...

  3. Java设计模式学习记录-状态模式

    前言 状态模式是一种行为模式,用于解决系统中复杂的对象状态转换以及各个状态下的封装等问题.状态模式是将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象的状态可以灵活多变.这样在客户端使 ...

  4. Java设计模式学习记录-观察者模式

    前言 观察者模式也是对象行为模式的一种,又叫做发表-订阅(Publish/Subscribe)模式.模型-视图(Model/View)模式. 咱们目前用的最多的就是各种MQ(Message Queue ...

  5. Java设计模式学习记录-备忘录模式

    前言 这次要介绍的是备忘录模式,也是行为模式的一种 .现在人们的智能手机上都会有备忘录这样一个功能,大家也都会用,就是为了记住某件事情,防止以后自己忘记了.那么备忘录模式又是什么样子的呢?是不是和手机 ...

  6. Java设计模式学习记录-解释器模式

    前言 这次介绍另一个行为模式,解释器模式,都说解释器模式用的少,其实只是我们在日常的开发中用的少,但是一些开源框架中还是能见到它的影子,例如:spring的spEL表达式在解析时就用到了解释器模式,以 ...

  7. Java设计模式学习记录-命令模式

    前言 这次要介绍的是命令模式,这也是一种行为型模式.最近反正没有面试机会我就写博客呗,该投的简历都投了.然后就继续看书,其实看书也会给自己带来成就感,原来以前不明白的东西,书上已经给彻底的介绍清楚了, ...

  8. Java设计模式学习记录-享元模式

    前言 享元模式也是一种结构型模式,这篇是介绍结构型模式的最后一篇了(因为代理模式很早之前就已经写过了).享元模式采用一个共享来避免大量拥有相同内容对象的开销.这种开销最常见.最直观的就是内存损耗. 享 ...

  9. Java设计模式学习记录-外观模式

    前言 这次要介绍的是外观模式(也称为门面模式),外观模式也属于结构型模式,其实外观模式还是非常好理解的,简单的来讲就是将多个复杂的业务封装成一个方法,在调用此方法时可以不必关系具体执行了哪些业务,而只 ...

随机推荐

  1. [SoapUI] 在SoapUI中,设置开关批量保存整个Response,作为期望结果进行校验

    //获取保存response的文件路径和名称 def testSuiteName = context.testCase.testSuite.name def testCaseName = contex ...

  2. c++ stl源码剖析学习笔记(一)uninitialized_copy()函数

    template <class InputIterator, class ForwardIterator>inline ForwardIterator uninitialized_copy ...

  3. Python3实战系列之六(获取印度售后数据项目)

    问题:续接上一篇.说干咱就干呀,勤勤恳恳写程序呀! 目标:此篇我们试着把python程序打包成.exe程序.这样就可以在服务器上运行了.实现首篇计划列表功能模块的第三步: 3..exe文件能在服务器上 ...

  4. deug的使用经验

    最基本的操作是: 1, 首先在一个java文件中设断点,然后运行,当程序走到断点处就会转到debug视图下, 2, F5键与F6键均为单步调试,F5是step into,也就是进入本行代码中执行,F6 ...

  5. tensorflow学习之(四)使用placeholder 传入值

    #placeholder 传入值 import tensorflow as tf """ tf.Variable:主要在于一些可训练变量(trainable variab ...

  6. Java程序员职业生涯规划完整版:从程序员到CTO( 摘)

    在技巧方面无论我们怎么学习,总感觉需要晋升自已不知道自己处于什么水平了.但如果有清晰的指示图供参考还是非常不错的,这样我们清楚的知道我们大概处于那个阶段和水平. Java程序员 高等特性 反射.泛型. ...

  7. android 测试

    Android Monkey压力测试 monkey测试结果详细分析 Android自动化测试工具 UiAutomator使用详解 在Android Sudio中使用Uiautomator 六款Andr ...

  8. android 混淆基本知识

    官网介绍:https://www.guardsquare.com/en/proguard/manual/introduction android 开发文档:https://developer.andr ...

  9. 从navicat中导入sql文件过大:Got a packet bigger than 'max_allowed_packet' bytes

    失败背景:刚才通过navicat向本地mysql数据库中导入sql文件.第一个sql文件(多个表)大小为967M,导入成功: 第二个sql(单个表)大小为50.1M,导入失败. 1.在navicat中 ...

  10. Delphi - TDateTimePicker使用注意问题

    TDateTimePicker使用时候,如果想获取到修改后的值,必须注Kind和time属性必须对应! 1,时间设置: treatmentTime1DTPicker.Kind := dtkTime; ...