【设计模式】装饰器模式

装饰器模式

以生活中的场景来举例,一个蛋糕胚,给它涂上奶油就变成了奶油蛋糕,再加上巧克力和草莓,它就变成了巧克力草莓蛋糕。

像这样在不改变原有对象的基础之上,将功能附加到原始对象上的设计模式就称为装饰模式(Decorator模式),属于结构型模式。

装饰器模式中主要有四个角色:
Component : 定义被装饰对象的接口,装饰器也需要实现一样的接口
ConcreteComponent: 具体的装饰对象,实现了Component接口,通常就是被装饰的原始对象
Decorator: 所有装饰器的抽象父类,需要定义与Component一致的接口,并且持有一个被装饰的Component对象
ConcreteDecorator: 具体装饰器对象
​​​​​​​​
在这里插入图片描述

代码示例

假设我们需要设计一个奖金的系统,目前一个工作人员的奖金包含三部分:

本月销售额的奖金 : 当月销售额的3%

累计销售额的奖金 : 累计销售额的0.1%

团队销售额的奖金 : 团队销售额的1%,只有经理才有

使用装饰模式的实现如下 : 定义一个所有奖金的抽象接口,然后定义一个BasicPrize的初始奖金对象,不同的人奖金的组成不同,就为其添加不同的装饰器


public abstract class Component {

    abstract double calPrize(String userName);
}

public class BasicPrize extends Component {

    //保存了一个员工与销售额的对应关系的map
    public static Map<String,Double> saleMoney = new HashMap<String,Double>();

    static {
        saleMoney.put("小明",9000.0);
        saleMoney.put("小陈",20000.0);
        saleMoney.put("小王",30000.0);
        saleMoney.put("张经理",55000.0);
    }

    public double calPrize(String userName) {
        return 0;
    }
}

/**
 *
 * 所有装饰器的抽象父类,持有一个被装饰对象
 */
public abstract class Decorator extends Component {
    protected Component component;

    public Decorator(Component component){
        this.component = component;
    }

    public abstract double calPrize(String userName);
}

/**
 * 
 * 当月奖金计算规则
 */
public class MonthPrizeDecorator extends  Decorator{

    public MonthPrizeDecorator(Component component) {
        super(component);
    }

    public double calPrize(String userName)  {
        //先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)
        double money = this.component.calPrize(userName);
        //计算本奖金  对应员工的业务额的3%
        double prize = BasicPrize.saleMoney.get(userName)*0.03;
        System.out.println(userName+"当月 业务奖金:"+prize);
        return money + prize;
    }

}
/**
 * 累计奖金
 */
public class SumPrizeDecorator extends Decorator {
    public SumPrizeDecorator(Component component) {
        super(component);
    }


    public double calPrize(String userName)  {
        //先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)
        double money = this.component.calPrize(userName);
        //计算本奖金  累计业务额的0.1% 假设是100000
        double prize = 100000*0.001;
        System.out.println(userName+"当月 累计奖金:"+prize);
        return money + prize;
    }

}
/**
 * 团队奖金
 */
public class GroupPrizeDecorator extends Decorator {
    public GroupPrizeDecorator(Component component) {
        super(component);
    }

    public double calPrize(String userName)  {
        //先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)
        double money = this.component.calPrize(userName);
        //计算本奖金  本团队的业务额的1%
        double sumSale = 0;
        for(double sale: BasicPrize.saleMoney.values()){
            sumSale += sale;
        }
        double prize = sumSale*0.01;
        System.out.println(userName+"当月 团队奖金:"+prize);
        return money + prize;
    }


}
/**
 * @Description
 */
public class Client {
    public static void main(String[] args) throws FileNotFoundException {
        //基础对象 奖金0
        Component basePrice = new BasicPrize();
        //当月奖金
        Component salePrize = new MonthPrizeDecorator(basePrice);
        //累计奖金
        Component sumPrize = new SumPrizeDecorator(salePrize);
        //团队奖金
        Component groupPrize = new GroupPrizeDecorator(sumPrize);
 
        //普通员工的奖金由两部分组成
        double d1 = sumPrize.calPrize("小明");
        System.out.println("========================小明总奖金:"+d1);
        double d2 = sumPrize.calPrize("小陈");
        System.out.println("========================小陈总奖金:"+d2);
        double d3 = sumPrize.calPrize("小王");
        System.out.println("========================小王总奖金:"+d3);
        //王经理的奖金由三部分组成
        double d4 = groupPrize.calPrize("张经理");
        System.out.println("========================张经理总奖金:"+d4);

       
    }
}

输出结果如下:
​​在这里插入图片描述
这里我们使用装饰器模式,主要是为了方便组合和复用。在一个继承的体系中,子类往往是互斥的,比方在一个奶茶店,它会有丝袜奶茶,红茶,果茶等,用户想要一杯饮料,一般都会在这些种类中选一种,不能一杯饮料既是果茶又是奶茶。然后用户可以根据自己的喜好添加任何想要的decorators,珍珠,椰果,布丁等,这些添加物对所有茶类饮品都是相互兼容的,并且是可以被允许反复添加的(同样的装饰器是否允许在同一个对象上装饰多次,视情况而定,像上面的奖金场景显然是不被允许的)

jdk中的装饰器模式

java.io包是用于输入输出的包,这里使用了大量的装饰器模式,我们再来体会一下装饰器模式的优点。

