装饰者模式

设计模式之装饰者模式

装饰者模式应用在java的IO中是非常频繁的,其属于结构型模式。该模式主要是通过动态扩展包装对象来实现的。下面通过一个简单的示例来进行说明。

简单示例

说有一男的(高富帅,会编码,呵呵……)相交一个女朋友,但是选择是太多了,女孩们有来自亚洲、非洲、美洲,有的会唱歌,有的会跳舞,还有的会说话等等吧,于是这个男的就在想,我如果给每个女生都创建一个java类的话,那么我得创建成千上万个类啊,不要说谈恋爱了,就是这个准备工作也把我给累死了,那么有什么比较好的方法可以让我不创建那么多的类,又能表示出每一种女孩呢?装饰者模式(decorator)啊
so……
创建如下类图:

示例类图

代码如下所示:
女孩接口

public abstract class Girl {
 String description = "no particular";
 public String getDescription(){
  return description;
 }
}

美国女孩

public class AmericanGirl extends Girl {
 public AmericanGirl() {
  description = "+American";
 }
}

EuropeanGirl.java

public class EuropeanGirl extends Girl {
 public EuropeanGirl(){
  description = "+European";
 }
}

GirlDecorator.java

public abstract class GirlDecorator extends Girl {
 public abstract String getDescription();
}

Science.java

public class SingSkill extends GirlDecorator {
 private Girl girl;
 public SingSkill (Girl girl){
  this.girl = girl;
 }
 @Override
 public String getDescription() {
  return this.girl.getDescription() + "+Like art";
 }
 public void canSing() {
  System.out.println("sing songs!");
 }

public void canDance() {
  System.out.println("danceing!");
 }
}

TalkSkill.java

public class TalkSkill extends GirlDecorator {
 private Girl girl;
 public TalkSkill(Girl girl){
  this.girl = girl;
 }
 @Override
 public String getDescription() {
  return this.girl.getDescription() + "+talk";
 }
 public void canTalk() {
  System.out.println("bla bla bla");
 }
}

Main.java

public class Main {
 public static void main(String[] args) {
  //普通美国女孩
  Girl g1 = new AmericanGirl();
  System.out.println(g1.getDescription());
  //会说话
  TalkSkill g2 = new TalkSkill(g1);
  System.out.println(g2.getDescription());
  //喜欢艺术的有很会说话
  SingSkill g3 = new SingSkill(g2);
  System.out.println(g3.getDescription());
 }
}

以上就是一个简单的装饰者的例子,也很能说明问题,原始类会被各种“技能”修饰,从而使原始类具有修饰者所具有的能力,简单的说就是一层包装,这种设计方式,并不是简单的应用接口和继承,在不改变原类文件的情况下,不使用继承,而进行动态的扩展,很好的支持开闭原则,这是装饰者模式最大的优点。

装饰者模式最普遍的应用在java IO的过程中,看下面代码可以很好的理解

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PushbackInputStream;
import java.io.PushbackReader;

public class IOTest {

  /* test.txt内容:
   * hello world!
   */
  public static void main(String[] args) throws IOException, ClassNotFoundException {
    //文件路径可自行更换
    final String filePath = "E:/myeclipse project/POITest/src/com/decorator/test.txt";

    //InputStream相当于被装饰的接口或者抽象类,FileInputStream相当于原始的待装饰的对象,FileInputStream无法装饰InputStream
    //另外FileInputStream是以只读方式打开了一个文件,并打开了一个文件的句柄存放在FileDescriptor对象的handle属性
    //所以下面有关回退和重新标记等操作,都是在堆中建立缓冲区所造成的假象,并不是真正的文件流在回退或者重新标记
    InputStream inputStream = new FileInputStream(filePath);
    final int len = inputStream.available();//记录一下流的长度
    System.out.println("FileInputStream不支持mark和reset:" + inputStream.markSupported());

    System.out.println("---------------------------------------------------------------------------------");

    /* 下面分别展示三种装饰器的作用BufferedInputStream,DataInputStream,PushbackInputStream,LZ下面做了三个装饰器的功能演示  */

    //首先装饰成BufferedInputStream,它提供我们mark,reset的功能
    BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);//装饰成 BufferedInputStream
    System.out.println("BufferedInputStream支持mark和reset:" + bufferedInputStream.markSupported());
    bufferedInputStream.mark(0);//标记一下
    char c = (char) bufferedInputStream.read();
    System.out.println("LZ文件的第一个字符:" + c);
    bufferedInputStream.reset();//重置
    c = (char) bufferedInputStream.read();//再读
    System.out.println("重置以后再读一个字符,依然会是第一个字符:" + c);
    bufferedInputStream.reset();

