设计模式之装饰者设计模式

643 阅读7分钟

前言

装饰者设计模式,是一种比较容易理解的模式之一,顾名思义是一种,将主体功能抽象出来,把修饰主体功能的逻辑单独封装一个类--装饰类。注意我们封装是对变化部分的封装,这里变化主要指的是装饰功能。一个主体功能可以有多种多样的修饰功能。动态的给一个对象添加一些额外的职责,更多的是体现对一个方法增强效果。 现在的人都喜欢喝奶茶,点好奶茶后会让你选择配料,珍珠、椰果、核桃仁。奶茶作为奶茶店的主业务,配料就相当于装饰品,用来修饰奶茶,使得奶茶有不同的种类口感。一杯奶茶的制作是有流程的,首先把奶茶煮好,然后选择什么样的装饰配料,最后一杯奶茶制作完成。

装饰者设计模式

image.png 主业务与修饰业务相互分离解耦,前面的章节中屡次提到,封装变化,不难看出修饰业务是变化的,所以主要针对修改业务进行封装。因此先把奶茶煮好,顾客想要什么样的装饰配料,就加什么样的。 代码来源与生活,试想一下如果你是开奶茶店的,如果不对装饰配料封装,你会是什么情况呢?既然不封装那就是奶茶与配料耦合在一起,那么你需要准备三款奶茶,把不同的配料与奶茶混合到一起,制作三款奶茶半成品,所以你需要三种流水线,但是现在年轻人口味变化这么快,又出现了新配料,芒果奶茶,你难道还要加一个业务流水线,用来做芒果奶茶?随着业务的扩展,你的奶茶店还能够正常运行吗?不难看出耦合在一起,扩展业务成本高,而且不同的流水线都用公共的业务,那就是主业务---煮奶茶。不难发现,如果把主业务煮奶茶抽离出来,把不同的配料专门封装起来,那么业务扩展起来就非常方便,如果出现新口味,那就多封装一个芒果配料逻辑就是了,主体业务又没发现改变,不需要投资任何东西,成本明显降低。

主逻辑

最早做奶茶是只有奶茶没有其他配料,我们创建一个MilkTeaContent类,用来制作奶茶。以CoCo奶茶店为例,创建一个制作奶茶接口 MilkTeaContent ,然后创建 CoCoMilkTeaContent 实体类,用来实现具体的制作奶茶过程。MilkTeaPojo 是产品实例类。

//产品实体类


刚开始一切是很顺利的,但是随着年轻人口味变化,奶茶需要创建,因此CoCo奶茶店,首先推出珍珠奶茶。 因此创建一个私有添加珍珠方法,当煮奶茶逻辑执行完后,执行添加珍珠逻辑。

public class MilkTeaPojo {
       ....
               ....
}

public interface MilkTeaContent {
    MilkTeaPojo makeMilkTea();
}

public class CoCoMilkTeaContent implements MilkTeaContent {
    @Override
    public MilkTeaPojo makeMilkTea() {
    .........
    }
}

class Console {

    public static void main(String[] args) {
        CoCoMilkTeaContent content = new CoCoMilkTeaContent();
        MilkTeaPojo milkTea = content.makeMilkTea()
    }

}

这么看也没什么不妥,但是CoCo奶茶店又推出新品,椰果奶茶。这么看问题就大很多了,因为需要根据客户的口味选择,添加不同的配料,所以甚至要修改接口类型,传入一个 milkTeaType 类型,从而制作对应的奶茶。本来更改历史代码就是不明智的了,随着业务的扩展竟然要更改接口类型,那更是不允许了。所以你应该也看出来,如果制作奶茶主体逻辑与添加配料修饰逻辑耦合到一起,就会出现随着配料装饰逻辑的扩展,主体业务不仅需要频繁修改,还变的越来越臃肿,扩展业务极其复杂。

public class CoCoMilkTeaContent implements MilkTeaContent{
    @Override
    public MilkTeaPojo makeMilkTea(String milkTeaType) {
    //煮奶茶
    .........
    //判断客户选择奶茶类型,添加不同的配料
    if(pearl==milkTeaType)
    {
       this.addPearl(milkTeaPojo)
    }else if(coconut==milkTeaType){
       this.addCoconut(milkTeaPojo)
     }
     return milkTeaPojo;
    }
    //添加珍珠
    private MilkTeaPojo addPearl(MilkTeaPojo milkTeaPojo){
     .......
     return milkTeaPojo;
    }
    
   //添加椰果
   private MilkTeaPojo addCoconut(MilkTeaPojo milkTeaPojo){
   .......
   return milkTeaPojo;
   }

}

class Console {
    public static void main(String[] args) {
        String milkTeaType='珍珠'
        CoCoMilkTeaContent content=new CoCoMilkTeaContent();
        MilkTeaPojo milkTeaPojo=content.makeMilkTea(milkTeaType);
    }

}

有人就会问,主体业务是 makeMilkTea ,这个逻辑是重复的,装饰的私有方法是变化的,能不能用继承来实现扩展,这样就不会涉及主体业务逻辑的修改? 如下所示,创建一个珍珠奶茶类 CoCoPearlMilkTeaContent ,跟椰果奶茶类 CoCoCoconutMilkTeaContent,用户需要哪个就创建哪个类。

public class CoCoPearlMilkTeaContent extends CoCoMilkTeaContent {
    @Override
    public MilkTeaPojo makeMilkTea() {
    //煮奶茶
    MilkTeaPojo milkTeaPojo=super.makeMilkTea();
    this.addPearl(milkTeaPojo)
    return milkTeaPojo;
    }
    //添加珍珠
    public MilkTeaPojo addPearl(MilkTeaPojo milkTeaPojo){
     .......
     return milkTeaPojo;
    }

}

