装饰者模式

定义

  • 在不改变原有对象的基础之上,将功能附加到对象上
  • 提供了比继承更有弹性的替代方案(扩展原有对象功能)

类型

结构型

适用场景

  • 扩展一个类的功能或给一个类添加附加职责
  • 动态的给一个对象添加功能,这些功能可以再动态的撤销

从名字来简单解释下装饰器。既然说是装饰,那么往往就是添加小功能这种,而且,我们要满足可以添加多个小功能。最简单的,代理模式就可以实现功能的增强,但是代理不容易实现多个功能的增强,当然你可以说用代理包装代理的多层包装方式,但是那样的话代码就复杂了。

优点

  • 继承有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能

  • 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果

  • 符合开闭原则

缺点

  • 会出现更多的代码,更多的类,增加程序复杂性
  • 动态装饰时、多层装饰时会更复杂

相关设计模式

  • 装饰者模式和代理模式

装饰者模式关注在给一个对象上动态的添加方法;代理模式关注于控制对对象的访问,代理模式的代理类可以向客户隐藏对象的具体信息。在使用代理模式时,通常在代理类中创建一个对象的实例。而装饰者模式,通常会把原始对象当做参数传给装饰者的构造器。

Coding

举个简单的例子,我们大家都吃过煎饼,有时候会加一个蛋,加一根肠这样:

// 煎饼类
public class BatterCake {
    public String getDesc(){
        return "煎饼";
    }
    // 价格
    public int cost(){
        return 5;
    }
}

下面在来一个加蛋的煎饼,直接让它继承煎饼类就行:

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();
        System.out.println(batterCake.getDesc() + " 花费价格: " + batterCake.cost());

        BatterCakeWithEgg batterCakeWithEgg = new BatterCakeWithEgg();
        System.out.println(batterCakeWithEgg.getDesc() + " 花费价格: " + batterCakeWithEgg.cost());

        BatterCakeWithEggSausage batterCakeWithEggSausage = new BatterCakeWithEggSausage();
        System.out.println(batterCakeWithEggSausage.getDesc() + " 花费价格: " + batterCakeWithEggSausage.cost());
    }
}
// console
// 煎饼 花费价格: 5
// 煎饼 加一个鸡蛋 花费价格: 6
// 煎饼 加一个鸡蛋 加一根香肠 花费价格: 8

如果这时候有客户想要加两个鸡蛋呢,想加5个肠呢,是不是很变态?所有的可能都排列出来那要写多少个类?

接下里就是装饰者模式的show time:

装饰者模式要有一个抽象的实体类,还要有确定的实体类,要抽象的装饰者,和确定的装饰者。

在这场景中,被装饰的实体是煎饼,装饰者是鸡蛋和香肠。

// 抽象煎饼类
public abstract class AbsBatterCake {
    protected abstract String getDesc();
    protected abstract int cost();
}
// 一个实现类,煎饼
public class BatterCake extends AbsBatterCake {
    @Override
    protected String getDesc() {
        return "煎饼";
    }
    @Override
    protected int cost() {
        return 5;
    }
}

定义要加的小吃抽象装饰者基类,此类必须继承 AbsBatterCake:

// 小吃
public abstract class AbsSnack extends AbsBatterCake {}

定义具体能加的小吃,自然要继承 AbsSnack:

public class EggSnack extends AbsSnack {
    private AbsBatterCake batterCake;

    // 这里很关键,需要传入具体的煎饼
    // 当然也可以传入已经装饰好的煎饼
    public EggSnack(AbsBatterCake batterCake) {
        this.batterCake = batterCake;
    }

    @Override
    protected String getDesc() {
        return this.batterCake.getDesc() + " 加一个鸡蛋";
    }

    @Override
    protected int cost() {
        return this.batterCake.cost() + 1;
    }
}

public class SausageSnack extends AbsSnack {
    private AbsBatterCake batterCake;

    public SausageSnack(AbsBatterCake batterCake) {
        this.batterCake = batterCake;
    }

    @Override
    protected String getDesc() {
        return this.batterCake.getDesc() + " 加一个香肠";
    }

    @Override
    protected int cost() {
        return this.batterCake.cost() + 2;
    }
}

客户端调用:

public class Test {
    public static void main(String[] args) {
        AbsBatterCake batterCake = new BatterCake();
        // 开始装饰
        // 加一个鸡蛋
        batterCake = new EggSnack(batterCake);
        // 再加一个鸡蛋
        batterCake = new EggSnack(batterCake);
        // 加一个肠
        batterCake = new SausageSnack(batterCake);
        System.out.println(batterCake.getDesc() + " 售出价格: " + batterCake.cost());

    }
}
// console
// 煎饼 加一个鸡蛋 加一个鸡蛋 加一个香肠 售出价格: 9

下面,我们再来说说 java IO 中的装饰模式。看下图 InputStream 派生出来的部分类:
在这里插入图片描述

我们知道 InputStream 代表了输入流,具体的输入来源可以是文件(FileInputStream)、管道(PipedInputStream)、数组(ByteArrayInputStream)等,这些就像前面奶茶的例子中的红茶、绿茶,属于基础输入流。

FilterInputStream 承接了装饰模式的关键节点,它的实现类是一系列装饰器,比如 BufferedInputStream 代表用缓冲来装饰,也就使得输入流具有了缓冲的功能,LineNumberInputStream 代表用行号来装饰,在操作的时候就可以取得行号了,DataInputStream 的装饰,使得我们可以从输入流转换为 java 中的基本类型值。

当然,在 java IO 中,如果我们使用装饰器的话,就不太适合面向接口编程了,如:

InputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream("")));

这样的结果是,InputStream 还是不具有读取行号的功能,因为读取行号的方法定义在 LineNumberInputStream 类中。

我们应该像下面这样使用:

DataInputStream is = new DataInputStream(
                              new BufferedInputStream(
                                  new FileInputStream("")));

所以说嘛,要找到纯的严格符合设计模式的代码还是比较难的。


如果帮到你了,请点击右上角给个赞吧!!

学习笔记。内容总结于Geely老师的《Java设计模式精讲 》和 javadoop

欢迎访问我的博客:他和她的猫

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值