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. etcdv3 集群的搭建和使用

    etcd是一个开源的分布式键值对数据库,他的每一个节点都有一份数据的copy,当有节点故障时保证了高可用性.etcd使用Raft算法来保证一致性. 第一次接触etcd是在学习k8s时.k8s用etcd ...

  2. Python并行实例

    任务 def single(): # 单进程单线程实现 s = 0 for i in range(1, N): s += math.sqrt(i) return s 结论 Python多线程无法利用多 ...

  3. html input 文本框 只能输入数字,包含输小数点.

    <input type="text" id="source_tds" name="source_tds" value="&l ...

  4. ip代理优化

    如何保证可用ip不低于2000个,代理ip池优化策略 第一:获得大量ip: 第二:验证可用ip: 第三:监控可用ip: 第三:保证可用ip不低于3000或者5000: 截图是实时可用ip数量 心得:不 ...

  5. Apigee 简介与简单试用

     Apigee (国内访问需要***)是一家成立于2004年的API管理公司,于2016年9月被Google收购,作为Google云的服务之一.Apigee提供从API设计.开发.管理.门户.网关等 ...

  6. 如何在Mac的Finder中显示/usr,/tmp,/var等隐藏目录

    在终端里输入下面命令即可让它们显示出来. defaults write com.apple.finder AppleShowAllFiles -bool true 如果想恢复隐藏,可以用这个命令: d ...

  7. 用virsh console vhosts 卡住

    [root@666 ok]# virsh list --all Id Name State ---------------------------------------------------- 1 ...

  8. 如何免费的将本地Web服务映射到外网

    链接地址:https://hongmaju.github.io/2018/05/13/ngrok%E5%B0%86%E6%9C%AC%E5%9C%B0Web%E6%9C%8D%E5%8A%A1%E6% ...

  9. s和t的特殊权限

    ls -l 通常会显示r w x权限,分别对应:读,写,执行权限. 但是有时我么会看到,s或t这类权限标识. eg: #include <unistd.h> #include <st ...

  10. 构建基于Suricata+Splunk的IDS入侵检测系统

    一.什么是IDS和IPS? IDS(Intrusion Detection Systems):入侵检测系统,是一种网络安全设备或应用软件,可以依照一定的安全策略,对网络.系统的运行状况进行监视,尽可能 ...