在介绍装饰器模式之前,先来看看在一般的开发生产中,对于已有功能新需求的实现,往往一开始想到的方法:一种是直接修改已有功能的代码;一种是使用继承方式。第一种缺点很明显,破坏方法稳定性、复杂场景下导致方法代码冗长,维护起来十分麻烦等,对于第二种,在场景简单的情况下可以适当采用,但场景复杂起来,子类的数量会增长的十分迅速,这时候就可以考虑使用装饰器模式优化代码,下面以生活中的一个场景加以说明。
场景
目前国内奶粉市场十分庞大,国家也对奶粉安全加强监管,不仅国内品牌,国外品牌也针对中国市场推出了各自的国行产品。家长们肯定都经历过给宝宝选奶粉的情况,大体上关注奶粉的奶源地、配方、营养成分等关键指标,这里为简化情况,暂时只关注营养成分一项。国行奶粉的营养成分目前都要满足国家标准,在此之外可以选择加入其它营养成分,比如: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也不用知道具体的装饰类(注意装饰没有顺序限制)。
但是如果装饰类过多,虽说比继承好很多,但是问题还是一样的,都会有类(具体装饰类)过多的问题,这些具体装饰类的逻辑将不会非常的清晰,不够直观,容易令人迷惑,使用的时候,可能需要更多的对象来表示继承关系中的一个对象,而且多层装饰的使用变得复杂,在查找问题时,层层嵌套会不容易发现问题所在。
文章如有错误请指正,希望各位看官留下宝贵的意见,学习进步,谢谢。
设计模式是作为解决问题或者设计类层级结构时的一种思维的存在,而不是公式一样的存在