设计模式之装饰器模式详解

在介绍装饰器模式之前,先来看看在一般的开发生产中,对于已有功能新需求的实现,往往一开始想到的方法:一种是直接修改已有功能的代码;一种是使用继承方式。第一种缺点很明显,破坏方法稳定性、复杂场景下导致方法代码冗长,维护起来十分麻烦等,对于第二种,在场景简单的情况下可以适当采用,但场景复杂起来,子类的数量会增长的十分迅速,这时候就可以考虑使用装饰器模式优化代码,下面以生活中的一个场景加以说明。

场景

目前国内奶粉市场十分庞大,国家也对奶粉安全加强监管,不仅国内品牌,国外品牌也针对中国市场推出了各自的国行产品。家长们肯定都经历过给宝宝选奶粉的情况,大体上关注奶粉的奶源地、配方、营养成分等关键指标,这里为简化情况,暂时只关注营养成分一项。国行奶粉的营养成分目前都要满足国家标准,在此之外可以选择加入其它营养成分,比如:DHA(现在不含都不好意思卖了)、LF(乳铁蛋白)、OPO、益生元等。

在不考虑设计模式的情况下,要实现一个描述奶粉营养成分和一罐生产成本的功能,我们把奶粉抽象成一个类,并定义出两个方法。

public class Formula {
	// 成本假设201元
	public int cost(){
		return 201;
	}
	// 营养成分为国标成分
	public String nutrient(){
		return "国标";
	}
}

为抢占市场,有品牌推出了添加DHA成分的奶粉,为了描述它,我们定义一个新的类DhaFormula继承Formula,并根据情况调整方法。

public class DhaFormula extends Formula {

	@Override
	public int cost() {
		return super.cost() + 49;
	}

	@Override
	public String nutrient() {
		return super.nutrient() + "+DHA";
	}
}

竞争是激烈的,又有品牌推出了添加OPO成分的奶粉,现在为了描述它,我们不得不又定义一个新的类OpoFormula继承Formula,并根据情况调整方法。

public class OpoFormula extends Formula {

	@Override
	public int cost() {
		return super.cost() + 27;
	}

	@Override
	public String nutrient() {
		return super.nutrient() + "+OPO";
	}
}

为避免被挤出市场,有品牌直接推出了添加DHA+OPO组合的奶粉,不用我说,也知道又要定义一个新类DnaOpoFormula了,这里我们可以选择Formula、OpoFormula、DhaFormula其中一个集成,这里为了保持统一还是继承Formula,并根据情况调整方法。

public class DnaOpoFormula extends Formula {

	@Override
	public int cost() {
		return super.cost() + 76;
	}

	@Override
	public String nutrient() {
		return super.nutrient() + "+DHA+OPO";
	}
}

情况很显然,仅仅2种可选择添加成分,我们为了全可能描述它们就要创建4个类(一个基类,三个子类),这里我们测试一下。

public class Test {

	public static void main(String[] args) {

		Formula formula = new Formula();
		System.out.println("营养成分:" + formula.nutrient());
		System.out.println("成本(元):" + formula.cost());

		Formula dhaFormula = new DhaFormula();
		System.out.println("营养成分:" + dhaFormula.nutrient());
		System.out.println("成本(元):" + dhaFormula.cost());

		Formula opoFormula = new OpoFormula();
		System.out.println("营养成分:" + opoFormula.nutrient());
		System.out.println("成本(元):" + opoFormula.cost());

		Formula dnaOpoFormula = new DnaOpoFormula();
		System.out.println("营养成分:" + dnaOpoFormula.nutrient());
		System.out.println("成本(元):" + dnaOpoFormula.cost());
	}
}

运行结果:
在这里插入图片描述
科技在不断进步,市场上有出现了乳铁蛋白、益生元等新的添加成分,2种的时候我们就定义了 C 2 1 C_2^1 C21 + C 2 2 C_2^2 C22 3个子类;3种添加成分的话,全可能描述的话就要定义 C 3 1 C_3^1 C31 + C 3 2 C_3^2 C32 + C 3 3 C_3^3 C33 7个子类;4种,5种……添加成分的话,这个子类的增长速度是不是有点吃不消,这个时候就可以考虑使用装饰器模式来优化我们的代码,动态的为对象添加功能,基于组合的方式来实现,而我们知道一句话:组合优于继承

装饰器模式

先提一个设计原则:类应该对扩展开放,对修改关闭。简单的说,就是扩展新功能而不是修改代码,在不修改现有代码的情况下,适应新的行为改变。

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构;属于结构型模式。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

实现该模式的关键角色:

角色名说明备注
Component(被装饰对象基类)定义对象的接口或者抽象类,最原始的对象奶粉接口
ConcreteComponent(具体被装饰对象)接口或抽象类的实现,你要装饰的就是它国标奶粉实现
Decorator(装饰者抽象类)维护指向Component实例的引用,定义与Component一致的接口(也就是要继承或实现被装饰对象基类)添加成分抽象类
ConcreteDecorator(具体装饰者)具体的装饰对象,给内部持有的具体被装饰对象增加具体的职责DHA、OPO具体可添加成分

现在我们按照装饰器和接口编程的思想来实现上面的场景。国家规定了奶粉必须包含的营养成分标准,不管奶粉中添加了什么成分,国标中规定的是一定要包含的,那么国标成分就是奶粉的营养成分基准,而添加成分只是奶粉的附加属性。

