设计模式之装饰者模式

装饰者模式

手抓饼问题:我们可以单点一个手抓饼,也可以给手抓饼添加一些配料,比如可以添加鸡蛋、培根、火腿等,有了这些配料之后,那么我们可以点一个鸡蛋手抓饼、鸡蛋火腿手抓饼、还可以点一个加了两个鸡蛋一个火腿的手抓饼……,组合有很多种,另外,我们还要能够计算出每个手抓饼的价格!

第一种方案:我首先想到的设计一个抽象父类Food(食物类),该抽象类包含一个抽象方法cost,用于计算手抓饼的价格,然后再根据客户的需求,创建各种子类去继承父类,实现cost方法,比如可以创建手抓饼类,鸡蛋手抓饼类,鸡蛋火腿手抓饼类……,有人可能会说,这样能够满足ocp原则,便于扩展,无可否认,这种方案确实满足了ocp原则,客户要点一个培根火腿手抓饼,那么我们就去创建一个培根火腿手抓饼子类去继承Food类,便于扩展,而且不会修改原来的代码;但是我们应该清楚,客户的需求有千万种,也就是说手抓饼的组合有千万种,如果客户每来一个需求我们就去创建一个子类,就会出现类爆炸,代价岂不是太大了?

方案一类图:
在这里插入图片描述
我们换一种方案:还是设计一个抽象父类(Food),让手抓饼类和调料类分别去继承Food类,这里我们将调料类设计成抽象类,该类聚合了Food类,cost方法提供空实现,让具体的鸡蛋类、培根类、火腿类去重写即可。

类图:
在这里插入图片描述
代码如下:

(为了便于知道某个类的功能,下面代码对类的命名并没有满足代码规范)

public abstract class Food {
	//计算价格
	public abstract int cost();
}
public class SZB extends Food{
	@Override
	public int cost() {
		return 4;//单点手抓饼的价格是4元
	}	
}
public abstract class tiaoliao extends Food{

	public Food food=null;
	//注意:tiaoliao是一个抽象类,不能直接通过new创建对象,类的构造方法是为了给子类去调用的
	public tiaoliao(Food food) {
		this.food=food;
	}
	//默认空实现
	@Override
	public int cost() {
		return 0;
	}
}
public class Egg extends tiaoliao{
	
	public Egg(Food food){
		super(food);//调用父类的构造方法
	}

	@Override
	public int cost() {
		int value=2;//鸡蛋的价格
		int cost = super.food.cost();//父类的价格
		return value+cost;//总价格
	}
}
public class huotui extends tiaoliao{
	
	public huotui(Food food){
		super(food);//调用父类的构造方法
	}

	@Override
	public int cost() {
		int value=3;//火腿的价格
		int cost = super.food.cost();//父类的价格
		return value+cost;//总价格
	}
}
public class peigen extends tiaoliao{
	
	public peigen(Food food){
		super(food);//调用父类的构造方法
	}

	@Override
	public int cost() {
		int value=4;//培根的价格
		int cost = super.food.cost();//父类的价格
		return value+cost;//总价格
	}
}
public class test {

	public static void main(String[] args) {

		Food food1=new SZB();
		System.out.println("手抓饼的价格为:"+food1.cost());
		
		Food food2=new Egg(food1);
		System.out.println("鸡蛋手抓饼的价格为:"+food2.cost());
		
		Food food3=new huotui(food2);
		System.out.println("火腿鸡蛋手抓饼的价格为:"+food3.cost());
		
		Food food4=new peigen(food3);
		System.out.println("培根火腿鸡蛋手抓饼的价格为:"+food4.cost());
	}

}
手抓饼的价格为:4
鸡蛋手抓饼的价格为:6
火腿鸡蛋手抓饼的价格为:9
培根火腿鸡蛋手抓饼的价格为:13

以上的方案就是装饰者模式,装饰者模式就是动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性, 装饰者模式也体现了开闭原则(ocp)

之前还在另一篇博客上看到关于装饰者模式的另一个定义:装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

