装饰者模式是动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

假设我们有一个需求,是给一家饮料店做一个计算各种饮料价格的功能。听起来很简单,我们创建一个抽象父类Beverages,description用来描述饮料名字,price方法用来计算饮料的价格。

public abstract class Beverages {

private String description;

public String getDescription() {

return description;

}

public abstract double price();

}

每种饮料我们都创建一个子类去继承父类,赋值给description,并且重写price方法来设置自己的价格。看上去很完美,我们运行一下看看结果。

public class Coffee extends Beverages {

public Coffee(String description){

this.description = description;

}

@Override

public String getDescription() {

return description;

}

@Override

public double price() {

return 3.5;

}

}

public static void main(String[] args){

Coffee coffee = new Coffee("咖啡");

System.out.println(coffee.getDescription());

System.out.println(coffee.price());

}

结果:

咖啡

3.5

但是问题来了,饮料店里不仅仅只有一种饮料,还有可乐、七喜、奶茶其他各种各样的饮料,难道我们要每个都创建一个子类吗?好吧,就算你觉得几十种饮料还不算多,那如果各种饮料直接再进行搭配呢?咖啡加牛奶、咖啡加巧克力、加糖、不加糖,因为各种配料的不同价格和描述也不同,我的天呀,难道要创建几百个类吗?这个时候我们突然想到了不是可以用继承来解决这个问题吗?那我们就来试一下。

我们改造一下Beverages类,把要加的配料用boolean值声明,如果需要添加配料就调用set方法设置为true,在price方法中计算的时候来判断哪一些配料添加了需要计算价格。

public class Beverages {

public String description;

public boolean milk;

public boolean sugar;

public double milkPrice = 1.5;

public double sugarPrice = 0.5;

public boolean isMilk() {

return milk;

}

public void setMilk(boolean milk) {

this.milk = milk;

}

public boolean isSugar() {

return sugar;

}

public void setSugar(boolean sugar) {

this.sugar = sugar;

}

public String getDescription() {

return description;

}

public double price(){

double basePrice = 0.0;

if (isMilk()){

basePrice += milkPrice;

}

if (isSugar()){

basePrice += sugarPrice;

}

return basePrice;

}

}

然后我们在子类计算价格的时候加上父类中计算好的配料的价格。

public class CoffeeWithMilk extends Beverages {

public CoffeeWithMilk(String description){

this.description = description;

}

@Override

public double price() {

return 3.0 + super.price();

}

}

我们运行看一下,咖啡加牛奶加糖,结果没有问题。

CoffeeWithMilk coffeeWithMilk = new CoffeeWithMilk("咖啡加牛奶加糖");

coffeeWithMilk.setMilk(true);

coffeeWithMilk.setSugar(true);

System.out.println(coffeeWithMilk.getDescription());

System.out.println(coffeeWithMilk.price());

结果:

咖啡加牛奶加糖

5.0

这样一来我们解决了新建无数个子类的问题,但是我们发现单纯继承的做法还是有太多弊端,比如说如果我们想要新添加配料,我们得修改在父类Beverages中添加新的调料字段,还要修改price方法,这严重违反了开发-关闭的设计原则,类应该对扩展开发,对修改关闭。而且有些饮料和配料是没办法搭配的,例如啤酒加糖,但是子类还是会继承到这些配料,并且如果是要同一份配料要加双份又该怎么改呢?所以单纯继承的方法还是不行。这时候我们就要使用到装饰者模式。

首先我们创建抽象父类Beverages,这个父类只是饮料的父类,不是配料的父类。我们创建一个Cola类直接继承它。

public abstract class Beverages {

public String description;

public String getDescription() {

return description;

}

public abstract double price();

}

public class Cola extends Beverages {

public Cola(String description) {

this.description = description;

}

@Override

public double price() {

return 2.0;

}

}

现在就缺配料的部分的代码了,我们再创建一个配料的抽象类Seasonings,我们让它继承Berverages类,并且声明了一个Berverages的引用和抽象的getDescription方法,这里我们稍后再作解释。

public abstract class Seasonings extends Beverages{

public Beverages beverages;

public abstract String getDescription();

}

接下来我们看看配料的实现类怎么写。我们让他继承了父类Seasonings,构造函数接收父类Beverages类型,其实也就是要被装饰的对象,也就是各种各样需要加配料的饮料。然后我们重写了getDescription,我们在传进来的那个Beverages对象的基础上添加名字,price方法也是同理。这样其实就在给传进来的对象外面做了一层装饰,也就是给饮料添加了配料。

public class Ice extends Seasonings {

public Ice(Beverages beverages) {

this.beverages = beverages;

}

@Override

public String getDescription() {

return beverages.getDescription() + "加冰";

}

@Override

public double price() {

return beverages.price() + 0.2;

}

}

运行一下,看看结果。没有问题,可以搭配成功。

Cola cola = new Cola("可乐");

Beverages ice = new Ice(cola);

System.out.println(ice.getDescription());

System.out.println(ice.price());

结果:

可乐加冰

2.2

我们还想再添加别的配料,再创建一个新的配料。

public class Sugar extends Seasonings {

public Sugar(Beverages beverages) {

this.beverages = beverages;

}

@Override

public String getDescription() {

return beverages.getDescription() + "加糖";

}

@Override

public double price() {

return 0.5 + beverages.price();

}

}