我们可以定义一个接口IFormula,包含成本和营养成分两个方法作为被装饰对象基类。

public interface IFormula {

	int cost();

	String nutrient();
}

最普通的国标奶粉作为奶粉的一种,实现了此接口,添加成分也是针对此类奶粉添加,作为具体被装饰对象。

public class Formula implements IFormula{

	@Override
	public int cost() {
		return 201;
	}

	@Override
	public String nutrient() {
		return "国标";
	}
}

而添加成分有很多种类,我们可以单独把它抽象出来,将其设计成一个抽象类,让子类去设计定义具体的成分(是DHA还是OPO)。但是,这个添加成分抽象类单独定义是没有任何意义,需要将添加成分放进奶粉中才能完成制成配方奶。因此,这个抽象类需要实现奶粉接口,并通过构造函数,将顶层接口进行赋值操作(这里是为了再次调用接口的方法),作为装饰者抽象类。

public abstract class OptNutrient implements IFormula{

	private IFormula iFormula;

	public OptNutrient(IFormula iFormula){
		this.iFormula = iFormula;
	}

	@Override
	public int cost() {
		return iFormula.cost();
	}

	@Override
	public String nutrient() {
		return iFormula.nutrient();
	}
}

这样我们就可以针对不同的添加成分定义具体装饰者类了,这里为了体现组合的优势,定义DHA、OPO、LF(乳铁蛋白)三个具体装饰者类。

DhaFormula

public class DhaFormula extends OptNutrient {

	public DhaFormula(IFormula iFormula) {
		super(iFormula);
	}

	@Override
	public int cost() {
		return super.cost() + 49;
	}

	@Override
	public String nutrient() {
		return super.nutrient() + "+DHA";
	}
}

OpoFormula

public class OpoFormula extends OptNutrient {

	public OpoFormula(IFormula iFormula) {
		super(iFormula);
	}

	@Override
	public int cost() {
		return super.cost() + 27;
	}

	@Override
	public String nutrient() {
		return super.nutrient() + "+OPO";
	}
}

LfFormula

public class LfFormula extends OptNutrient {

	public LfFormula(IFormula iFormula) {
		super(iFormula);
	}

	@Override
	public int cost() {
		return super.cost() + 32;
	}

	@Override
	public String nutrient() {
		return super.nutrient() + "+LF";
	}
}

这样我们就可以对三种添加成分进行描述了,我们来测试一下。

public class Test {

	public static void main(String[] args) {
		// 国标奶粉
		IFormula formula = new Formula();
		System.out.println("营养成分:" + formula.nutrient() + ",成本(元):" + formula.cost());
		// 添加DHA奶粉
		IFormula dhaFormula = new DhaFormula(formula);
		System.out.println("营养成分:" + dhaFormula.nutrient() + ",成本(元):" + dhaFormula.cost());
		// 添加OPO奶粉
		IFormula opoFormula = new OpoFormula(formula);
		System.out.println("营养成分:" + opoFormula.nutrient() + ",成本(元):" + opoFormula.cost());
		// 添加LF奶粉
		IFormula lfFormula = new LfFormula(formula);
		System.out.println("营养成分:" + lfFormula.nutrient() + ",成本(元):" + lfFormula.cost());
		// 添加DHA+OPO奶粉
		IFormula dhaOpoFormula = new DhaFormula(opoFormula);
		System.out.println("营养成分:" + dhaOpoFormula.nutrient() + ",成本(元):" + dhaOpoFormula.cost());
		// 添加DHA+LF奶粉
		IFormula dhaLfFormula = new LfFormula(dhaFormula);
		System.out.println("营养成分:" + dhaLfFormula.nutrient() + ",成本(元):" + dhaLfFormula.cost());
		// 添加OPO+LF奶粉
		IFormula opoLfFormula = new LfFormula(opoFormula);
		System.out.println("营养成分:" + opoLfFormula.nutrient() + ",成本(元):" + opoLfFormula.cost());
		// 添加DHA+OPO+LF奶粉
		IFormula dhaOpoLfFormula = new LfFormula(dhaOpoFormula);
		System.out.println("营养成分:" + dhaOpoLfFormula.nutrient() + ",成本(元):" + dhaOpoLfFormula.cost());
	}
}

运行结果:
在这里插入图片描述
可以看出我们只定义了三个添加成分类,就可以全可能描述添加的7种可能,根本思路是使用组合替代继承。

结语

装饰器模式是继承的一种替代模式,其优点是可以动态扩展一个实现类的功能。不仅可以扩展一个类的功能,也可以动态增加功能,动态撤销;装饰类和被装饰类可以独立发展,而不会相互耦合,Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的装饰类(注意装饰没有顺序限制)。

但是如果装饰类过多,虽说比继承好很多,但是问题还是一样的,都会有类(具体装饰类)过多的问题,这些具体装饰类的逻辑将不会非常的清晰,不够直观,容易令人迷惑,使用的时候,可能需要更多的对象来表示继承关系中的一个对象,而且多层装饰的使用变得复杂,在查找问题时,层层嵌套会不容易发现问题所在。

文章如有错误请指正,希望各位看官留下宝贵的意见,学习进步,谢谢。

设计模式是作为解决问题或者设计类层级结构时的一种思维的存在,而不是公式一样的存在

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值