https://mp.weixin.qq.com/s/-bj71dBylRHRqiPorOpVyg

原创: 李立敏 Java识堂 3月10日

有一个卖煎饼的店铺找上了你,希望你能给她们的店铺开发一个收银系统,已知一个煎饼的价格是8元,一个鸡蛋的价格是1元,一根香肠的价格是2元。

你二话不说写出了如下代码

// 煎饼类
public class Battercake {     protected String getDesc() {
        return "煎饼";
    }     protected int cost() {
        return 8;
    }
}

// 加鸡蛋的煎饼
public class BattercakeWithEgg extends Battercake{     @Override
    public String getDesc() {
        return super.getDesc() + " 加一个鸡蛋";
    }     @Override
    public int cost() {
        return super.cost() + 1;
    }
}

// 加鸡蛋和香肠的煎饼
public class BattercakeWithEggSausage extends BattercakeWithEgg {     @Override
    public String getDesc() {
        return super.getDesc() + " 加一根香肠";
    }     @Override
    public int cost() {
        return super.cost() + 2;
    }
}

测试一下,正常工作

public class Test {

    public static void main(String[] args) {

        Battercake battercake = new Battercake();
        // 煎饼 销售价格:8
        System.out.println(battercake.getDesc() + " 销售价格:" + battercake.cost());         Battercake battercakeWithEgg = new BattercakeWithEgg();
        // 煎饼 加一个鸡蛋 销售价格:9
        System.out.println(battercakeWithEgg.getDesc() + " 销售价格:" + battercakeWithEgg.cost());         Battercake battercakeWithEggSausage = new BattercakeWithEggSausage();
        // 煎饼 加一个鸡蛋 加一根香肠 销售价格:11
        System.out.println(battercakeWithEggSausage.getDesc() + " 销售价格:" + battercakeWithEggSausage.cost());     }
}

但是这样会造成一个问题,煎饼的搭配种类很多。比如,加1根香肠的煎饼,加2个鸡蛋的煎饼,加2个鸡蛋和1根香肠的煎饼,如果对每一种可能都写一个实现,会造成类爆炸。

这个时候你就应该想到用装饰者模式了。来看看如何改造上面的代码

// 组件类
public abstract class ABattercake {     protected abstract String getDesc();
    protected abstract int cost();
}

// 具体组件实现类
public class Battercake extends ABattercake {     protected String getDesc() {
        return "煎饼";
    }     protected int cost() {
        return 8;
    }
}

// 抽象装饰器类
public class AbstractDecorator extends ABattercake {     private ABattercake aBattercake;     public AbstractDecorator(ABattercake aBattercake) {
        this.aBattercake = aBattercake;
    }     protected String getDesc() {
        return this.aBattercake.getDesc();
    }     protected int cost() {
        return this.aBattercake.cost();
    }
}

// 具体的装饰器实现类
public class EggDecorator extends AbstractDecorator {     public EggDecorator(ABattercake aBattercake) {
        super(aBattercake);
    }     @Override
    protected String getDesc() {
        return super.getDesc() + " 加一个鸡蛋";
    }     @Override
    protected int cost() {
        return super.cost() + 1;
    }
}

// 具体的装饰器实现类
public class SausageDecorator extends AbstractDecorator {     public SausageDecorator(ABattercake aBattercake) {
        super(aBattercake);
    }     @Override
    protected String getDesc() {
        return super.getDesc() + " 加一根香肠";
    }     @Override
    protected int cost() {
        return super.cost() + 2;
    }
}

如果有人想买加2个鸡蛋和1根香肠的煎饼,实现方式如下

public class Test {

    public static void main(String[] args) {
        ABattercake aBattercake = new Battercake();
        aBattercake = new EggDecorator(aBattercake);
        aBattercake = new EggDecorator(aBattercake);
        aBattercake = new SausageDecorator(aBattercake);
        // 煎饼 加一个鸡蛋 加一个鸡蛋 加一根香肠 销售价格为: 12
        System.out.println(aBattercake.getDesc() + " 销售价格为: " + aBattercake.cost());
    }
}

