设计模式--装饰者模式
OO原则:开放-关闭原则
内容:类应该对扩展开放,对修改关闭
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。如能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求
虽然似乎有点矛盾,但是的确有一些技术可以允许在不直接修改代码的情况下对其进行扩展。在选择需要被扩展的代码部分时要小心。每个地方都采用开放-关闭原则,是一种浪费,也没必要,还会导致代码变得复杂且难以理解,接下来让我们一起在装饰者模式中体会这个原则。
装饰者模式的定义:
概念:装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者模式--类图:
案例:饮料店
框架图:
说明:cost()方法用来返回饮料的价格,description用来描述饮料,如:“越优深培(Dark Roast)”
在类图中我们可以发现,CondimentDecorator扩展自Beverage类,这用到了继承,这么做的重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的超类,这是相当关键的地方。在这里,我们利用继承达到“类型匹配”,而不是利用继承获得“行为”,继承Beverage抽象类,是为了有正确的类型,而不是继承它的行为。行为来自装饰者和基础组件,或与其他装饰者之间的组合关系,而且因为使用对象组合,可以把所有饮料和调料更有弹性地加以混和与匹配,非常方便,我们可以在任何时候,实现新的装饰者增加新的行为。如果依赖继承,每当需要新行为时,还得修改现有的代码。
案例
咖啡价格表:
代码:
饮料抽象类Beverage
public abstract class Beverage {
String description="Unknown Beverage";
public String getDescription(){
return description; //该方法已经在此实现
}
public abstract double cost();//必须在子类中实现
}
饮料一:浓缩咖啡
public class Espresso extends Beverage{//浓缩咖啡
public Espresso(){
description="Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
饮料二:houseBlend
public class HouseBlend extends Beverage{
public HouseBlend(){
description="House Blend Coffee";
}
@Override
public double cost() {
return 0.89;
}
}
饮料三:DarkRoast
public class DarkRoast extends Beverage{//深培咖啡
public DarkRoast(){
description="DarkRoast";
}
@Override
public double cost() {
return 0.99;
}
}
调料抽象类:CondimentDecorator
//所有装饰者的父类
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
//所有的调料装饰者都必须重新实现这个方法
}
调料一:摩卡
public class Mocha extends CondimentDecorator{
Beverage beverage;//记录饮料,也就是被装饰者
public Mocha(Beverage beverage){
this.beverage=beverage;
}
@Override
public double cost() {
return 0.20+beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription()+", Mocha";
}
}
调料二:豆浆
public class Soy extends CondimentDecorator{//豆浆
Beverage beverage;//记录饮料,也就是被装饰者
public Soy(Beverage beverage){
this.beverage=beverage;
}
@Override
public double cost() {
return 0.15+beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription()+", Soy";
}
}
调料三:奶泡
public class Whip extends CondimentDecorator{
Beverage beverage;//记录饮料,也就是被装饰者
public Whip(Beverage beverage){
this.beverage=beverage;
}
@Override
public double cost() {
return 0.10+beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription()+", Whip";
}
}
点单模拟:
public class StartCoffe {
public static void main(String[] args) {
//订一杯Espresso,不需要调料,打印出它的描述与价钱。
Beverage beverage1=new Espresso();
System.out.println(beverage1.getDescription()+" $"+beverage1.cost());
//DarkRoast,调料:两份摩卡,奶泡
Beverage beverage2=new DarkRoast();
beverage2=new Mocha(beverage2);
beverage2=new Mocha(beverage2);
beverage2=new Whip(beverage2);
System.out.println(beverage2.getDescription()+" $"+beverage2.cost());
//再来一杯调料为豆浆、摩卡、奶泡的HouseBlend咖啡。
Beverage beverage3=new HouseBlend();
beverage3=new Soy(beverage3);
beverage3=new Mocha(beverage3);
beverage3=new Whip(beverage3);
System.out.println(beverage3.getDescription()+" $"+beverage3.cost());
}
}
测试解果:
补充: cost()运作图:
如果顾客想要摩卡和奶泡深焙咖啡,cost的运作图如下:
jdk中的装饰者模式:java I/O
java.io包内的类太多了,简直是……“排山倒海”
案例:
BufferedInputStream及LineNumberInputStream都扩展自FilterInputStream,而FilterInputStream是一个抽象的装饰类。
装饰java.io类
你会发现“输出”流的设计方式也是一样的。你可能还会发现Reader/Writer流(作为基于字符数据的输入输出)和输入流/输出流的类相当类似(虽然有一些小差异和不一致之处,但是相当雷同,所以你应该可以了解这些类)。
但是JavaAI/O也引出装饰者模式的一个“缺点”:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程
序员的困扰。
编写自己的java I/O装饰类
案例:
将读入的流内的所有大写字符转换成小写
代码如下:
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c=super.read();
return (c==-1?c:Character.toLowerCase((char)c));
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
for (int i = off; i < off+result; i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
测试类:
public class InputTest {
public static void main(String[] args) {
InputStream in=null;
try {
in =new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")));
int c;
while((c=in.read())!=-1){
System.out.print((char)c);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (in!=null)
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
test.txt内容如下:
测试结果:
总结
- 体会OO原则:对外开放,对修改关闭
- 继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。
- 在我们的设计中,应该允许行为可以被扩展,而无须修改现有的代码
- 组合和委托可用于在运行时动态地加上新的行为
- 除了继承,装饰者模式也可以让我们扩展行为
- 装饰者模式意味着一群装饰者类,这些类用来包装具体组件
- 装饰者类反映出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现)。
- 装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的
- 你可以用无数个装饰者包装一个组件
- 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型
- 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