我们在原来搭配好的基础上再进行装饰,结果也是没有问题的。这个时候就要提到为什么我们的Seasonings类要继承Beverages类了,因为每个配料的实现类里有一个Beverages类型的引用,这样我们才可以对它的子类进行装饰,我们让Seasonings也继承Beverages那它的子类也是Beverages类型,我们就可以想装饰几层就装饰几层。

Beverages ice = new Ice(cola);

System.out.println(ice.getDescription());

System.out.println(ice.price());

Beverages sugar = new Sugar(ice);

System.out.println(sugar.getDescription());

System.out.println(sugar.price());

结果:

可乐加冰

2.2

可乐加冰加糖

2.7

在Java的类库中就有很多实际应用到了装饰模式,比如BufferedInputStream就可以用来装饰FileInputStream,提供更加强大的功能。

总结:

装饰模式就是装饰者和被装饰者都具有相同的超类,装饰者拿到被装饰者的引用之后,在调用被装饰者的方法的同时再加上自己的新功能,从而实现了功能的增加,也不需要修改原来的代码。而且因为是相同的超类,所以可以装饰很多层。

设计模式java装饰模式范例_Java设计模式之装饰模式详解相关推荐

  1. java jtable组件_java中jtable组件详解实例

    java中jtable组件详解实例 java 表格控件 JTable 常用操作详解 JTable 是 Swing 编程中很常用的控件,这里总结了一些常用方法以备查阅.欢迎补充,转载请注明作者与出处.一 ...

  2. java异常例子_java 异常的实例详解

    java 异常的实例详解 1.异常的定义:程序在运行时出现不正常情况. 异常的划分: Error:严重的问题,对于error一般不编写针对性的代码对其进行处理. Exception:非严重的问题,对于 ...

  3. java list用法_java list的用法详解

    java list的用法详解 java中可变数组的原理就是不断的创建新的数组,将原数组加到新的数组中.以下是百分网小编搜索整理的关于java list的用法详解,需要的朋友可以参考一下!想了解更多相关 ...

  4. java 自定义正则表达式_java中正则表达式实例详解

    Java中正则表达式运用实例(参看java中正则表达式运用详解): 测试代码 package test; /** * 在String的matches()方法,split()方法中使用正则表达式. * ...

  5. java aqs原理_Java并发之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  6. java多线程代码_java多线程实例代码详解

    原文:http://blog.csdn.net/paranoidyang/article/details/70184523 作者:Paranoidyang 线程与进程的区别 (1)程序是一段静态的代码 ...

  7. java file 实例_Java File类的详解及简单实例

    Java File类的详解及简单实例 1. File():构造函数,一般是依据文件所在的指定位置来创建文件对象. CanWrite():返回文件是否可写. CanRead():返回文件是否可读. Co ...

  8. java 枚举 方法_Java枚举使用方法详解

    在实际编程中,往往存在着这样的"数据集",它们的数值在程序中是稳定的,而且"数据集"中的元素是有限的. 例如星期一到星期日七个数据元素组成了一周的"数 ...

  9. java 返回值_Java方法返回值详解

    Java方法返回值详解 每个方法都是为了完成某个特定的功能,例如:登录功能.求和功能等,既然是功能,那么当这个功能完成之后,大多数情况下都会有一个结果的,比如,登录成功了或者失败了(true/fals ...

最新文章

  1. qzone.class.php,PHP实现QQ空间自动回复说说的方法
  2. gen_cam_par_area_scan_division和gen_cam_par_area_scan_polynomial区别
  3. 高端人工智能服务器,产品技术-HPE Apollo6500 Gen10服务器:人工智能的高速引擎-新华三集团-H3C...
  4. java职业发展路线图_Java程序员如何选择未来的职业路线
  5. Linux下构建FTP服务器
  6. 计算机网络通信的仿真,计算机网络虚拟仿真技术研究与应用.doc
  7. R开发(part6)--pryr包
  8. Arcgis License的安装及破解
  9. 编写一个脚本判断某个用户是否处在活动模式_大数据技术之Spark内核解析(二)Spark 部署模式...
  10. c/c++编译的程序占用的内存分配
  11. 图片怎么批量修改尺寸
  12. 8个常用的python办公室自动化技巧
  13. VSCode格式化JS自动添加或去掉分号
  14. linux打开nginx配置文件,【linux】systemctl启动nginx没有加载nginx.conf配置文件?
  15. JavaScript《一》
  16. android 手机红外遥控器
  17. word2vec训练中文模型—wiki百科中文库
  18. [Spring手撸专栏学习笔记]——把AOP动态代理,融入到Bean的生命周期
  19. SpringBoot实现定时任务的三种方式
  20. 战神引挚手游数据库解析mysql/mir

热门文章

  1. Apache Jserv protocol
  2. rop的noejs客户端
  3. 新年图书整理和相关的产品
  4. 数据库连接oracle 10g rman 备份与恢复 之一
  5. 用Windows live Writer 2012发布51cto博客
  6. java 调整数组顺序使奇数位于偶数前面
  7. oracle case when exists()
  8. Failed to create the Java Virtual Machine
  9. 不要为框架作过多的假设
  10. git commit Please tell me who you are it config --global user.email you@example.com