JAVA设计模式之装饰者模式
装饰者模式
1.什么是装饰者模式?
装饰者模式指在不改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。也就是创建一些装饰对象来装饰对象,使对象具有装饰对象的行为。
《Head First设计模式》书中指出,动态地将行为附加到对象上,动态的扩展对象功能,装饰者模式提供了一种有别于继承体系的另一种实现。
继承属于功能扩展形式之一,但不是弹性设计的最佳方案。
装饰者类反映出被装饰的组件类型,他们具有相同的类型,都是经过同一个接口或者继承实现。
装饰者模式能建立弹性的设计,维持开放-关闭原则。
2.角色
基类(Component):通常是一个接口或者是一个超类,定义了属性和方法,子类实现方法,一般不会直接使用此类,主要作用是约束和控制子类的特性。所有的“被装饰者”和“装饰者”都要继承或实现此类。实例代码中是Beverage(饮料)类。
被装饰者(ConcreteComponent):Component的直接子类。实例代码中分别为Espresso(浓缩咖啡)、HouseBlend(混合咖啡)、OrangeJuice(果汁)。
装饰者抽象类(Decorator):Component的直接子类,是具体装饰者共同的抽象类(或接口),所有的装饰者都要实现或继承此类。实例代码中为CondimentDecorator(调料抽象类)。
装饰者(ConcreteDecorator):Decorator的子类,具体的装饰者。装饰者同时也是Component的子类,因此和被装饰者保持了“一致性”(同时继承自Component)可以方便的为被装饰者添加功能。装饰者中都应该提供一个实例变量来保存Component的引用,由于装饰者是Component的子类,因此相当于装饰者包裹了Component类,不但有Component的特性也拥有自己的特性,这就是“装饰”由来,让被装饰者动态的拥有新特性。
3.优点
在不改变原类文件的情况下动态的为对象添加新功能,符合弹性设计和开闭原则。
4.缺点
装饰者模式会产生比继承体系更多的类,会对加大对功能的理解难度。
5.使用场景
一般使用于要求能够在不改变原代码的情况下,动态的增加对象的功能的情景。
6.实例代码
此实例是模拟一个咖啡店的点餐系统
(1)、饮料超类
public abstract class Beverage {
/*
* 饮料描述
*/
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
/*
* 计算价格
*/
public abstract float cost();
/*
* 提供一个精准计算价格的静态方法
*/
public static float addMoney(float arg1, float arg2) {
return (new BigDecimal(String.valueOf(arg1)).add(new BigDecimal(String.valueOf(arg2)))).floatValue();
}
}
(2)、浓缩咖啡饮料
public class Espresso extends Beverage {
//饮料名称
public Espresso() {
description="Espresso";
}
@Override
public float cost() {
return 1.99f;
}
}
(3)、混合咖啡饮料
public class HouseBlend extends Beverage {
public HouseBlend() {
description="House Blend coffee";
}
@Override
public float cost() {
return 0.89f;
}
}
(4)、橙汁饮料
public class OrangeJuice extends Beverage {
public OrangeJuice() {
description="OrangeJuice";
}
@Override
public float cost() {
return 1.05f;
}
}
(5)、调料装饰者超类
public abstract class CondimentDecorator extends Beverage{
//获取描述
public abstract String getDescription();
}
(6)、牛奶调料
public class Milk extends CondimentDecorator {
Beverage beverage;
public Milk(Beverage beverage) {
this.beverage=beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+",Milk";
}
@Override
public float cost() {
return addMoney(0.1f,beverage.cost());
}
}
(7)、摩卡调料
public class Mocha extends CondimentDecorator {
/*
* 用一个实例变量记录饮料,也就是被装饰者
*/
Beverage beverage;
/*
* 使用构造器将饮料对象记录到实例变量中
*/
public Mocha(Beverage beverage) {
this.beverage=beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+",Mocha";
}
@Override
public float cost() {
return addMoney(0.2f,beverage.cost());
}
}
(8)、豆奶调料
public class SoybeanMilk extends CondimentDecorator {
Beverage beverage;
public SoybeanMilk(Beverage beverage) {
this.beverage=beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+",SoybeanMilk";
}
@Override
public float cost() {
return addMoney(0.15f,beverage.cost());
}
}
(9)、测试代码-模拟点餐
public static void main(String[] args) {
/*
* 点一杯橙汁
*/
Beverage b1=new OrangeJuice();
System.out.println(b1.getDescription()+" 价格$"+b1.cost());
/*
* 点一杯浓缩咖啡,加摩卡,加牛奶
*/
Beverage b2=new Espresso();
b2=new Mocha(b2);//用摩卡装饰浓缩咖啡
b2=new Milk(b2);//用牛奶装饰浓缩咖啡
System.out.println(b2.getDescription()+" 价格$"+b2.cost());
/*
* 点一份混合咖啡,加双份豆奶
*/
Beverage b3=new HouseBlend();
b3=new SoybeanMilk(b3);
b3=new SoybeanMilk(b3);
System.out.println(b3.getDescription()+" 价格$"+b3.cost());
}
(10)、运行结果
OrangeJuice 价格$1.05
Espresso,Mocha,Milk 价格$2.29
House Blend coffee,SoybeanMilk,SoybeanMilk 价格$1.19
7.JDK内部实现:java.io
装饰者模式在JDK中最直接的使用就是java.IO。java.io中有许多的类,在了解装饰者模式之前对这些类多少有点蒙圈,不知道有些类的作用和使用方法。面对如此众多类文件的情况下,我们只需要哪些是基类(被装饰者),哪些是装饰者类就可以了。
就字节流来说,两个基类分别是 字节输入流InputStream和字节输出流OutputStream,而字符流分别是 字符输入流 Reader和字符输出流Writer。其他类似于FileInputStream、StringBufferInputStream这些类都是装饰者,也就是角色图中的ConcreteDecorator。
除了以上的具体装饰者和被装饰者以外,还有四个类需要格外的注意,因为这些类为我们自己扩展java.io提供了支持,这四个类就是装饰者抽象类(Decorator),分别是InputStream的抽象装饰者:FilterInputStream、OutputStream的抽象装饰者FilterOutputStream、Reader的抽象装饰者FilterReader、Writer的抽象装饰者FilterWriter。
自己扩展的装饰者只要继承(extends)相应的抽象装饰者,就可以分别为字节流和字符流扩展额外的功能。
InputStream类图如下图(图片来源于网络):
例如,我们为字节输入流扩展一个可以将输入单词转换为小写的功能。
(1)、实例代码:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class LowerCaseInputStream extends FilterInputStream {
/*
* FilterInputStream中已经提供InputStream的实例变量 此处直接调用父类的方法
*/
protected LowerCaseInputStream(InputStream in) {
super(in);
}
/**
* 覆盖父类中的read()方法
*/
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toLowerCase((char) c));
}
}
(2)、输入文件test.txt中的内容
THIS IS MY JAVA.IO Decorator test;
(3)、测试代码
public static void main(String[] args) throws IOException {
int c;
InputStream is = new LowerCaseInputStream(
new BufferedInputStream(new FileInputStream("test.txt")));
while ((c = is.read()) >= 0) {
System.out.print((char) c);
}
is.close();
}
(4)运行结果
this is my java.io decorator test;
【乐山程序员联盟,欢迎大家加群相互交流学习5 7 1 8 1 4 7 4 3】