`

装饰者模式

 
阅读更多
装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者模式提供了比集成更有弹性的体态方案。
1. 装饰者和被装饰对象有相同的超类型。

2. 可以用一个或多个装饰者包装一个对象。

3. 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。

4. 对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。

5. 装饰模式中使用继承的关键是想达到装饰者和被装饰对象的类型匹配,而不是获得其行为。

6. 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。在实际项目中可以根据需要为装饰者添加新的行为,做到“半透明”装饰者。

7. 适配器模式的用意是改变对象的接口而不一定改变对象的性能,而装饰模式的用意是保持接口并增加对象的职责。

 package com.jelly.decorator;

/**
 * 定义一个对象接口,可以给这些对象动态地添加职责
 * 
 * @author Jelly QQ:136179492
 * 
 */
public abstract class Component {
	public abstract void operation();
}

/**
 * 定义一个对象,可以给这个对象添加一些职责
 * @author Jelly QQ:136179492
 *
 */
public class ConcreteComponent extends Component {

	public void operation() {
		System.out.println("具体对象的操作");
	}

}
/**
 * 装饰者
 * 维持一个指向Component对象的引用,并定义一个与 Component接口一致的接口。
 * 
 * @author Jelly QQ:136179492
 * 
 */
public class Decorator extends Component {
	private Component component;

	public void setComponent(Component component) {
		this.component = component;
	}

	public void operation() {
		if (null != component) {
			component.operation();
		}
	}

}
public class ConcreteDecoratorA extends Decorator {

	private String addedState;

	@Override
	public void operation() {
		super.operation();
		addedState = "New State";
		System.out.println(addedState);
		System.out.println("具体装饰对象A的操作");
	}

}
public class ConcreteDecoratorB extends Decorator {
	@Override
	public void operation() {
		super.operation();
		addedBehavior();
		System.out.println("具体装饰对象B的操作");
	}

	public void addedBehavior() {
		System.out.println("ConcreteDecoratorB操作");
	}
}

public class Test {

	public static void main(String[] args) {
		ConcreteComponent c = new ConcreteComponent();
		ConcreteDecoratorA d1 = new ConcreteDecoratorA();
		ConcreteDecoratorB d2 = new ConcreteDecoratorB();
		d1.setComponent(c);
		d2.setComponent(d1);
		d2.operation();
	}
}

总结:装饰者模式是位了已有功能动态的添加更多功能的一种方式。当系统需要新功能的时候,是向旧功的类中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为。
适用性:

以下情况使用Decorator模式

1. 需要扩展一个类的功能,或给一个类添加附加职责。

2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。

3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。

4. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

 

优点:

1. Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。

2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

缺点:

1. 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。

2. 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。

3. 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。

《Head First Design Patterns》对装饰者模式说的很清楚。这里稍微注意几点:       
      (1) 装饰者和被装饰者必须具有相同的超类型。
      (2) 装饰者即可以包装被装饰者,也可以包装装饰者。往往利用多层包装来达到目的。
      (3) 装饰者中组合了被装饰者对象,这是装饰类的关键特征。正是由于这种组合,使得我们能够随心所欲的通过嵌套装饰来动态扩展行为。



Java IO框架的装饰者设计



在java类库中的IO流就是用装饰者模式设计的。JDK5.0中60多个IO流类组成了四大家族:InputStream,OutputStream,Reader,Writer。
      InputStream/OutputStream是对字节序列进行操作的抽象类。
      Reader/Writer是基于Unicode代码单元进行操作的抽象类。

这四大家族中大量的类都具有自己不同的功能,要做到方便的完成各种输入输出行为。必须组合使用这些类,装饰者模式是再好不过的设计了。那么IO类库如何实现装饰者模式的,我们看看几个类的部分源码:
//InputStream:字节序列输入类鼻祖
public abstract class InputStream implements Closeable { 
    //最基本的读取字节的抽象方法,供子类扩展。 
    public abstract int read() throws IOException;
 } 
    //FileInputStream: 读取文件中的字节流类 继承InputStream
public class FileInputStream extends InputStream{ 
   //构造器
     public FileInputStream(String name) throws FileNotFoundException{ 
    //.......
    } 
  //本地方法,与操作系统低层交互的具体读入方法
   public native int read() throwsIOException; 
} 
  //FilterInputStream: 过滤流类,起装饰器作用,用于对输入装配各种功能 
public class FilterInputStream extends InputStream { 
   //用于记录被装饰者,也就是需要装配新功能的InputStream对象 
    protected volatile InputStream in;
   protected FilterInputStream(InputStream in) { 
   //构造装饰器 
    this.in = in; 
   //设置需要被包装InputStream对象
   } 
  //读入字节 
   public int read() throws IOException { 
     return in.read(); 
   } 
} 
//BufferedInputStream: 使输入流具有缓冲功能,是一种可以装配缓冲功能的装饰器,继承FilterInputStream 
public class BufferedInputStream extends FilterInputStream { 
//构造器 
   public BufferedInputStream(InputStream in) { 
    this(in, defaultBufferSize); 
   //in就是被装配缓冲功能的InputStream 
  } 
} 

这四个类同属于InputStream家族,他们就是一个经典的装饰器模式设计。其中
       InputStream 具有读入功能的抽象被装饰器。
       FileInputStream  具有读入文件功能的具体被装饰器
       FilterInputStream  具备装饰器的抽象意义。
       BufferedInputStream   具有具体功能(缓冲功能)的装饰器。
这个时候后我想设计一个具有缓冲功能的读取文件中的字节的行为:

public void IOTest{ 
  //缓冲装饰器包装文件字节输入流 
  BufferedInputStream bis=new BufferedInputStream(new FileInputStream("C://decorator.txt")); 
  //读取内容 
    bis.read(); 
} 
IO类库中还有很多其他的装饰器,比如处理基本数据类型的DataInputStream,处理ZIP文件流的ZipInputStream,等等。只要我们想的到的行为,都可以用这些装饰器包装组合来完成。就这一点,装饰器绝对是Perfect。



Java Collection框架的装饰者设计

在JDK类库种,集合类也使用了这种设计模式,我们看看这种设计模式给集合类库带来了什么好处?



问题提出:当我们即需要List结构的可重复存储,又需要Set中高效率的查找操作。怎么办?

最好的解决办法就是:先用List存储好所有的数据,当需要查找某个元素的时候,将List对象包装成Set类型进行查找,然后返回List数据结构和Set的查找结果。将两种不同类别的功能合并使用,装饰者模式(包装器)无疑是最好的设计。


所有实现Collection接口的集合类都有一种构造器,其参数是集合类的引用。






//ArrayList的包装构造器 
public ArrayList(Collection<? extends E> c) { ..... } 
//LinkedList的包装构造器 
public LinkedList(Collection<? extends E> c) { ..... } 
//HashSet的包装构造器 public HashSet(Collection<? extends E> c) { ..... }


我们可以通过这种构造直接将一种Collection类对象包装成另一种。

//Collection类的打包过程 
import java.util.*;
public class TestDemo{ 
   public static void main(String[] args){ 
    ArrayList<String> list=new ArrayList<String>(); 
    list.add("a1"); 
    list.add("a1"); 
    list.add("b1"); 
    list.add("c1"); 
    System.out.println("List ="+list); 
    HashSet<String> set=new HashSet<String>(list);
   //包装 
    System.out.println("Set ="+set); 
  } 
} 
/*运行结果: List =[a1, a1, b1, c1] Set =[b1, a1, c1] */

值得注意的是:包装过程中集合类的存储数据类型必须兼容Collection<? extends E>。也就是被 包装 集合中数据类型必须是包装集合数据类型的子类或两者类型相同 。例如:被包装的ArrayList中的数据类型是Manager,它是包装的HashSet中数据类型Employee的子类。否则编译器不会通过。 这也是为了保证类型的自动向上转型的特性。被包装的类型可以通过包装操作自动向上转型成父类。

//包装过程中泛型类型的兼容 
import java.util.*; 
class Employee{ } 
class Manager extends Employee{ } 
public class TestDemo{ 
    public static void main(String[] args){ 
   /*error : 
ArrayList<Employee> list=new ArrayList<Employee>();        HashSet<Manager> set=new HashSet<Manager>(list);*/ 
  //正确: 
 ArrayList<Manager> list=new ArrayList<Manager>(); 
 ashSet<Employee> set=new HashSet<Employee>(list); 
 } 
}

顺便提一句: 集合框架中的Map类型是不能和Collection类型互相包装的,他们的数据结构毕竟相差太大了 。(~你不想在商店买到的可乐里包装的是医用酒精吧)



这里提一下我一直的疑问:Collection的名字有点让人费解,刚学的时候还以为他就是所有集合类的基础接口,毕竟Collection的中文意思就是集合吗? 后来发现Map类型和

Collection没有一点关系,不知道Java的设计者是怎么取名字的,或者有难言之隐吧。

数组转化为集合:Array的asList()。
       String[] values=".....";
       HashSet<String> hs=new HashSet<String>(Array.asList(values));

集合转化为数组:Collection的toArray()。
       HashSet<String> hs=new HashSet<String>();
       Object[] o=hs.toArray();



千万要注意:toArray()方法返回的是一个Object[]数组。而且你无法将其强制类型转化成你需要的数组类型。(关于类类型间的强制类型转换,我在《【解惑】Java类型间的转型 》一文中有详细阐述)
               String[] array=(String[])hs.toArray(); //error

我们必须使用toArray的变体来做到这一点,为toArray传递一个长度为0的随意类型的数组。然后,返回的数组就是这个类型了。
               String[] array=hs.toArray(new String[0]);

                                                                                     
  • 大小: 48.3 KB
  • 大小: 40.5 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics