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. H+ 关闭menuTab页面

    //注:在contabs.js文件中 $(function () { }); 方法外 加入//注: data-name="' + menuName + '" 这句是加入的自定义属性 ...

  2. kubernetes 学习资料

    谷歌大神详解 Kubernetes 配置管理最佳方法 https://www.kubernetes.org.cn/3031.html all in on kubernetes https://gith ...

  3. Clash Royale开发日志

    2018-04-17 本次更新: [修复点击时间bug] [修复断线重连后不自动开始脚本bug] 2018-04-16 本次更新增加以下功能: [支持断线重连机制(5分钟)] [界面UI设置] 201 ...

  4. Gitee vs插件(Gitee Extension for Visual Studio)

    Gitee 码云(gitee.com)是开源中国推出的代码托管平台,支持 Git 和 SVN,提供免费的私有仓库托管. https://gitee.com/GitGroup/Gitee.VisualS ...

  5. hdoj:2076

    夹角有多大(题目已修改,注意读题) Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others ...

  6. Centos&Redhat下bcm43142博通无线网卡linux驱动之二

    上次通过更换内核实现成功编译驱动无线网卡,但是启动到系统原内核下依然没有bcm43142的驱动,遂准备在原内核下编译驱动,记录一下 ps:更推荐这种方法,避免因更换内核出现其他兼容性问题 1.准备驱动 ...

  7. Houdini技术体系 过程化地形系统(一):Far Cry5的植被系统分析

    背景     在大世界游戏里,植被(biome)是自然环境非常重要的组成部分,虽然UE4里的也有比较不错的地形+植被系统,但相比国外AAA级游戏的效果,还是有不少的差距,简介如下: UE4的植被分为( ...

  8. php -v 与phpinfo显示版本不一样

    问题描述: php -v显示版本7.0 phpinfo 显示版本 7.2 使用软件phpstudy 原因:环境变量中显示的是7.0,所以php -v显示的也是7.0 解决办法:修改环境变量,然后重启电 ...

  9. SpringBoot------整合MyBatis

    1.添加pom.xml需要的依赖 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="htt ...

  10. springmvc 返回 404 解决

    Idea Maven springmvc spring 项目搭建中/url 可以访问controller,并且能返回正确的ModelAndView,但是页面总是显示404 项目结构: web.xml ...