Java设计模式及应用场景之《装饰模式》

一、装饰模式定义

Attach additional responsibilities to an object dynamically keeping the
same interface.Decorators provide a flexible alternative to subclassing for
extending functionality.
(动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。)

二、装饰模式的结构和说明

在这里插入图片描述

  • Component 抽象组件对象。Component一般是一个接口或者抽象类,定义最原始、最基本的对象。
  • ConcreteComponent 具体对象。实现Component,被装饰器装饰的原始对象。
  • Decorator 所有装饰类的父类。实现Component,并持有一个Component对象。
  • ConcreteDecorator 装饰类,实现具体的装饰功能。

三、装饰模式示例

上班路上,路过一个卖煎饼果子的摊位,摊位前围着一些买煎饼的人。卖煎饼的大妈手法娴熟,左手舀一勺面糊到鏊子上,右手拿刮板唰唰绕两圈,一张厚薄均匀的煎饼就成型了,大妈左手抓一颗鸡蛋在鏊子边上一磕,举到煎饼正中,手指一掰,整团蛋黄就落到了煎饼上,一气呵成!伴随着热气,阵阵香味迎面飘来…,大妈将做好的煎饼打包,递给还在玩手机的小伙子,小伙也没想到大妈的速度这么快,看了看大妈递过来的煎饼,小伙对大妈说:你是不是少给我加了个蛋?大妈一听急了:我月入3万,怎么可能少你一个鸡蛋!
路人我,瞬间扎心!!!

那么,让我们用装饰模式也来摆个月入3W的煎饼摊吧。

首先,我们定义一个食材类。

/**
 * 食材
 * 抽象组件对象
 */
public interface Food {

    /**
     * 描述
     */
    String getDescription();

    /**
     * 价格
     */
    int getPrice();

}

定义一个煎饼类,此处就是我们的被装饰对象

/**
 * 煎饼果子
 */
public class Pancake implements Food {
    @Override
    public String getDescription() {
        return "煎饼果子";
    }

    /**
     * 纯煎饼果子8元
     */
    @Override
    public int getPrice() {
        return 8;
    }
}

定义装饰器父类

/**
 * 所有装饰器的父类,需要跟被装饰的对象实现同样的接口
 */
public class FoodDecorator implements Food{

    private Food food;

    public FoodDecorator(Food food) {
        this.food = food;
    }

    @Override
    public String getDescription() {
        return this.food.getDescription();
    }

    @Override
    public int getPrice() {
        return this.food.getPrice();
    }
}

定义具体装饰类:加鸡蛋的装饰类

/**
 * 装饰器对象,用来加鸡蛋
 */
public class EggDecorator extends FoodDecorator{

    public EggDecorator(Food food) {
        super(food);
    }

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

    /**
     * 加一个鸡蛋需要2元
     */
    @Override
    public int getPrice() {
        return super.getPrice() + 2;
    }
}

定义具体装饰类:加辣条的装饰类

/**
 * 装饰器对象,用来加辣条,卫龙牌的
 */
public class SpicyGlutenDecorator extends FoodDecorator{

    public SpicyGlutenDecorator(Food food) {
        super(food);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + " 加一包辣条";
    }

    /**
     * 加一包辣条1元
     */
    @Override
    public int getPrice() {
        return super.getPrice() + 1;
    }
}

定义具体装饰类:加火腿的装饰类

/**
 * 装饰器对象,用来加火腿
 */
public class SausageDecorator extends FoodDecorator{

    public SausageDecorator(Food food) {
        super(food);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + " 加一根火腿";
    }

    /**
     * 加一根火腿3元
     */
    @Override
    public int getPrice() {
        return super.getPrice() + 3;
    }
}

下面,我们先来做一套煎饼果子。

public static void main(String[] args) {
	Food pancake = new Pancake();
	// 加一个鸡蛋
	pancake = new EggDecorator(pancake);
	// 加一包辣条
	pancake = new SpicyGlutenDecorator(pancake);
	// 加一根火腿
	pancake = new SausageDecorator(pancake);
	// 再加一个鸡蛋吧
	pancake = new EggDecorator(pancake);

	System.out.println("套餐内容:" + pancake.getDescription());
	System.out.println("套餐价格:" + pancake.getPrice());
}

输出结果为:

