设计模式之装饰者模式

前言

本节从装饰者模式到Java 里的 IO。装饰者模式能够在不修改任何底层类代码的情况下,给你的(或别人的) 对象赋予新的职责。属于结构型设计模式。符合开闭原则,但是会产生更多的类,提高程序复杂性。

代码实现

// 抽象实体类
public abstract class Cake {

    protected abstract String getDesc();

    protected abstract int cost();
}
// 确定的实体类
public class RelCake extends Cake {
    @Override
    public String getDesc() {
        return "这是一个蛋糕";
    }

    @Override
    public int cost() {
        return 150;
    }
}
// 抽象装饰者
public abstract class Decorator extends Cake {
    private Cake cake;

    public Decorator(Cake cake) {
        this.cake = cake;
    }

    // 抽象装饰者可以通过此方法保证装饰者实现此方法 (拓展)
    // protected abstract void doSomething();

    @Override
    public String getDesc() {
        return cake.getDesc();
    }

    @Override
    public int cost() {
        return cake.cost();
    }
    
}

// 具体装饰者
// 往蛋糕里面加葡萄
public class CakeWithGrapes extends Decorator {
    public CakeWithGrapes(Cake cake) {
        super(cake);
    }

    @Override
    public String getDesc() {
        return super.getDesc() + " 加一份葡萄";
    }

    @Override
    public int cost() {
        return super.cost() + 20;
    }
}
// 往蛋糕里加草莓
public class CakeWithStra extends Decorator {

    public CakeWithStra(Cake cake) {
        super(cake);
    }

    @Override
    public String getDesc() {
        return super.getDesc() + " 加一份草莓";
    }

    @Override
    public int cost() {
        return super.cost() + 30;
    }
}
// 测试
public class DecoratorTest {
    public static void main(String[] args) {
        // 声明抽象实体
        Cake cake;
        // 赋值具体实体
        cake = new RelCake();
        // 进行装饰
        cake = new CakeWithGrapes(cake);
        cake = new CakeWithGrapes(cake);
        cake = new CakeWithStra(cake);
        System.out.println(cake.getDesc() + "价钱:" + cake.cost());
    }
}

UML

角色:抽象的实体类,确定的实体类。抽象的装饰者,确定的装饰者。
设计模式之装饰者模式

Java I/O

InputStream 作为基类,抽象组件

一类是直接提供数据的基础InputStream:抽象装饰者

  • FileInputStream
  • ByteArrayInputStream
  • ServletInputStream
  • ...

一类是提供额外附加功能的InputStream:具体装饰者

  • BufferedInputStream
  • DigestInputStream
  • CipherInputStream
  • ...

当我们需要给一个“基础”InputStream附加各种功能时,我们先确定这个能提供数据源的InputStream,因为我们需要的数据总得来自某个地方,例如,FileInputStream,数据来源自文件:

InputStream file = new FileInputStream("test.gz");

紧接着,我们希望FileInputStream能提供缓冲的功能来提高读取的效率,因此我们用BufferedInputStream包装这个InputStream,得到的包装类型是BufferedInputStream,但它仍然被视为一个InputStream:

InputStream buffered = new BufferedInputStream(file);

无论我们包装多少次,得到的对象始终是InputStream

注意到在叠加多个FilterInputStream,我们只需要持有最外层的InputStream,并且,当最外层的InputStream关闭时(在try(resource)块的结束处自动关闭),内层的InputStream的close()方法也会被自动调用,并最终调用到最核心的“基础”InputStream,因此不存在资源泄露。

自己实现一个 LowerCaseInputStream

对文件中的字符串将大写字符小写处理

// 拓展FilterInputStream 这是所有InputStream 的抽象装饰者
public class LowerCaseInputStream extends FilterInputStream {

    protected LowerCaseInputStream(InputStream in) {
        super(in);
    }

    // 针对字节
    @Override
    public int read() throws IOException {
        int cur = super.read();
        return ((cur == -1) ? cur : Character.toLowerCase((char) cur));
    }

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

}

public class FileTest {
    public static void main(String[] args) {
        int c;
        try {
            // 对 InputStream 的包装
            InputStream inputStream = new FileInputStream("yourPath\\test.txt");
            inputStream = new BufferedInputStream(inputStream);
            inputStream = new LowerCaseInputStream(inputStream);

            // 读取字符进行输出
            while ((c = inputStream.read()) >= 0) {
                System.out.print((char) c);
            }
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

test.txt 内容:What are you Doing now?

控制台输出:what are you doing now?

总结

通过在继承上面加入组合的方式实现装饰。抽象策略将抽象实体用构造器传入,从而对抽象实体进行装饰。 可以在运行时给某个功能添加附加的功能。

References

上一篇:算法笔记系列:4.3 递归 4.4 贪心


下一篇:【Python基础入门】喝一杯奶茶的时间学会面向对象—继承