下图是jdk 输出流的一部分类图,很明显是一个装饰模式的类图,OutputStream是顶层父类,FileOutputStream和ObjectOutputStream是具体的被装饰类,FilterOutputStream是所有输出流装饰器的抽象父类
在这里插入图片描述
使用的代码如下:

	InputStream in = new FileInputStream("/user/wangzheng/test.txt");
	InputStream bin = new BufferedInputStream(in);
	byte[] data = new byte[128];
	while (bin.read(data) != -1) {
		//...
	}

为什么Java IO设计的时候要使用装饰器模式, 而J不设计⼀个继承 FileInputStream 并且⽀持缓存的 BufferedFileInputStream 类呢?

如果InputStream 只有⼀个⼦类 FileInputStream 的话,那我们在 FileInputStream 基础之上,再设计⼀个孙⼦类BufferedFileInputStream,也是可以接受的。但实际上,继承 InputStream 的⼦类有很多。我们需要给每⼀个 InputStream 的⼦类,
再继续派⽣⽀持缓存读取的⼦类。
除此之外,我们还需要对功能进⾏其他⽅⾯的增强,⽐如下⾯的DataInputStream 类,⽀持按照基本数据类型(int、boolean、long 等)来读取数据。这种情形下,使用继承的方式的话类的继承结构变得⽆⽐复杂,代码维护起来也比较费劲。

按照装饰器模式的结构,我们可以继承FilterOutputStream实现自定义的装饰器,并且在使用的时候可以和jdk自带的装饰器对象任意组合。

我们可以实现一个简单的复制输出内容的OutputStream装饰器

public class DuplicateOutputStream2 extends FilterOutputStream {
    /**
     * Creates an output stream filter built on top of the specified
     * underlying output stream.
     *
     * @param out the underlying output stream to be assigned to
     *            the field <tt>this.out</tt> for later use, or
     *            <code>null</code> if this instance is to be
     *            created without an underlying stream.
     */
    public DuplicateOutputStream2(OutputStream out) {
        super(out);
    }

    //将所有的内容复制一份输出 ab 变成aabb
    public void write(int b) throws IOException {
        super.write(b);
        super.write(b);
    }
}

装饰器类是否可以直接实现Component父类?

以输出流为例,如果直接继承OutputStream来实现自定义装饰器

public class DuplicateOutputStream extends OutputStream {
 
    private OutputStream os;
 
    public DuplicateOutputStream(OutputStream os){
        this.os = os;
    }
 
    //将所有的内容复制一份输出 ab 变成aabb
    public void write(int b) throws IOException {
        os.write(b);
        os.write(b);
    }
}
public class ClientTest {
    public static void main(String[] args) throws IOException {
 
        DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(new DuplicateOutputStream2(new FileOutputStream("1.txt"))));
        testOutputStream(dataOutputStream);
 
        DataOutputStream dataOutputStream1 = new DataOutputStream(new BufferedOutputStream(new DuplicateOutputStream(new FileOutputStream("1.txt"))));
        testOutputStream(dataOutputStream1);
 
    }
 
    public static void testOutputStream(DataOutputStream dataOutputStream) throws IOException {
        DataInputStream dataInputStream = new DataInputStream(new FileInputStream("1.txt"));
        dataOutputStream.write("bdsaq".getBytes());
        dataOutputStream.close();
        System.out.println(dataInputStream.available());
        byte[] bytes3 = new byte[dataInputStream.available()];
        dataInputStream.read(bytes3);
        System.out.println("文件内容:"+new String(bytes3));
    }
}

输出结果:
在这里插入图片描述
乍一看好像没什么区别,但是如果把BufferedOutputStream装饰器和自定义的装饰器互换。

DataOutputStream dataOutputStream2 = new DataOutputStream(new DuplicateOutputStream2(new BufferedOutputStream(new FileOutputStream("1.txt"))));
testOutputStream(dataOutputStream2);

DataOutputStream dataOutputStream3 = new DataOutputStream(new DuplicateOutputStream(new BufferedOutputStream(new FileOutputStream("1.txt"))));
testOutputStream(dataOutputStream3);

输出结果:
在这里插入图片描述
使用了实现OutputStream的DuplicateOutputStream会出现没有正常输出数据,这是因为我们使用了BufferedOutputStream这个带缓存区的输出流,缓存区的输出流在缓存区没有满的情形下是不会进行输出操作的。一般情形下我们在调用jdk的DataOutputStream的close方法的时候会调用其传入的输出流的flush()方法,并且向下传递调用,BufferedOutputStream里的数据会正常输出。

在使用DuplicateOutputStream2的时候其调用关系是这样的:

dataOutputStream.close()–>duplicateOutputStream2.flush()–>bufferedOutputStream.flush()

使用DuplicateOutputStream的时候由于DuplicateOutputStream继承的OutputStream的flush()方法是空实现,所以不会继续往下调用bufferedOutputStream的flush()方法,故而最后没有得到输出内容

所以装饰器类需要继承Decorator抽象父类,而不是直接继承Component抽象类,我认为是为了在Decorator里实现一些共性的代码,以便在使用装饰器的时候能够更加自由,无视其组合顺序 (当然如果你的Decorator里没有任何逻辑代码,在合适的场景下你可以不定义抽象装饰器类)

总结

装饰器模式主要用于解决继承关系过于复杂的问题,通过组合来替代继承。
它主要的作⽤是给原始类添加增强功能。

除此之外,装饰器模式还有⼀个特点,那就是可以对原始类嵌套使⽤多个装饰器。为了满⾜这个应⽤场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接⼝。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值