装饰模式

2019-01-14 18084 ℃

定义

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

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

结构

  • 抽象组件(Component): 给出一个接口,以规范准备接收附加责任的对象。
  • 具体组件(ConcreteComponent): 定义一个将要接收附加责任的类。
  • 抽象装饰类(Decorator): 持有一个组件(Component)对象的实例,并实现一个与抽象组件接口一致的接口。
  • 具体装饰类(ConcreteDecorator): 负责给组件对象添加上附加的责任。

优点

  • 装饰模式是继承的一个替代模式,装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  • 具体组件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体组件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。

装饰模式UML类图

缺点

  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

适用场景

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类).

代码实现

设计不同种类的饮料,饮料可以添加佐料,并且支持动态添加新佐料。每增加一种佐料,该饮料的价格就会增加。

饮料抽象类(抽象组件)

public abstract class Beverage { //饮料描述 protected String description = "未知饮料"; /** * 获得描述 * @return 描述 */ public String getDescription() { return description; } /** * 计算价格的方法,所有子类必须实现此方法 * @return 价格 */ public abstract double cost(); }

佐料抽象类(抽象装饰类)

public abstract class CondimentDecorator extends Beverage { /** * 获得描述,佐料的子类必须重写描述信息 * @return 描述 */ @Override public abstract String getDescription(); }

饮料实现类(具体组件)

public class DarkRoast extends Beverage { public DarkRoast() { description = "深焙咖啡"; } @Override public double cost() { return .99; } }
public class Espresso extends Beverage { public Espresso() { description = "浓缩咖啡"; } @Override public double cost() { return 1.99; } }

佐料实现类(具体装饰类)

/** * 摩卡佐料 */ public class Mocha extends CondimentDecorator { //需要装饰的饮料 Beverage beverage; /** * 实例化佐料要求使用需要装饰的饮料作为参数 * @param beverage 饮料 */ public Mocha(Beverage beverage) { this.beverage = beverage; } /** * 饮料描述的末尾附加佐料的描述信息 * @return 描述 */ @Override public String getDescription() { return beverage.getDescription() + ", 摩卡"; } /** * 总价等于佐料的价格加上饮料的当前价格 * @return 总价 */ @Override public double cost() { return .20 + beverage.cost(); } }
/** * 豆浆佐料 */ public class Soy extends CondimentDecorator { //需要装饰的饮料 Beverage beverage; /** * 实例化佐料要求使用需要装饰的饮料作为参数 * @param beverage 饮料 */ public Soy(Beverage beverage) { this.beverage = beverage; } /** * 饮料描述的末尾附加佐料的描述信息 * @return 描述 */ @Override public String getDescription() { return beverage.getDescription() + ", 豆浆"; } /** * 总价等于佐料的价格加上饮料的当前价格 * @return 总价 */ @Override public double cost() { return .15 + beverage.cost(); } }
/** * 奶泡佐料 */ public class Whip extends CondimentDecorator { //需要装饰的饮料 Beverage beverage; /** * 实例化佐料要求使用需要装饰的饮料作为参数 * @param beverage 饮料 */ public Whip(Beverage beverage) { this.beverage = beverage; } /** * 饮料描述的末尾附加佐料的描述信息 * @return 描述 */ @Override public String getDescription() { return beverage.getDescription() + ", 奶泡"; } /** * 总价等于佐料的价格加上饮料的当前价格 * @return 总价 */ @Override public double cost() { return .10 + beverage.cost(); } }

测试

public class Test { public static void main(String args[]) { //购买一杯浓缩咖啡,不需要佐料 Beverage beverage = new Espresso(); //加入豆浆 beverage = new Soy(beverage); System.out.println(beverage.getDescription() + " $" + beverage.cost()); //购买一杯深焙咖啡 Beverage beverage2 = new DarkRoast(); //添加摩卡 beverage2 = new Mocha(beverage2); //再添加一份摩卡 beverage2 = new Mocha(beverage2); //最后加上奶泡 beverage2 = new Whip(beverage2); System.out.println(beverage2.getDescription() + " $" + beverage2.cost()); } }

输出结果

浓缩咖啡, 豆浆 $2.14
深焙咖啡, 摩卡, 摩卡, 奶泡 $1.49

Java I/O

java.io是一个装饰模式的典型案例,java.io中有许多类都是装饰者。

  • 输入相关的抽象类java.io.InputStream就是一个抽象组件

  • 继承自InputStreamFileInputStreamByteArrayInputStreamStringBufferInputStream 等都是具体组件

  • 同样继承自InputStreamFilterInputStream是一个抽象装饰类

  • 继承自FilterInputStreamBufferedInputStreamPushbackInputStreamDataInputStreamLineNumberInputStream等就是具体装饰类

实例:扩展java.io.FilterInputStream,添加一个新的装饰类LowerCaseInputStream,把输入流中的所有大写字符转成小写。

创建新的装饰类LowerCaseInputStream(具体装饰类)

public class LowerCaseInputStream extends FilterInputStream { /** * 调用父类的构造方法 * @param in 输入流 */ public LowerCaseInputStream(InputStream in) { super(in); } /** * 重写read方法,用于读取单个字节 * @return 字节的(0-255)内的字节值 * @throws IOException */ @Override public int read() throws IOException { //调用父类的read()方法 int c = super.read(); //进行大小写转换 return (c == -1 ? c : Character.toLowerCase((char)c)); } }

测试

public class InputTest { public static void main(String[] args){ int c; try { //先用BufferedInputStream装饰FileInputStream,再用自定义的LowerCaseInputStream进行装饰 InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("src\\decorator\\io\\test.txt"))); //每次读取一个字符并打印 while((c = in.read()) >= 0) { System.out.print((char)c); } in.close(); } catch (IOException e) { e.printStackTrace(); } } }

src\decorator\io\test.txt

I know the Decorator Pattern therefore I RULE!

输出结果

i know the decorator pattern therefore i rule!
版权声明:周华个人博客原创文章,转载请注明出处。

文章链接:http://www.iszhouhua.com/decorator-pattern.html

发表时间:2019-01-14 20:11

最后更新时间:2019-03-04 23:25