可以看到当要添加新的功能时,我们可以使用继承,在子类中添加新能的扩展实现。但有时候继承是不可行的,因为有些类是被final修饰的。而且待添加的新功能存在多种组合,使用继承的方式会导致大量子类的的出现。

而装饰者模式则是通过组合的方式来替代继承,为对象添加功能

看一下上述代码的UML图

从上图就可以画出装饰者模式的UML图如下

Component(组件):组件接口或抽象类定义了全部组件实现类以及所有装饰器实现的行为。

ConcreteComponent(具体组件实现类):具体组件实现类实现了Component接口或抽象类。通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了Component接口中定义的最基本的功能,其他高级功能或后序添加的新功能,都是通过装饰器的方式添加到该类的对象之上的。

Decorator(抽象装饰器):所有装饰器的父类,它是一个实现了Component接口的类,并在其中封装了一个Component对象,也就是被装饰的对象。而这个被装饰的对象只要是Component类型即可,这就实现了装饰器的组合和复用

ConcreteDecorator(具体的装饰器):该实现类要向被装饰对象添加某些功能

java io包

从上图可以看出,InputStream是组件,FileInputStream,ByteArrayInputStream是具体组件实现类,FilterInputStream是抽象装饰器,LineInputStream是具体的装饰器。

InputStream和OutputStream,Reader和Writer体系都用到了装饰者模式,不再概述。

举个例子,我们进行IO操作时,经常写如下代码,你是否意识到这个用到了装饰者模式呢?

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("D:/test.txt")));

当我们意识到这个用到装饰器模式时,想增加新功能时,就直接查找是否有相应的具体装饰器即可,或者自己实现一个装饰器,而不是陷入迷茫。

举个例子,我们想把从文件中读入的内容都转为小写时,只要自己继承FilterInputStream,实现相应的功能即可

public class LowerCaseInputStream extends FilterInputStream {

    /*
     * 自己的装饰类,将大写字母转为小写字母
     */
    protected LowerCaseInputStream(InputStream in) {
        super(in);
    }     @Override
    public int read() throws IOException {
        int c = super.read();
        return (c == -1 ? -1 : Character.toLowerCase((char)c));
    }     @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;
    }
}

D:/test.txt的文件内容如下

THIS is JUST for TEST

测试类

public class InputTest {

    public static void main(String[] args) {

        int c;
        try {
            InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("D:/test.txt")));
            while ((c = in.read()) >= 0) {
                //this is just for test
                System.out.print((char)c);
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }     } }

Mybatis缓存模块

Mybatis的缓存模块中,使用了装饰器模式的变体,其中将Decorator接口和Componet接口合并为一个Component接口,类间结构如下

Mybatis的Cache接口就是上图中的Component

public interface Cache {

  // 省略一部分方法
  String getId();   void putObject(Object key, Object value);   Object getObject(Object key);   Object removeObject(Object key);
}

看一下Cache接口的实现类

仔细看包名,由包名就可以看到PerpetualCache扮演着ConcreteComponent(具体组件实现类)的角色,其余的都是装饰类,为什么要弄这么多装饰类呢?

举个例子,我们可以在二级缓存中配置缓存回收策略。

可配置的选项有

LRU:最近最少使用,移除最长时间不被使用的对象
FIFO:先进先出,按对象进入缓存的顺序来移除它们
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象

再看上面的装饰类和这个配置选项的名字是不是很类似,Mybatis根据你配置的缓存回收策略来选择相应的装饰类,完成扩展功能。