装饰者模式类图:
在这里插入图片描述
上面例子中,手抓饼类就是被装饰者,而调料类就是装饰者,被装饰者被聚合到装饰者类里面,可以动态的将各种调料附加到被装饰者上,如果客户有新的需求或者要添加新的调料,都非常的方便,无需修改原来的代码,扩展性很好!

装饰者模式的思想就是使用了继承+聚合/组合,核心就是将被装饰者聚合到装饰者类里面,有人可能会问为什么不将装饰者聚合到被装饰者里面,例如上面的例子中就是将调料类装饰到手抓饼类,这样做的话被装饰者类里面需要进行逻辑判断,针对不同的调料执行不同的方法,那么如果增加新的调料需要修改原来的代码,违反ocp,不可取。

在生活中,我们能举出很多用到装饰者模式的例子:

  • 奶茶问题:我们可以单点一杯奶茶,也可以给奶茶加珍珠、加椰果、加奶盖……
  • 咖啡问题:我们可以点单品咖啡,也可以给咖啡加牛奶、加巧克力、加豆浆……
  • 快递问题:我们可以给我们的物件加上报纸填充、塑料泡沫、纸板、木板等……

装饰者模式的优点

  1. 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加。虽然说装饰者类可能也有很多的子类,但是却比继承的方式大大减少子类的个数;比如前面说的手抓饼加调料的例子中,调料类(装饰者)虽然有火腿、鸡蛋、培根等子类,但是对于这些调料的组合却是可以使用多重装饰的方式,不断叠加,不需要更多的子类;相比于继承,火腿、鸡蛋、培根是子类,但是对于这些调料类的每一种组合又是新的子类,比如火腿鸡蛋类、火腿培根类;会导致子类的个数大大增加。

多重装饰指的是:一个已被装饰者加强的类也可以继续再被加强,有点像链式调用;例如手抓饼被鸡蛋装饰后,变成鸡蛋手抓饼,此时要实现火腿鸡蛋手抓饼,只需要再次对鸡蛋手抓饼做加强就可以了,而不是直接用火腿类去加强手抓饼类。

public class test {

	public static void main(String[] args) {

		Food food1=new SZB();
		System.out.println("手抓饼的价格为:"+food1.cost());
		
		Food food2=new Egg(food1);
		System.out.println("鸡蛋手抓饼的价格为:"+food2.cost());
		
		Food food3=new huotui(food2);
		System.out.println("火腿鸡蛋手抓饼的价格为:"+food3.cost());
	}

}
  1. 在一定程度上继承的方式也能解决装饰者模式的功能,使用子类的方式扩展一个类的功能,也满足ocp;但是在一些继承受限的场景下,就可以用装饰者模式代替继承,比如java中,如果要扩展功能的类被final修饰了,那么就不能被子类继承了。

装饰者模式在jdk源码中的体现

JAVA的IO就用到了装饰者模式,我们先附上类图:

在这里插入图片描述

  1. InputStream 是抽象类, 类似我们前面例子中的Food
  2. FileInputStream、StringBufferInputStream、ByteArrayInputStream是 InputStream 子类,类似我们前面的SZB,是被装饰者
  3. FilterInputStream 是 InputStream 子类:类似我们前面的tiaoliao,也就是装饰者抽象类,类内部聚合了InputStream类,源码是protected volatile InputStream in
  4. BufferedInputStream、DataInputStream 是 FilterInputStream的子类,具体的装饰者,类似前面的Egg、peigen、huotui

从java的io结构中,可以充分体会到装饰器模式的灵活运用,我们创建的一个FileInputstream对象,可以使用各种装饰器让它具有不同的特别的功能,比如我们可以把它装饰成BufferedInputStream,它提供我们mark,reset的功能,这正是动态扩展一个类的功能的最佳体现。

//普通的FileInputStream对象
InputStream inputStream = new FileInputStream(filePath);
//装饰成 BufferedInputStream,提供mark、reset功能
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
//再进行一层包装,装饰成DataInputStream
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值