public class CoCoCoconutMilkTeaContent extends CoCoMilkTeaContent {
    @Override
    public MilkTeaPojo makeMilkTea() {
    //煮奶茶
    MilkTeaPojo milkTeaPojo=super.makeMilkTea();
    return this.addCoconut(milkTeaPojo)
    }
      //添加椰果
   private MilkTeaPojo addCoconut(MilkTeaPojo milkTeaPojo){
   .......
   }

}
class Console{
 public static void main(String[] args) {
    CoCoMilkTeaContent contentA=new CoCoPearlMilkTeaContent();
    MilkTeaPojo milkTeaA=contentA.makeMilkTea();
    CoCoMilkTeaContent contentB=new CoCoCoconutMilkTeaContent();
    MilkTeaPojo milkTeaB=contentB.makeMilkTea();
  }
}

实际上继承并没有解决根本问题,只是主体业务逻辑不会再被修改了,整体来看主体业务依旧跟装饰逻辑耦合在一起,为什么这么说呢?如果客户要求,奶茶里面放珍珠同时也放椰果,此刻类改怎么扩展?难不成还要创建一个两者都放的类?要知道排列组合可以有很多种结果,那么后面随着业务的扩展,你将会面临类爆炸似的增长,这也是开发过程中需要避免的,因为需要为不同的配料与奶茶创建不同的类,就导致了配料与主体业务的耦合,导致了这个问题。所以说继承并没有解决配料业务扩展问题。要从根本上解决问题,就要对煮奶茶主体业务逻辑与装饰业务配料逻辑分别进行单独封装。


public interface MilkTeaContent {
    milkTeaA makeMilkTea();
}

public class CoCoMilkTeaContent implements MilkTeaContent {
    @Override
    public MilkTeaPojo makeMilkTea() {
    .........
     return milkTeaPojo;
    }
}

对配料进行单独封装,创建一个Decorator 抽象类,并实现MilkTeaContent接口。

public abstract class Decorator implements MilkTeaContent {
    MilkTeaContent content;

    public abstract MilkTeaPojo makeMilkTea();

    public void setContent(MilkTeaContent content) {
        this.content = content;
    }
}

public class PearlDecorator implements Decorator {
    @Override
    public MilkTeaPojo makeMilkTea() {
     MilkTeaPojo milkTea=this.content.makeMilkTea();
     return this.addPearl(milkTea)
    }
    //添加珍珠
    private MilkTeaPojo addPearl(MilkTeaPojo milkTeaPojo){
     .......
     return milkTeaPojo;
    }
}

public class CoconutDecorator implements Decorator {
    @Override
    public MilkTeaPojo makeMilkTea() {
     MilkTeaPojo milkTea=this.content.makeMilkTea();
     return this.addCoconut(milkTea)
    }
    //添加椰果
   private MilkTeaPojo addCoconut(MilkTeaPojo milkTeaPojo){
   .......
    return milkTeaPojo;
   }
}

控制台代码如下

class Console {
 public static void main(String[] args) {
    MilkTeaContent content = new CoCoMilkTeaContent();
    Decorator pearlDecorator=new PearlDecorator();
    pearlDecorator.setContent(content);
    pearlDecorator.makeMilkTea();
    }
}

UML 类图如下,主体奶茶逻辑与装饰配料类都实现了 MilkTeaContent 接口,这就使得具体主体奶茶逻辑类 CoCoMilkTeaContent 不需要关心和认识装饰配料类,两者完全解耦,并且能够通过不同的装饰配料类来扩展主体奶茶逻辑。后期即使设计到装饰配料的业务扩展,也变的非常容易,再实现一个装饰类即可。如果说奶茶店又扩展了一个核桃奶茶,那么是不是只要在实现一个核桃配料的装饰类即可?

diagram-11705165096326516291.png 还记得继承解决业务扩展遇到的问题了吗?就是如果遇到混合配料,用户要求奶茶中既要加珍珠也要加椰果。如下控制台代码,只需要嵌套传参即可,是不是非常的神奇?其实也不是,也在意料之中。这就是继承无法解决的扩展问题,当主体逻辑与装饰逻辑完全解耦后,迎刃而解。

class Console {
 public static void main(String[] args) {
    MilkTeaContent content = new CoCoMilkTeaContent();
    Decorator pearlDecorator=new PearlDecorator();
    pearlDecorator.setContent(content);
    
    Decorator coconutDecorator=new CoconutDecorator();
    coconutDecorator.setContent(pearlDecorator);
    
    coconutDecorator.makeMilkTea();
    }
}

小结

对装饰者设计模式的理解核心,在于区分主体与配件,主体是不变的,配件是经常更新换代的,这是一个对业务场景抽象的理解。不过笔者觉得装饰设计模式的点睛之笔,在于装饰类,也实现了主体类的接口。这就能够实现在不改变主体逻辑的情况下,对主体逻辑行为进行扩展。此刻再理解一下,装饰模式的定义:动态的给一个对象添加一些额外的职责,更多的是体现对一个方法增强效果。 理解下面的两个问题,你会更懂这个模式

1、如何动态添加额外职责?

2、如何体现是对方法增强?

这里写下笔者的理解,欢迎一起讨论,留下你读完后的理解

1、通过抽象实现不同的装饰类

2、继承或实现主体方法接口