详解设计模式之装饰者模式[以奶茶为案例介绍]

背景案例介绍

以奶茶为例,如下图所示,Beverage(饮料)是一个抽象类,类里面有一个description(描述)实例用来描述该奶茶,可以描述出奶茶的分类,奶茶有很多种分类,比如抹茶,波霸,玛奇朵,奶绿等等。还有一个cost()方法,用于给子类继承,实现每种奶茶的各自价格.

在这里插入图片描述

众所周知,我们每个人去买奶茶,可能还需要往奶茶里面加一些配料,如红豆,燕麦,布丁,奶霜等等,但是每种配料的价格也不尽相同,每款奶茶的价格也不一定相同,所以刚开始OO设计师就得设计出无数个类(因为我每种配料可以加多份,如我加两份红豆和3份燕麦,而这得加钱),分别用于得到每种组合的价格,如红豆抹茶奶茶、红豆波霸奶茶、布丁燕麦玛奇朵奶茶…如果奶茶分类和配料有很多的话,就会导致类多到爆炸.而且还存在一个问题,并且如果某天需要修改红豆的价格,完蛋了,需要找到所有带有红豆的奶茶类,然后进入代码一个个修改每个奶茶的cost方法。这样就严重的违反了设计模式的开闭原则.下图可以生动形象的体现出类爆炸的情况.

 ......上面也说了,设计师需要设计的类远不止这些,因为你没法确定顾客要加哪些配料,每种配料要加几份.所以聪明的OO设计师们就想到了后文的装饰者设计模式.

装饰者模式

装饰者模式的作用

装饰者模式的主要作用就是实现了开闭原则,即对功能扩展开发,对代码修改关闭.

定义:动态给一个对象添加一些额外的职责,就象在墙上刷油漆.使用Decorator装饰者模式相比用生成子类方式(继承)达到功能的扩充显得更为灵活。
设计初衷:通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了的,是静态的。所以为了避免这些问题,装饰者模式就出现了.

 注意: 装饰者与被装饰者拥有共同的超类,继承的目的是继承类型,而不是行为.

对我们初学者来说,我们可以对已经存在的核心对象称之为被装饰者,对这些核心对象进行扩展的对象称之为装饰者.

以奶茶为例,奶茶的分类有抹茶,波霸,玛奇朵,奶绿等等,这些就相当于是被装饰者,而且每一种奶茶都可以加一些配料,如红豆,燕麦,布丁等等,这些配料就相当于是装饰者.

装饰者模式的实现

我们可以将奶茶的品种,抹茶、波霸、玛奇朵、奶绿都继承Beverage类,都重写一下,然后用Decorator继承Beverage为一个装饰者类(也是抽象的类,同时注意我们上面也提了,我们继承的目的),然后红豆、奶绿、燕麦等调料都继承了Decorator类(装饰者类),在每个装饰者类里面都存放了一个Beverage对象,也就是用来装我们的抹茶、波霸等奶茶的。

说明:编写代码的时候我只编写了一种奶茶类和一种配料类,因为其它奶茶分类和配料类均类似,只是修改一下对应的描述和价格就可以了.

package com.deco;
/**
 * @Author 作者:小龙猿
 * @Project 项目:decoration
 * @Time 时间:2021/9/4 10:08
 */
public abstract class Beverage {
    String description = "";
    public String getDescription(){//用于子类继承,得到每种款式的名称
        return description;
    }
    public abstract double cost();//用于子类继承,实现各自的价格
}

定义被装饰者 

package com.deco;
import com.deco.Beverage;
/**
 * @Author 作者:小龙猿
 * @Project 项目:decoration
 * @Time 时间:2021/9/4 10:14
 */
//被装饰者类
public class BoBa extends Beverage {
    public BoBa(){
        description = "波霸奶茶";
    }
    @Override
    public double cost() {
        return 10;//定义波霸奶茶的价格为10元
    }
}

定义装饰者

package com.deco;
/**
 * @Author 作者:小龙猿
 * @Project 项目:decoration
 * @Time 时间:2021/9/4 10:19
 */