套餐内容:煎饼果子 加一个鸡蛋 加一包辣条 加一根火腿 加一个鸡蛋
套餐价格:16

嗯嗯,那有人可能有这样的疑问,为什么不直接定义一个既加鸡蛋、加辣条又加火腿的煎饼果子类呢,这样直接new就可以,不是更直接么。

但是,假如我们只要加鸡蛋的?或者只要加辣条的?再或者要一个加鸡蛋和辣条,但是不加火腿的呢?这种类似的组合会很多,要是我们都创建出来的话,是不是需要创建的类的数量太多了啊。

并且,如果我们不仅有煎饼果子,我们还想推出烤冷面,通过装饰模式,这些装饰类就可以直接复用到烤冷面上了。

定义烤冷面类,也是我们的被装饰对象

/**
 * 烤冷面
 */
public class RoastColdNoodles implements Food {
    @Override
    public String getDescription() {
        return "哈尔滨烤冷面";
    }

    /**
     * 纯烤冷面10元
     */
    @Override
    public int getPrice() {
        return 10;
    }
}

再来做一份烤冷面:

public static void main(String[] args) {
	Food roastColdNoodles = new RoastColdNoodles();
	// 加一个鸡蛋
	roastColdNoodles = new EggDecorator(roastColdNoodles);
	// 加一包辣条
	roastColdNoodles = new SpicyGlutenDecorator(roastColdNoodles);

	System.out.println("套餐内容:" + roastColdNoodles.getDescription());
	System.out.println("套餐价格:" + roastColdNoodles.getPrice());
}

输出结果为:

套餐内容:哈尔滨烤冷面 加一个鸡蛋 加一包辣条
套餐价格:13

同上,如果我们再加一些装饰类,或者再推出一些其它产品,都可以很方便的进行搭配组合。

四、装饰模式在Java I/O中的使用

装饰模式在Java中最典型的应用,就是I/O流。

先来演示一下:

public static void main(String[] args) throws IOException {

	OutputStream outputStream = new FileOutputStream("E:\\MyEncrypt.txt");
	outputStream = new BufferedOutputStream(outputStream);
	outputStream = new DataOutputStream(outputStream);

	//输出内容
	outputStream.write("abc123".getBytes());
	outputStream.close();
}

执行完代码后,文件中就会输出下边的内容。

abc123

简单的画下它们之间的层级图:
在这里插入图片描述
是不是跟做一套煎饼的那个例子很像啊。

OutputStream 是最原始的抽象组件对象,
FileOutputStream 是被装饰类,
FilterOutputStream 是装饰类的父类
BufferedOutputStreamDataOutputStream 是装饰类。

同样的,输入流也类似,这里就不再演示了。

既然IO流是采用装饰模式实现的,那如果我们想自己实现一个装饰类功能,该怎么实现呢?比较简单,例如我们想实现一个简单的加密功能,就是将字节数组中的每个字节往后移动两位,看代码:

/**
 * 实现一个输出流的装饰类
 * 将字节往后移动两位
 */
public class EncryptOutputStream extends FilterOutputStream {

    public EncryptOutputStream(OutputStream out) {
        super(out);
    }

    /**
     * 将字节往后移动两位
     * @param b
     * @throws IOException
     */
    @Override
    public void write(int b) throws IOException {
        super.write(b+2);
    }

}

再来演示一下:

public static void main(String[] args) throws IOException {

	OutputStream outputStream = new FileOutputStream("E:\\MyEncrypt.txt");
	outputStream = new BufferedOutputStream(outputStream);
	outputStream = new DataOutputStream(outputStream);
	// 组合我们的加密装饰类(也就是将字节往后移动两位的功能组件)
	outputStream = new EncryptOutputStream(outputStream);

	//输出内容
	outputStream.write("abc123".getBytes());
	outputStream.close();
}

执行完后,文件中输出如下:

cde345

五、装饰模式的优缺点

优点:

  • 装饰模式可以动态的扩展一个类的功能。
  • 在扩展功能这一层面,比继承更灵活。
  • 装饰模式使得装饰器类可以更容易的复用。

缺点:

  • 装饰模式会产生很多细粒度的小类,如果过度使用,会使程序变得很复杂。

六、装饰模式的应用场景及案例

  • 需要扩展一个类的功能,或给一个类增加附加功能。
  • 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
  • Java中I/O流就是装饰模式最典型的应用。
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓呆同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值