装饰者模式在JDK和Mybatis中是怎么应用的? java io包的更多相关文章

  1. java.io包中的字节流—— FilterInputStream和FilterOutputStream

    接着上篇文章,本篇继续说java.io包中的字节流.按照前篇文章所说,java.io包中的字节流中的类关系有用到GoF<设计模式>中的装饰者模式,而这正体现在FilterInputStre ...

  2. Java中的阻塞和非阻塞IO包各自的优劣思考(经典)

    Java中的阻塞和非阻塞IO包各自的优劣思考 NIO 设计背后的基石:反应器模式,用于事件多路分离和分派的体系结构模式. 反应器(Reactor):用于事件多路分离和分派的体系结构模式 通常的,对一个 ...

  3. 1.java.io包中定义了多个流类型来实现输入和输出功能,

    1.java.io包中定义了多个流类型来实现输入和输出功能,可以从不同的角度对其进行分 类,按功能分为:(C),如果为读取的内容进行处理后再输出,需要使用下列哪种流?(G)   A.输入流和输出流 B ...

  4. Hadoop与HBase中遇到的问题(续)java.io.IOException: Non-increasing Bloom keys异常

    在使用Bulkload向HBase导入数据中, 自己编写Map与使用KeyValueSortReducer生成HFile时, 出现了以下的异常: java.io.IOException: Non-in ...

  5. [19/03/29-星期五] IO技术_File(文件)类(可操作文件,不能操作其里边内容,位于Java.io 包中)&递归遍历

    一.概念 java.io.File类:代表文件和目录. 在开发中,读取文件.生成文件.删除文件.修改文件的属性时经常会用到本类. 以pathname为路径创建File对象,如果pathname是相对路 ...

  6. 在mybatis中使用存储过程报错java.sql.SQLException: ORA-06550: 第 1 行, 第 7 列: PLS-00905: 对象 USER1.HELLO_TEST 无效 ORA-06550: 第 1 行, 第 7 列:

    hello_test是我的存储过程的名字,在mapper.xml文件中是这么写的 <select id="getPageByProcedure" statementType= ...

  7. windows 中使用hbase 异常:java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.

    平时一般是在windows环境下进行开发,在windows 环境下操作hbase可能会出现异常(java.io.IOException: Could not locate executable nul ...

  8. java.io包中的四个抽象类

    IO所谓的四大抽象类就是:  InputStream.OutputStream.Reader.Writer

  9. 从装饰者模式的理解说JAVA的IO包

    1. 装饰者模式的详解 装饰者模式动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性 的替代方案. 装饰者模式设计类之间的关系: 其 中Component是一个超类,ConcreteC ...

随机推荐

  1. OpenLayers Node环境安装运行构建-支持Vue集成OpenLayers

    NodeJS 环境安装包下载:https://nodejs.org/zh-cn/download/ 安装vue-cli3.0.1: https://cli.vuejs.org/guide/instal ...

  2. C# 简单POST请求 同时防止中文乱码的出现

    实现POST网络请求方法 public static string HttpPost(string url,string postDataStr) { string strReturn; //在转换字 ...

  3. C语言截取从某位置开始指定长度子字符串方法

    c语言标准库没有截取部分字符串的函数,为啥?因为用现有函数strncpy,很容易做到! ] = {""}; "}; strncpy(dest, src, ); puts( ...

  4. Redis 分布式锁的实现

    0X00 测试环境 CentOS 6.6 + Redis 3.2.10 + PHP 7.0.7(+ phpredis 4.1.0) [root@localhost ~]# cat /etc/issue ...

  5. Gin框架使用详解

    1.什么是Gin Gin是go编写的一个web应用框架. 2.Gin安装 go get github.com/gin-gonic/gin 3.Gin使用示例 package main import ( ...

  6. Asp.Net \uxxx Unicode编码解码

    /// <summary> /// Unicode编码 /// </summary> /// <param name="str"></pa ...

  7. 关于C/C++中求最大公约数和最小公倍数的算法

    1.更相减损法 int gcd(int a,int b) { while(a!=b) { if(a>b) a-=b; else b-=a; } return a; } 2.辗转相除法--递归 i ...

  8. centos6.5上安装ftp服务

    这是之前搭建过,但没记录,因为昨天使用人过来说使用有问题,突然发现没有记录,好心慌,现在的记忆真的只有1周而已,穷和老都是原罪啊!! 环境准备:centos6.5 vm 安装ftp:

  9. 第四百一十四节,python常用算法学习

    本节内容 算法定义 时间复杂度 空间复杂度 常用算法实例 1.算法定义 算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机 ...

  10. oss2罗列所有文件

    使用oss python sdk罗列某目录下所有文件. #!/usr/bin/python3 import sys, os import oss2 auth = oss2.Auth('keyID', ...