前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >装饰者模式在JDK和Mybatis中是怎么应用的?

装饰者模式在JDK和Mybatis中是怎么应用的?

作者头像
Java识堂
发布2019-05-21 23:06:57
4230
发布2019-05-21 23:06:57
举报
文章被收录于专栏:Java识堂Java识堂

介绍

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

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

代码语言:javascript
复制
// 煎饼类
public class Battercake {

    protected String getDesc() {
        return "煎饼";
    }

    protected int cost() {
        return 8;
    }
}

代码语言:javascript
复制
// 加鸡蛋的煎饼
public class BattercakeWithEgg extends Battercake{

    @Override
    public String getDesc() {
        return super.getDesc() + " 加一个鸡蛋";
    }

    @Override
    public int cost() {
        return super.cost() + 1;
    }
}

代码语言:javascript
复制
// 加鸡蛋和香肠的煎饼
public class BattercakeWithEggSausage extends BattercakeWithEgg {

    @Override
    public String getDesc() {
        return super.getDesc() + " 加一根香肠";
    }

    @Override
    public int cost() {
        return super.cost() + 2;
    }
}

测试一下,正常工作

代码语言:javascript
复制
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根香肠的煎饼,如果对每一种可能都写一个实现,会造成类爆炸。

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

代码语言:javascript
复制
// 组件类
public abstract class ABattercake {

    protected abstract String getDesc();
    protected abstract int cost();
}

代码语言:javascript
复制
// 具体组件实现类
public class Battercake extends ABattercake {

    protected String getDesc() {
        return "煎饼";
    }

    protected int cost() {
        return 8;
    }
}

代码语言:javascript
复制
// 抽象装饰器类
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();
    }
}

代码语言:javascript
复制
// 具体的装饰器实现类
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;
    }
}

代码语言:javascript
复制
// 具体的装饰器实现类
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根香肠的煎饼,实现方式如下

代码语言:javascript
复制
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操作时,经常写如下代码,你是否意识到这个用到了装饰者模式呢?

代码语言:javascript
复制
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("D:/test.txt")));

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

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

代码语言:javascript
复制
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的文件内容如下

代码语言:javascript
复制
THIS is JUST for TEST

测试类

代码语言:javascript
复制
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

代码语言:javascript
复制
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根据你配置的缓存回收策略来选择相应的装饰类,完成扩展功能。

推荐阅读:

不扯概念,用例子演示什么是脏读,不可重复读,幻读?

五分钟读懂工厂模式三兄弟

vim三种模式下的小技巧,提高一半工作效率

Java识堂

一个有干货的公众号

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java识堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • java io包
  • Mybatis缓存模块
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档