//装饰者类
public abstract class Decorator extends Beverage{
    Beverage beverage;
    public abstract String getDescription();
}

package com.deco;

/**
 * @Author 作者:小龙猿
 * @Project 项目:decoration
 * @Time 时间:2021/9/4 10:21
 */
//红豆类
public class HD extends Decorator {
    public HD(Beverage beverage){
        this.beverage = beverage;
    }
    @Override
    public String getDescription() {
        return beverage.getDescription()+"红豆";
    }
    @Override
    public double cost() {
        return beverage.cost()+2;//原有基础上加了一份红豆的钱2元
    }
}

测试结果

package com.deco;
/**
 * @Author 作者:小龙猿
 * @Project 项目:decoration
 * @Time 时间:2021/9/4 10:25
 */
//测试类
public class TestDecorate {
    public static void main(String args[]){
        Beverage beverage = new BoBa();//只要一份波霸奶茶
        System.out.println(beverage.getDescription()
                +"--"+beverage.cost()+"元");
        Beverage beverage1 = new BoBa();//要一份波霸奶茶
        beverage1 = new HD(beverage1);//要加一份红豆
        beverage1 = new HD(beverage1);//再加一份红豆
        //beverage1 = new NS(beverage1);//再加一份奶霜,这儿没有实现这个类,只是说明一下用法
        System.out.println(beverage1.getDescription()
                +"--"+beverage1.cost()+"元");
    }
}

第一位顾客点了一杯波霸奶茶,什么配料都没有加,价格就是10元;

第二位顾客点了一杯波霸奶茶,还加了两份红豆,价格就是10+2+2=14元

  • 每当红豆价格改变的时候我们只需要修改红豆(装饰者HD)的价格就可以了
  • 如果增加配料(装饰者),我们只需要增加一个类来继承Decorator类就可以了,同样可以用来装饰
  • 如果来了一种新饮品(被装饰者),如奶绿奶茶,就直接建立一个奶绿奶茶类继承Beverage,就可以了
  • 添加多份配料,如两份红豆,上面已经解决.

Java中已存在的装饰者模式

Java中的I/O,我们知道InputStream,FileInputStream,BufferedInputStream …等等,其实I/O就是使用的装饰者模式,只要继承FIlterInpiutStream类,就可以作为装饰者了.

我们现在就来实现一个装饰者类,功能是将文件中的大写字母转小写字母.

定义装饰者类:

package com.deco;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
 * @Author 作者:小龙猿
 * @Project 项目:decoration
 * @Time 时间:2021/9/4 11:05
 */
//自定义一个装饰者类,用于将大写字母转小写
public class LowerCaseInputStream extends FilterInputStream {
    protected LowerCaseInputStream(InputStream in) {
        super(in);
    }
    //重写父类的read方法
    public int read() throws IOException{
        int c = in.read();
        return (c == -1 ? c : Character.toLowerCase((char) c));//只要还能读到数据,就把该数据变成小写字母
    }
}

测试:

package com.deco;
import java.io.*;
/**
 * @Author 作者:小龙猿
 * @Project 项目:decoration
 * @Time 时间:2021/9/4 11:13
 */
public class InputDecorationTest {
    public static void main(String[] args) throws IOException {
        //创建文件
        File file = new File("test.txt");
        writeDataToFile(file);//向文件写入字母数据
        convertToLower(file);//将文件中的大写字母转为小写
    }
    private static void writeDataToFile(File file) throws IOException {
        OutputStream out = null;
        try {
            out = new BufferedOutputStream(new FileOutputStream(file));
            byte[] b = {65,66,67,97,98,99};//ABCabc
            out.write(b);
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            out.close();
        }
    }
    private static void convertToLower(File file) throws IOException {
        int c;
        InputStream in = null;
        try {
            in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream(file)));
            while ((c = in.read()) >= 0)//只要还能读到数据,就大写转小写
                System.out.print((char) c);
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if (in != null){
                in.close();
            }
        }
    }
}

我们先向一个文件写入了数据,然后再从这个文件读取数据并将其转为小写字母.

文件内容:

控制台结果: 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值