    System.out.println("---------------------------------------------------------------------------------");

    //装饰成 DataInputStream,我们为了又使用DataInputStream,又使用BufferedInputStream的mark reset功能,所以我们再进行一层包装
    //注意,这里如果不使用BufferedInputStream,而使用原始的InputStream,read方法返回的结果会是-1,即已经读取结束
    //因为BufferedInputStream已经将文本的内容读取完毕,并缓冲到堆上,默认的初始缓冲区大小是8192B
    DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
    dataInputStream.reset();//这是BufferedInputStream提供的功能,如果不在这个基础上包装会出错
    System.out.println("DataInputStream现在具有readInt,readChar,readUTF等功能");
    int value = dataInputStream.readInt();//读出来一个int,包含四个字节
    //我们转换成字符依次显示出来,可以看到LZ文件的前四个字符
    String binary = Integer.toBinaryString(value);
    int first = binary.length() % 8;
    System.out.print("使用readInt读取的前四个字符:");
    for (int i = 0; i < 4; i++) {
      if (i == 0) {
        System.out.print(((char)Integer.valueOf(binary.substring(0, first), 2).intValue()));
      }else {
        System.out.print(((char)Integer.valueOf(binary.substring(( i - 1 ) * 8 + first, i * 8 + first), 2).intValue()));
      }
    }
    System.out.println();

    System.out.println("---------------------------------------------------------------------------------");

    //PushbackInputStream无法包装BufferedInputStream支持mark reset,因为它覆盖了reset和mark方法
    //因为流已经被读取到末尾,所以我们必须重新打开一个文件的句柄,即FileInputStream
    inputStream = new FileInputStream(filePath);
    PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream,len);//装饰成 PushbackInputStream
    System.out.println("PushbackInputStream装饰以后支持退回操作unread");
    byte[] bytes = new byte[len];
    pushbackInputStream.read(bytes);//读完了整个流
    System.out.println("unread回退前的内容:" + new String(bytes));
    pushbackInputStream.unread(bytes);//再退回去
    bytes = new byte[len];//清空byte数组
    pushbackInputStream.read(bytes);//再读
    System.out.println("unread回退后的内容:" + new String(bytes));

    System.out.println("---------------------------------------------------------------------------------");

    /*  以上有两个一层装饰和一个两层装饰,下面我们先装饰成Reader,再进行其它装饰   */

    //由于之前被PushbackInputStream将流读取到末尾,我们需要再次重新打开文件句柄
    inputStream = new FileInputStream(filePath);
    InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"utf-8");//先装饰成InputStreamReader
    System.out.println("InputStreamReader有reader的功能,比如转码:" + inputStreamReader.getEncoding());

    System.out.println("---------------------------------------------------------------------------------");

    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);//我们进一步在reader的基础上装饰成BufferedReader
    System.out.println("BufferedReader有readLine等功能:" + bufferedReader.readLine());

    System.out.println("---------------------------------------------------------------------------------");

    LineNumberReader lineNumberReader = new LineNumberReader(inputStreamReader);//我们进一步在reader的基础上装饰成LineNumberReader
    System.out.println("LineNumberReader有设置行号,获取行号等功能(行号从0开始),当前行号:" + lineNumberReader.getLineNumber());

    System.out.println("---------------------------------------------------------------------------------");

    //此处由于刚才被readLine方法将流读取到末尾,所以我们再次重新打开文件句柄,并需要将inputstream再次包装成reader
    inputStreamReader = new InputStreamReader(new FileInputStream(filePath));
    PushbackReader pushbackReader = new PushbackReader(inputStreamReader,len);//我们进一步在reader的基础上装饰成PushbackReader
    System.out.println("PushbackReader是拥有退回操作的reader对象");
    char[] chars = new char[len];
    pushbackReader.read(chars);
    System.out.println("unread回退前的内容:" + new String(chars));
    pushbackReader.unread(chars);//再退回去
    chars = new char[len];//清空char数组
    pushbackReader.read(chars);//再读
    System.out.println("unread回退后的内容:" + new String(chars));
  }
}

在java的collection中也是用到了装饰者模式

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

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

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

总之,装饰器模式就是一个可以非常灵活的动态扩展类功能的设计模式,它采用组合的方式取代继承,使得各个功能的扩展更加独立和灵活。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值