JAVA设计模式之装饰者模式

一、装饰者模式简介

装饰( Decorator )模式又叫做包装模式。通过一种对客户端透明的方式来扩展对象的功能,是继承关系的一个替换方案,在功能扩展方面比继承更具有弹性。

二、装饰者模式的结构

在这里插入图片描述

三、装饰者模式的角色与职责

  1. 抽象组件角色: 一个抽象接口,是被装饰类和装饰类的父接口。
  2. 具体组件角色:为抽象组件的实现类。
  3. 抽象装饰角色:包含一个组件的引用,并定义了与抽象组件一致的接口。
  4. 具体装饰角色:为抽象装饰角色的实现类。负责具体的装饰。
-组件具体组件装饰组件具体装饰组件
ComponentConrreteComponent extends ComponentDecorator extends ComponentConrreteDecorator extends Component
方法methodA()methodA()methodA()methodA()
方法methodB()methodB()methodB()methodB()

四、装饰者模式的具体实现

考虑如下的案例
咖啡馆订单项目:

  1. 咖啡种类:Espresso、ShortBlack、LongBlack、Decaf
  2. 调料:Milk、Soy、Chocolate
  3. 扩展性好、改动方便、维护方便

1、不使用装饰者模式方案一

方案设计
DrinkDecaf extends DrinkShortblack extends DrinkLongblack extends DrinkShortblack&Soy extends DrinkDecaf&Milk extends Drink
字段desc
方法getDesc()getDesc()getDesc()getDesc()getDesc()getDesc()
方法cost()cost()cost()cost()cost()cost()
类设计

不使用装饰者时,我们首先定义一个抽象类,接着从抽象类中继承各种咖啡类,并重写Drink类的getDesc()方法和cost()方法。
Drink类:

// An highlighted block
package design.decorator.gys.nodeco1;
public abstract class Drink {
private String desc;
public Drink(String desc) {
 super();
 this.desc = desc;
}
public String getDesc() {
 return desc;
 }
abstract double cost();
}

继承Drink类的三个子类
Decaf类:

// An highlighted block
package design.decorator.gys.nodeco1;
public class Decaf extends Drink{
 public Decaf() {
  super("Decaf");
 }
 @Override
 public double cost() {
  return 5.0;
 }
}

Shortblack类:

// An highlighted block
package design.decorator.gys.nodeco1;
public class Shortblack extends Drink{
 public Shortblack() {
  super("Shortblack");
 }
 @Override
 public double cost() {
  return 6.0;
 }
}

Espresso类:

// An highlighted block
package design.decorator.gys.nodeco1;
public class Espresso extends Drink{
 public Espresso() {
  super("Espresso");
 }
 @Override
 public double cost() {
  return 7.0;
 }
}

测试程序如下:

// An highlighted block
package design.decorator.gys.nodeco1;
public class Test {
 public static void main(String[] args) {
 Drink d1=new Decaf();
  Drink d2=new Shortblack();
  Drink d3=new Espresso();
  System.out.println(d1.getDesc()+":  $"+d1.cost());
  System.out.println(d2.getDesc()+":  $"+d2.cost());
  System.out.println(d3.getDesc()+":  $"+d3.cost());
 }
}

测试输出如下:

// An highlighted block
Decaf:  $5.0
Shortblack:  $6.0
Espresso:  $7.0

此种方法在有大量子类时,会出现类爆炸现象,因此不适合本案例。

2、不适用装饰者模式方案二

方案设计
Drink(抽象类)Decaf extends DrinkShortblack extends DrinkLongblack extends Drink
字段desc、milk、soy、chocolate
方法getDesc()
方法cost()cost()cost()cost()
方法hasMilk()、setMilk()
方法hasSoy()、setSoy()
方法hasChocolate()、setCholocate()

在此方案中,我们设置三个布尔变量milk、soy、chocolate来判断是否包含加入调料,并通过hasMilk()、setMilk()、hasSoy()、setSoy()、hasChocolate()、setCholocate()方法来判断和设置加入调料。

类设计

Drink类:

// An highlighted block
package design.decorator.gys.nodeco2;
public abstract class Drink {
 private String desc;
 private boolean milk; 
 private boolean soy; 
 private boolean chocolate; 
 public Drink(String desc) {
  super();
  this.desc = desc;
 }
 public String getDesc() {
  StringBuilder sb=new StringBuilder(desc);
  sb=hasMilk()?sb.append("+Milk"):sb;
  sb=hasSoy()?sb.append("+Soy"):sb;
  sb=hasChocolate()?sb.append("+Chocolate"):sb;
  return sb.toString(); 
   }
 public boolean hasMilk() {
  return milk;
 }
 public void setMilk() {
  milk=true;
 }
 public boolean hasSoy() {
  return soy;
 }
 public void setSoy() {
  soy=true;
 }
 public boolean hasChocolate() {
  return chocolate;
 }
 public void setChocolate() {
  chocolate=true;
 }
 abstract double cost();
}

定义子类:

// An highlighted block
package design.decorator.gys.nodeco2;
public class Decaf extends Drink{
 public Decaf() {
  super("Decaf");
  // TODO Auto-generated constructor stub
 }
 @Override
 double cost() {
  // TODO Auto-generated method stub
  double money=5;
  money=hasMilk()?money+2:money;
  money=hasSoy()?money+3:money;
  money=hasChocolate()?money+3.5:money;
  return money;
 }
}
// An highlighted block
package design.decorator.gys.nodeco2;
public class Espresso extends Drink{
 public Espresso() {
  super("Espresso");
  // TODO Auto-generated constructor stub
 }
 @Override
 double cost() {
  // TODO Auto-generated method stub
  double money=7;
  money=hasMilk()?money+2:money;
  money=hasSoy()?money+3:money;
  money=hasChocolate()?money+3.5:money;
  return money;
 }
}

测试程序如下:

// An highlighted block
package design.decorator.gys.nodeco2;
public class Test {
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  Drink d1=new Decaf();
  Drink d2=new Espresso();
  d1.setMilk();
  d1.setSoy();
  d2.setChocolate();
  System.out.println(d1.getDesc()+": $"+d1.cost());
  System.out.println(d2.getDesc()+": $"+d2.cost());
  System.out.println("------------------");
  d1.setMilk();
  System.out.println(d1.getDesc()+": $"+d1.cost());
  }
 }

测试结果如下:

// An highlighted block
Decaf+Milk+Soy: $10.0
Espresso+Chocolate: $10.5
------------------
Decaf+Milk+Soy: $10.0

有一个顾客很喜欢牛奶,又加了一份,但此时显示出的价格和原来一样,那么这个顾客岂不是吃的霸王餐。
该方案中,若是增删调料种类或者添加多份调料时,对代码的修改仍需要大量的工作。接着我们使用装饰者模式来设计。

3、使用装饰者模式

方案设计
-抽象类具体组件具体组件具体组件装饰组件具体装饰组件具体装饰组件
-DrinkDecaf extends DrinkShortblack extends DrinkEspresso extends DrinkDecorator extends DrinkChocolate extends DecoratorMilk extends Decorator
字段desc、price---Drink obj--
方法getDesc()---getDesc()--
方法cost()---cost()--

首先设计一个抽象Drink类,在类中实现Drink所需的大部分方法,除了cost()方法,因为在装饰者中,我们还需要加上调料的价格,在getDesc()方法中,不仅返回所点单品与调料名称,还返回总价和单品调料价格:

类设计

Drink类:

// An highlighted block
package design.decorator.gys.component;
public abstract class Drink {
 private String desc;
 private double price;
 public Drink() { 
 }
 public double getPrice() {
  return price;
 }
 public void setPrice(double price) {
  this.price = price;
 }
 public abstract double cost();
 public String getDesc() {
  return desc + ": $"+this.getPrice();
 }
 public void setDesc(String desc) {
  this.desc = desc;
 }
}

接下来定义两个分支,咖啡分支即组件和调料分支即装饰者。
咖啡组件分支:

// An highlighted block
package design.decorator.gys.concretecomponent;
import design.decorator.gys.component.Drink;
public class Coffee extends Drink{
 @Override
 public double cost() {
  // TODO Auto-generated method stub
  return this.getPrice();
 }
}

调料装饰者分支:

// An highlighted block
package design.decorator.gys.decoretor;
import design.decorator.gys.component.Drink;
public class Decorator extends Drink{
 private Drink obj;
 public Decorator(Drink obj) {
  this.obj = obj;
 }
 @Override
 public double cost() {
  // TODO Auto-generated method stub
  return super.getPrice()+obj.cost();
 }
 public String getDesc() {
  return super.getDesc()+"\n"+obj.getDesc();
 }
}

接下来定义三个具体的组件,在这里,我们只需要提供咖啡单品的名称和价格即可:

// An highlighted block
package design.decorator.gys.concretecomponent;
public class Decaf extends Coffee{
 public Decaf() {
  this.setDesc("Decaf");
  this.setPrice(8);
  // TODO Auto-generated constructor stub
 }
}
// An highlighted block
package design.decorator.gys.concretecomponent;
public class Espresso extends Coffee{
 public Espresso() {
  this.setDesc("Espresso");
  this.setPrice(7.5);
  // TODO Auto-generated constructor stub
 }
}
// An highlighted block
package design.decorator.gys.concretecomponent;
public class Shortblack extends Coffee{
 public Shortblack() {
  this.setDesc("Shortblack");
  this.setPrice(5.0);
  // TODO Auto-generated constructor stub
 }
}

