装饰器模式(通俗易懂)

反例都是为了和装饰器模式进行对比,突出其优点,若想直接查看装饰器模式的“真身”,可直接跳到装饰器模式目录

装饰器模式

简述:装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

业务场景:星巴克卖咖啡,一开始只有4种咖啡

Decaf:无咖啡因咖啡 Espresso:特浓咖啡 DarkRoast:焦炒咖啡 HouseBlend:混合咖啡
因所有咖啡都有共性,所有开发人员,把它们的共性上提到一个父类中:Beverage

反例 #1

abstract class Beverage {
    private String description;

    public Beverage(String description) {
        this.description = description;
    }

    public abstract double cost();

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

class Decaf extends Beverage {

    public Decaf() {
        super("无咖啡因咖啡");
    }

    @Override
    public double cost() {
        return 1;
    }
}


class Espresso extends Beverage {

    public Espresso() {
        super("特浓咖啡");
    }

    @Override
    public double cost() {
        return 1.5;
    }
}

class DarkRoast extends Beverage {

    public DarkRoast() {
        super("焦炒咖啡");
    }

    @Override
    public double cost() {
        return 2;
    }
}

class HouseBlend extends Beverage {

    public HouseBlend() {
        super("混合咖啡");
    }

    @Override
    public double cost() {
        return 3;
    }
}

//==============================================
public class AppTest {
    public static void main(String[] args) {
        Beverage b = new Decaf();
        Beverage b2 = new Espresso();
        Beverage b3 = new DarkRoast();
        Beverage b4 = new HouseBlend();

        System.out.println(b.getDescription() + ":" + b.cost());
        System.out.println(b.getDescription() + ":" + b2.cost());
        System.out.println(b.getDescription() + ":" + b3.cost());
        System.out.println(b.getDescription() + ":" + b4.cost());
    }
}

如此设计存在问题:

星巴克的老板为了提高自身的竞争力,想出了一个新的业务:调料。也就是可以给咖啡中放入调料:牛奶、豆浆、摩卡、泡沫

如何设计应对这种变化呢?

。。。。。。

反例 #2

为了满足上面需求中的变化:可以尝试这样解决问题:

//为加牛奶的Decaf咖啡创建一个类
class DecafWithMilk {
    
}
// 为加牛奶的Espresso咖啡创建一个类
class EspressoWithMilk {
    
}
// 为加牛奶且加豆浆的Decaf咖啡创建一个类
class DecafWithMilkAndSoy {
    
}
......

很明显,这样设计会导致类爆炸式增长,因此不能如此设计。

反例 #3

针对于反例2的问题,我们何必为每一种咖啡每加一种调料都创建一个类呢?这样太2了、太笨了!

我们可以直接在父类Beverage中,添加4个boolean属性,分别代表是否加了对应的4种调料!

abstract class Beverage {
    private String description;

    private boolean milk,soy,mocha,bubble;

    public Beverage(String description) {
        this.description = description;
    }

    public double cost() {
        double total = 0;
        if (milk) {
            total += 0.2;
        }
        if (soy) {
            total += 0.3;
        }
        if (mocha) {
            total += 0.4;
        }
        if (bubble) {
            total += 0.1;
        }
        return total;
    }

    public String getDescription() {
        if (milk) {
            description += " 牛奶";
        }
        if (soy) {
            description += " 豆浆";
        }
        if (mocha) {
            description += " 摩卡";
        }
        if (bubble) {
            description += " 气泡";
        }
        return description;
    }

    public void setDescription(String description) { this.description = description; }

    public boolean isMilk() { return milk; }

    public void setMilk(boolean milk) { this.milk = milk; }

    public boolean isSoy() { return soy; }

    public void setSoy(boolean soy) { this.soy = soy; }

    public boolean isMocha() { return mocha; }

    public void setMocha(boolean mocha) { this.mocha = mocha; }

    public boolean isBubble() { return bubble; }

    public void setBubble(boolean bubble) { this.bubble = bubble; }
}

class Decaf extends Beverage {

    public Decaf() {
        super("无咖啡因咖啡");
    }

    @Override
    public double cost() {
        // 咖啡本身价格 + 调料价格
        return 1 + super.cost();
    }
}


class Espresso extends Beverage {

