论坛首页 Java企业应用论坛

【设计模式】 装饰者与IO/Collection框架

浏览 2800 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-08-15   最后修改:2010-08-06

装饰者模式

      继承是OOP程序设计的一大特点,但其实对于很多复杂问题来说,利用继承关系处理问题往往具有很高的耦合性,不利于代码的维护。利用组合很大程度上可以做到降耦。多

用组合,少用继承是OOP设计的重要思想。
      装饰者模式给我们提出了一个好的OOP设计原则:类应该对扩展开放,对修改关闭 。
      这句话的意思就是,如果问题发生改变,衡量一个好的设计标准就是:你不需要修改类中的代码,只需要扩展新类来适应新的行为。
     《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() throws IOException; } //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>(); HashSet<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]);

 

论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics