文章目录
一、装饰者模式简介
装饰( Decorator )模式又叫做包装模式。通过一种对客户端透明的方式来扩展对象的功能,是继承关系的一个替换方案,在功能扩展方面比继承更具有弹性。
二、装饰者模式的结构
三、装饰者模式的角色与职责
- 抽象组件角色: 一个抽象接口,是被装饰类和装饰类的父接口。
- 具体组件角色:为抽象组件的实现类。
- 抽象装饰角色:包含一个组件的引用,并定义了与抽象组件一致的接口。
- 具体装饰角色:为抽象装饰角色的实现类。负责具体的装饰。
- | 组件 | 具体组件 | 装饰组件 | 具体装饰组件 |
---|---|---|---|---|
类 | Component | ConrreteComponent extends Component | Decorator extends Component | ConrreteDecorator extends Component |
方法 | methodA() | methodA() | methodA() | methodA() |
方法 | methodB() | methodB() | methodB() | methodB() |
四、装饰者模式的具体实现
考虑如下的案例
咖啡馆订单项目:
- 咖啡种类:Espresso、ShortBlack、LongBlack、Decaf
- 调料:Milk、Soy、Chocolate
- 扩展性好、改动方便、维护方便
1、不使用装饰者模式方案一
方案设计
类 | Drink | Decaf extends Drink | Shortblack extends Drink | Longblack extends Drink | Shortblack&Soy extends Drink | Decaf&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 Drink | Shortblack extends Drink | Longblack 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、使用装饰者模式
方案设计
- | 抽象类 | 具体组件 | 具体组件 | 具体组件 | 装饰组件 | 具体装饰组件 | 具体装饰组件 |
---|---|---|---|---|---|---|---|
- | Drink | Decaf extends Drink | Shortblack extends Drink | Espresso extends Drink | Decorator extends Drink | Chocolate extends Decorator | Milk 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.
通常情况下,我们会扩展两个分支,一个组件分支,另一个装饰者分支进行功能扩展。