[设计模式] 结构型:装饰器模式(Decorator Pattern)

什么是装饰器模式

房子装饰的再好看,它也还是个房子,只是住着比不装修的房子更舒服。

女人打扮的花枝招展,她也还是个女人,但她比素颜女人对男人更加有吸引力。

装饰器模式的概念就是这样,通过对具体对象进行不断的装饰,使其具备更强大的能力,但究其本身,依旧还是那个对象类的概念。

父子类继承关系,可以从类的层面上扩展对象功能,但是继承本身又让类之间有了耦合联系。装饰器模式可以看作是继承的替代方案之一,它是基于对象层面去扩展新功能。

Java里面,I/O标准库的设计就很好的展示了装饰器模式的魅力,如果有兴趣可以去看源码,仔细研究体会。

设计与实现

我们不具体分析Java I/O展示出来的装饰器模式,但是可以基于它的基本方法,尝试着用装饰器模式实现一个场景。

假设现在有这么一个场景,要读取一个文件的内容,提供三个读取方法read(),依次满足:

  1. 一次可以读取一个单词
  2. 一次可以读取一行内容
  3. 一次可以读取整个文件内容

怎么设计代码实现这个场景呢?第一条最好实现,第二条可以建立在第一条之上,第三条可以建立在第二条之上。也就是说,实现的思路是“依次装饰”。

基本接口与文件内容

根据需求场景,可以定义一个简单的接口规范:

public interface StringInputStream {
	// 读取文件内容(一个单词、一行内容、所有内容)
    String read() throws IOException;
}

文件内容,这是Mac系统下编辑的文件,行结尾的符号是换行键,对应的ASCII码是10,空格符对应的ASCII码是32

hello word
I tell you
You are beautiful

所以,请注意,以下程序适用于文件换行符是换行键的文件,其它文件格式不适用,需要修改!!!

读取一个单词

我们以Java自有的FileInputStream为基础,就可以很容易的实现这个需求:

// 一次读取一个单词
public class WordInputStream implements StringInputStream {
    private FileInputStream fileInputStream;

    public WordInputStream(FileInputStream inputStream) {
        this.fileInputStream = inputStream;
    }

    protected StringBuilder read0() throws IOException {
        StringBuilder sb = new StringBuilder();
        int read;
        while ((read = fileInputStream.read()) != -1) {
            sb.append((char) read);
            if (read == 32 || read == 10) break;
        }
        return sb.length() == 0 ? null : sb;
    }

	@Override
    public String read() throws IOException {
        StringBuilder sb = read0();
        if (sb == null) return null;
        int charAt = sb.charAt(sb.length() - 1);
        if (charAt == 32 || charAt == 10) return sb.substring(0, sb.length() - 1);
        return sb.toString();
    }
}

// 使用方法
public static void main(String[] args) throws IOException {
    FileInputStream fileInputStream = new FileInputStream("word.txt");
    WordInputStream wordInputStream = new WordInputStream(fileInputStream);
    String word = wordInputStream.read();
}

读取一行内容

包装一下WordInputStream类,就可以很容易的读取一行数据:

// 一次读取一行内容
public class LineInputStream implements StringInputStream {
    private WordInputStream wordInputStream;

    public LineInputStream(WordInputStream inputStream) {
        this.wordInputStream = inputStream;
    }

    protected StringBuilder read0() throws IOException {
        StringBuilder sb = new StringBuilder(), temp;
        while ((temp = wordInputStream.read0()) != null) {
            sb.append(temp);
            if (temp.charAt(temp.length() - 1) == 10) break;
        }
        return sb.length() == 0 ? null : sb;
    }

    @Override
    public String read() throws IOException {
        StringBuilder sb = read0();
        if (sb == null) return null;
        else return sb.toString();
    }
}

// 使用方法
public static void main(String[] args) throws IOException {
    FileInputStream fileInputStream = new FileInputStream("word.txt");
    WordInputStream wordInputStream = new WordInputStream(fileInputStream);
    LineInputStream lineInputStream = new LineInputStream(wordInputStream);
    String word = lineInputStream.read();
}

读取所有内容

包装一下LineInputStream类,就可以很容易的读取一行数据:

// 一次读取所有内容
public class WholeInputStream implements StringInputStream {
    private LineInputStream lineInputStream;

    public WholeInputStream(LineInputStream lineInputStream) {
        this.lineInputStream = lineInputStream;
    }

    protected StringBuilder read0() throws IOException {
        StringBuilder sb = new StringBuilder(), temp;
        while ((temp = lineInputStream.read0()) != null) {
            sb.append(temp);
        }
        return sb.length() == 0 ? null : sb;
    }

    @Override
    public String read() throws IOException {
        StringBuilder sb = read0();
        if (sb == null) return null;
        else return sb.toString();
    }
}

// 使用方法
public static void main(String[] args) throws IOException {
    FileInputStream fileInputStream = new FileInputStream("word.txt");
    WordInputStream wordInputStream = new WordInputStream(fileInputStream);
    LineInputStream lineInputStream = new LineInputStream(wordInputStream);
    WholeInputStream wholeInputStream = new WholeInputStream(lineInputStream);
    String word = wholeInputStream.read();
}

以上程序写的较为简单,但是基本也能表达出装饰器模式的用途,可以认为:

  1. WordInputStream装饰了FileInputStream
  2. LineInputStream装饰了WordInputStream
  3. WholeInputStream装饰了LineInputStream

装饰器模式与代理模式的区别

装饰器模式和静态代理模式很像,代码结构也几乎是一模一样的,它们的主要区别不在于代码结构,而在于应用的语境和语义。

设计模式的命名规则都是来源于生活场景,懂得在不同场景下应用不同的设计模式思想,比研究设计模式的代码具体要怎么写更有意义。

抛开具体的编程语言,设计模式的写法应该是千变万化的。

但是针对一种具体的编程语言,设计模式总会有一些流行的常规写法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值