    public Espresso() {
        super("特浓咖啡");
    }

    @Override
    public double cost() {
        return 1.5 + super.cost();
    }
}

class DarkRoast extends Beverage {

    public DarkRoast() {
        super("焦炒咖啡");
    }

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

class HouseBlend extends Beverage {

    public HouseBlend() {
        super("混合咖啡");
    }

    @Override
    public double cost() {
        return 3 + super.cost();
    }
}

//====================客户端==========================
public class AppTest {
    public static void main(String[] args) {
        Beverage b = new Decaf();

        b.setMilk(true);
        b.setSoy(true);

        System.out.println(b.getDescription() + ":" + b.cost());

    }
}

优点:

1. 类没有爆炸式增长

2. 若星巴克的老板又加入了一个新的饮料:茶,不会带来大大影响
//====================客户端==========================
// 新增饮料
class Tea extends Beverage {
    public Tea() {
        super("茶");
    }
    public double cost() {
        return 2;
    }
}

public class AppTest {
    public static void main(String[] args) {
        Beverage b2 = new Tea();
        System.out.println(b2.getDescription() + ":" + b2.cost());

    }
}

缺点:

  1. 星巴克的老板新加入一种调料:枸杞,就要重新改写父类Berverage的cost方法和getdisecription方法,从而将枸杞加进去,违反了开闭原则。

装饰器模式

package com.aluem.m_decorator.d;

/**
 * 业务场景:星巴克卖咖啡,一开始只有4种咖啡
 */
//----------------饮料父类----------------
abstract class Beverage {
    // description ==> 无咖啡因咖啡
    private String description;
    public Beverage(String description) {
        this.description = description;
    }
    public abstract double cost();

    public String getDescription() {
        return description;
    }
}
/**
 * 判断两个类之间能不能有继承关系,主要看这两个类之间有没有“is a”关系。并且要符合里氏替换原则。
 * 以上只是原则,不是语法强制的,也就是说在特定情况下,可以违反这个规则,在装饰器模式中就是这样。
 * 尽管调料不是饮料,但是为了制作出装饰器模式,我们也要让调料去继承饮料。
 */
//----------------调料父类----------------
abstract class Condiment extends Beverage {
    protected Beverage beverage;
    public Condiment(Beverage beverage) {
        super("调料");
        this.beverage = beverage;
    }
    // 关联Beverage类是为了在该处使用传入的子类方法
    public double cost() {
        return beverage.cost();
    }
}
//----------------饮料----------------
class Decaf extends Beverage {
    public Decaf() {
        super("无咖啡因咖啡");
    }
    @Override
    public double cost() {
        return 3;
    }
}
//----------------调料----------------
class Milk extends Condiment {
    public Milk(Beverage beverage) {
        super(beverage);
    }
    @Override
    public double cost() {
        return beverage.cost() + 1;
    }
    @Override
    public String getDescription() {
        return beverage.getDescription() + " 牛奶";
    }
}

class Soy extends Condiment {
    public Soy(Beverage beverage) {
        super(beverage);
    }
    @Override
    public double cost() {
        return beverage.cost() + 0.5;
    }
    @Override
    public String getDescription() {
        return beverage.getDescription() + " 豆浆";
    }
}
//===================客户端===========================
// 1.加入新的饮料
class Tea extends Beverage {

    public Tea() {
        super("茶");
    }

    @Override
    public double cost() {
        return 2;
    }
}
// 2.加入新的调料
class GouQi extends Condiment {

    public GouQi(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " 枸杞";
    }

    @Override
    public double cost() {
        return beverage.cost() + 1;
    }
}

public class AppTest {
    public static void main(String[] args) {
        Beverage beverage = new Decaf();

        Beverage beverage2 = new Milk(beverage);
        Beverage beverage3 = new Soy(beverage2);

        System.out.println(beverage3.getDescription() + ":" + beverage3.cost());
    }
}

执行示意图

在这里插入图片描述

在这里插入图片描述

优点:

