引入


本节可以称为  “给爱用继承的人一个全新的设计眼界”。

我们即将再度讨论典型的继承滥用问题,在本章学到如何使用对象组合的方式,做到运行时装饰类,一旦你熟悉了装饰的技巧,你将能够在不修改任何底层代码的情况下,给你的对象赋予新的职责。

开放-关闭原则:类应该对扩展开放,对修改关闭

引用head first

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

UML类图

java装饰者模式例子有哪些 装饰者模式uml图_java装饰者模式例子有哪些

装饰者模式共有四大角色:

  • Component:抽象组件
  • 可以是一个接口或者是抽象类,其充当的就是被装饰的原始对象,用来定义装饰者和被装饰者的基本行为。
  • ConcreteComponent:组件具体实现类
  • 该类是 Component 类的基本实现,也是我们装饰的具体对象。
  • Decorator:抽象装饰者
  • 装饰组件对象,其内部一定要有一个指向组件对象的引用。在大多数情况下,该类为抽象类,需要根据不同的装饰逻辑实现不同的具体子类。当然,如果是装饰逻辑单一,只有一个的情况下我们可以忽略该类直接作为具体的装饰者。
  • ConcreteDecoratorA 和 ConcreteDecoratorB:装饰者具体实现类
  • 对抽象装饰者的具体实现。

  在已有的 Component 和 ConcreteComponent 体系下,实现装饰者模式来扩展原有系统的功能,可以分为 5 个步骤

  1. 继承或者实现 Component 组件,生成一个 Decorator 装饰者抽象类;
  2. 在生成的这个 Decorator 装饰者类中,增加一个 Component 的私有成员对象;
  3. 将 ConcreteComponent 或者其他需要被装饰的对象传入 Decorator 类中并赋值给上一步的 Component 对象;
  4. 在装饰者 Decorator 类中,将所有的操作都替换成该 Component 对象的对应操作;
  5. 在 ConcreteDecorator 类中,根据需要对应覆盖需要重写的方法。

 

示例

引用head first 星巴克咖啡例子

package com.zpkj.project8;
/**
 * Component
 * 装饰者模式 顶层
 */
public abstract class Beverage {
    
    protected String description;
    
    public abstract double cost();

    public String getDescription() {
        return description;
    }
}
package com.zpkj.project8;

//被装饰对象
public class DarkRoast extends Beverage{
    
    public DarkRoast() {
        description = "深焙咖啡";
    }
    
    @Override
    public double cost() {
        return 24;
    }

}
package com.zpkj.project8;
//被装饰对象
public class Decaf extends Beverage{

    public Decaf() {
        description = "无咖啡因咖啡";
    }

    @Override
    public double cost() {
        return 36;
    }

}
package com.zpkj.project8;
//被装饰对象
public class Espresso extends Beverage{
    
    public Espresso() {
        description ="浓缩咖啡";
    }

    @Override
    public double cost() {
        return 29;
    }

}

 

package com.zpkj.project8;
//被装饰对象
public class HouseBlend extends Beverage{
    
    public HouseBlend() {
        description = "黑咖啡";
    }

    @Override
    public double cost() {
        return 28;
    }

}

 

package com.zpkj.project8;

/**
 * 装饰者共同实现的接口(也可以是抽象类)
 */
public abstract class CondimentDecorator extends Beverage{
    
    protected Beverage beverage;
    
    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }
    public abstract String getDescription();
    
        
}
package com.zpkj.project8;
//装饰者具体实现
public class Whip extends CondimentDecorator{
    
    public Whip(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription()+",奶泡";
    }

    @Override
    public double cost() {
        return beverage.cost()+2;
    }

}

 

package com.zpkj.project8;
//装饰者具体实现
public class Mocha extends CondimentDecorator{
    
    public Mocha(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription()+",摩卡";
    }

    @Override
    public double cost() {
        return beverage.cost()+5;
    }

}
package com.zpkj.project8;

public class Test {
	
	public static void main(String[] args) {
		Beverage beverage = new DarkRoast();
		Mocha mocha = new Mocha(beverage);
		System.out.println(mocha.getDescription()+mocha.cost());
		Whip whip = new Whip(mocha);
		System.out.println(whip.getDescription()+whip.cost());
		
	}
}

 

测试

java装饰者模式例子有哪些 装饰者模式uml图_ide_02

Java 中的装饰者模式

java装饰者模式例子有哪些 装饰者模式uml图_ide_03

我们从java i/o也引出装饰者模式的一个缺点,利用装饰者模式,常常造成设计中有大量的小类,造成api使用的困扰,当了解装饰者模式的工作原理,很容易就能知道类结构是如何组织的。

编写自己的i/o装饰者

编写一个装饰者,把输入流内的所有大写字符转小写

package com.zpkj.project8;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

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) throws IOException {
        return super.read(b);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = super.read(b, off, len);
        for(int i = off;i<result+off;i++){
            b[i]=(byte)Character.toLowerCase((char)b[i]);
        }
        return result;
    }

}
package com.zpkj.project8;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class FileTest {
    public static void main(String[] args) throws IOException {
        int c;
        InputStream inputStream  =  new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("G:"+File.separator+"test.txt")));
        
        while((c = inputStream.read()) >=0){
            System.out.print((char)c);
        }
        inputStream.close();
    }
    

}

结果:

java装饰者模式例子有哪些 装饰者模式uml图_java_04

总结:

动态地将责任附加到对象上。想要扩展功能,装饰者提供有利于继承的另一种选择。

1.继承属于扩展形式之一,但不见得是达到弹性设计的最佳方案

2.在设计中,应允许行为可以被扩展,而无需修改现有的代码

3.组合和委任可以用在运行时动态加上新行为

4.除了继承、装饰者也可以让我们扩展行为

5.装饰者模式意味着一群装饰者类,这些类用来包装具体组件

6.装饰者反映出被装饰的组件类型,(事实上,他们都具有相同的类型,都经过接口或继承实现)。

7.装饰者可以在被装饰者的行为前面或后面加上自己的行为,甚至将装饰者的行为取代掉,达到特定目的

8.可以用无数装饰者包装一个组件

9.装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型

10.装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂

引用

[1] 弗里曼. Head First 设计模式(中文版)[Z]. 中国电力出版社: O'Reilly Taiwan公司 ,2007.

源码下载

https://github.com/isheroleon/design