下面定义具体装饰者,由于在Decorator类中已经定义了全部的公共方法,所以在此处,我们同样只需要提供调料的名称和价格:

// An highlighted block
package design.decorator.gys.decoretor;
import design.decorator.gys.component.Drink;
public class Milk extends Decorator{
 public Milk(Drink obj) {
  super(obj);
  this.setPrice(3.0);
  this.setDesc("Milk");
  // TODO Auto-generated constructor stub
 }
}
// An highlighted block
package design.decorator.gys.decoretor;
import design.decorator.gys.component.Drink;
public class Soy extends Decorator{
 public Soy(Drink obj) {
  super(obj);
  this.setPrice(3.5);
  this.setDesc("Soy");
  // TODO Auto-generated constructor stub
 }
}
// An highlighted block
package design.decorator.gys.decoretor;
import design.decorator.gys.component.Drink;
public class Chocolate extends Decorator{
 public Chocolate(Drink obj) {
  super(obj);
  this.setPrice(4.0);
  this.setDesc("Chocolate");
  // TODO Auto-generated constructor stub
 }
}

这是咖啡馆来了三个客人,一个人点了单品Shortblack,而另一个人喜欢喝加了牛奶的咖啡,剩下的人特别喜欢巧克力,所以又加了两大份巧克力。

// An highlighted block
package design.decorator.gys.component;
import design.decorator.gys.concretecomponent.Shortblack;
import design.decorator.gys.decoretor.Chocolate;
import design.decorator.gys.decoretor.Milk;
public class Test {
 public static void print(Drink d) {
  System.out.println("总价:$"+d.cost()+"\n"+"详单:"+"\n"+d.getDesc());
 }
 public static void main(String[] args) {
  // TODO Auto-generated method stub
   Drink shortblack=new Shortblack();
   Drink drink=new Milk(shortblack);
   print(shortblack);
   System.out.println("----------");
   print(drink);
   System.out.println("----------");
   drink=new Chocolate(drink);
   drink=new Chocolate(drink);
   print(drink);
 }
}

看一下点单系统的计算价格:

// An highlighted block
总价:$5.0
详单:
Shortblack: $5.0
----------
总价:$8.0
详单:
Milk: $3.0
Shortblack: $5.0
----------
总价:$16.0
详单:
Chocolate: $4.0
Chocolate: $4.0
Milk: $3.0
Shortblack: $5.0

现在,我们可是使用各种调料来包装单品咖啡,并且可以实现添加多份调料。再添加单品咖啡只需要继承Drink类,添加调料则继承Decorator类即可,不需要改变已有类的代码。
在此提出开放——关闭原则:

  • 开放:提供新功能的添加
  • 关闭:禁止修改已经存在的功能

五、JAVA内置装饰者

JAVA的IO结构

名称
组件InputStream
具体组件FileInputStream、StringBufferInputSrteam、ByteArrayInputStream
装饰组件FilterInputStream
具体装饰组件BufferInputStream、DataInputStream、LineNumberInputStream
类设计

自定义一个输入流UpperInputStream类,该类位装饰类,用来装饰InputStream,该类将读到的字符转为大写字符,下面是代码示例:

// An highlighted block
package design.decoretor.gys.inner;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class UpperInputStream extends FilterInputStream{
 protected UpperInputStream(InputStream in) {
  super(in);
  // TODO Auto-generated constructor stub
 }
 public int read() throws IOException {
  int c=super.read();
  return c==-1?c:Character.toUpperCase(c);
 } 
 public int read(byte[] b,int off,int len) throws IOException{
  int result=super.read(b, off, len);
  for(int i=0;i<result;i++)
   b[i]=(byte)Character.toUpperCase((char)(b[i]));
  return result;
 }
}

使用txt文本作文输入,:

测试程序如下:

// An highlighted block
package design.decoretor.gys.inner;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Testinner {
 public static void main(String[] args)  {
  // TODO Auto-generated method stub
  int ch;
  try {
  InputStream in=new UpperInputStream(new BufferedInputStream(new FileInputStream("F:\\5.txt")));
  
   while((ch=in.read())!=-1)
    System.out.print((char)ch);
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
}

测试输出如下:

// An highlighted block
THIS IS A TEST FILE.

通常情况下,我们会扩展两个分支,一个组件分支,另一个装饰者分支进行功能扩展。

要抱抱才会开心呀~~~~~~~~~~~~

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值