  1. 加入新的饮料,不会违反开闭原则
  2. 加入新的调料,也不会违反开闭原则
//===================客户端===========================
// 1.加入新的饮料
class Tea extends Beverage {
    public Tea() {
        super("茶");
    }
    @Override
    public double cost() {
        return 2;
    }
}
public class AppTest {
    public static void main(String[] args) {
        Beverage beverage;
        beverage = new Tea(); // 无咖啡因咖啡
        beverage = new Milk(beverage);
        beverage = new Milk(beverage);
        System.out.println(beverage.getDescription() + ": " + beverage.cost());
    }
}
//===================客户端===========================
// 2.加入新的调料
class GouQi extends Condiment {
    public GouQi(Beverage beverage) {
        super(beverage);
    }
    @Override
    public String getDescription() {
        return super.getDescription() + " 枸杞";
    }
    @Override
    public double cost() {
        return super.cost() + 2;
    }
}
public class AppTest {
    public static void main(String[] args) {
        Beverage beverage;
        beverage = new Tea(); // 无咖啡因咖啡
        beverage = new Milk(beverage);
        beverage = new GouQi(beverage);
        System.out.println(beverage.getDescription() + ": " + beverage.cost());
    }
}

UML类图

2020-11-22 23-30-06 的屏幕截图

缺点:

由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。

JDK源码中的装饰器模式展现

jdk中的流,就是使用装饰器模式

public class AppTest {
    public static void main(String[] args) throws IOException {
        //InputStream 抽象组件 ==> 饮料
        //FileInputStream 具体组件 ==> 无咖啡因咖啡
        InputStream in = new FileInputStream("/home/aluem/软件著作权任务安排.txt");
        //BufferedInputStream 装饰器 ==> 牛奶、豆浆
        BufferedInputStream bis = new BufferedInputStream(in);
        InputStreamReader isr = new InputStreamReader(bis);

        isr.close();
    }
}

从上面的代码可以大致看出装饰器的形式,Inputstream类的子类为具体组件(无咖啡因咖啡)与一个抽象装饰器

在这里插入图片描述

那么其抽象装饰器是什么呢?抽象装饰器是关联与继承抽象组件InputSream的,我们从类关系中找到FilterInputStream类:

在这里插入图片描述

在这里插入图片描述

从源码分析,FilterInputStream(抽象装饰器:调料)类确实关联且继承Inputsream(抽象组件:饮料)类

从而Filterinputstream的子类为(装饰器:牛奶,豆浆。。
在这里插入图片描述

装饰java.io类UML类图

在这里插入图片描述

装饰器模式的简单实用

public class AppTest2 {
    public static void main(String[] args) throws Exception {
        Reader in = new FileReader("/home/aluem/1.txt");
         //关联Reader是为了使用Reader的功能,继承Reader是为了自己本身可以作为Reader传到别的装饰器中
        BufferedReader br = new BufferedReader(in);//BufferedReader可以读行,而读行需要使用FileReader的读取字符的功能,因此使用装饰器模式
		//BufferedReader br2 = new BufferedReader(br);
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }

        br.close();
    }
}

上面是JDK中的BufferedReader,我们通过装饰器模式自己创建一个新的BufferredReader模拟原来的。

public class MyBufferReader extends Reader {
    private Reader in;

    public MyBufferReader(Reader in){
        this.in = in;
    }

    public String readLine() throws IOException {
        StringBuilder sb = new StringBuilder();
        int read;

        while(true){
            read = in.read();
            if (read == '\r')
                continue;
            if (read == '\n' || read == -1){
                break;
            }
            sb.append((char)read);
        }
        if (sb.toString().length() == 0){
            if (read == '\n'){
                return "";
            }else {
                return null;
            }
        }else {
            return sb.toString();
        }
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        return 0;
    }

    @Override
    public void close() throws IOException {
        in.close();
    }

    /*===================客户端===========================*/
    public static void main(String[] args) throws IOException {
        Reader in = new FileReader("E:\\1.txt");
        MyBufferReader myBufferReader = new MyBufferReader(in);
      //  BufferedReader br = new BufferedReader(myBufferReader);   可以层层进行套娃包装

        String line;
        while(( line = myBufferReader.readLine() ) != null){
            System.out.println(line);
        }
    }
}

参考资料:Java设计模式-Mr.high、《设计模式就该这样学》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值