【浅谈设计模式】(8):装饰者模式 --套娃+“手抓饼案例”快速入门

1,050 阅读6分钟

这是我参与更文挑战的第30天,活动详情查看: 更文挑战

前言

今天我们开始进入结构型模式的第三种----装饰者模式的学习。

一、装饰者模式入门

1.1 概述

装饰者模式全称Decorator Pattern。Decorator 是(房屋的)油漆匠的含义,油漆匠的工作就是将墙面粉刷成想要的样子,例如刚开始房屋被刷成白色,房屋主人觉得绿色更好看,那么油漆匠又可以在白色墙面上在刷上一层绿漆,一层层的包装上去。装饰者模式中也有这种思想,一层层包装上去,简称“套娃”。套娃其实是俄罗斯的特产木制玩具,由多个一样图案的空心木娃娃一个套一个组成,一般在六个以上。

这里是关于这个词,我们了解的装饰者模式的部分思想,记住这个思想,下面我们正式对该模式进行学习。

3b643dc0d1cecfb364b291d8b4bfb6d4

引用百度百科介绍:

装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

看完这个,我们联想套娃就很好理解了,我们通过多次套娃,并没有改变里面的娃娃,就能实现动态的扩展一个对象的功能。

1.2 案例引入

知道装饰者模式就是套娃之后,那它与继承有什么区别呢?

这两者都能在原来对象的基础上,丰富额外的功能,但很大的一点在于,装饰者模式它提供了比继承更灵活的解决方案,在一些场景之下,继承者往往为了实现一个简单的额外功能,而创作了过多的子类,导致项目非常臃肿,同时,扩展性也不好。

例如 手抓饼的配料,往往有很多选项,例如下图这样

image-20210630145955294

如果用继承来实现,一个手抓饼类,我们可能有 鸡蛋手抓饼类、热狗手抓饼类、黄瓜手抓饼类、鸡蛋热狗手抓饼类等各种你能想到的组合,造成所谓 “类爆炸”。

image-20210630151558934

不过,我们可以对这个设计进行改造,可以在pancake 类里面加上方法,判断是否具备热狗、是否具备鸡蛋、是否具备肉等方法。就不需要写这么多的子类了,但是呢,这又会引出另一件事,我们每次新增配类的时候,就会去修改这个pancake 类的方法和属性,违反了之前我们说的设计原则:类应该对扩展开放,对修改关闭。

好的 ,在看到这个问题带来的困难之后,我们需要引出今天的主角-----装饰者模式。来帮我们化解这个问题。

image-20210630152430943

没错,就是这样,并且可以改变套娃的顺序,我可以先套热狗,再套鸡蛋,也可以先套鸡蛋,再套热狗。

1.3 装饰者模式结构

装饰者模式中的角色如下:

  • 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

1.4 使用场景

  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。

    不能采用继承的情况主要有两类:

    • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
    • 第二类是因为类定义不能继承(如final类)
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

二、案例讲解

好,前置知识已经了解的差不多了,接下来,我们开始对这个场景进行 demo 还原。

image-20210630164236406

饼 接口 ----(抽象构件角色)

public abstract class Pancake {
    String desc = "饼";
    public String getDesc(){
        return desc;
    }
    public abstract double cost();
}

手抓饼 ----(具体构件角色)

 **/
public class HandPancke extends Pancake{
    public HandPancke(){
        desc = "原味手抓饼";
    }
    public double cost() {
        return 3.0;
    }
}

山东杂粮煎饼 ----(具体构件角色)

public class ShandongPancake extends Pancake{

    public ShandongPancake() {
        this.desc = "山东杂粮煎饼" ;
    }

    public double cost() {
        return 4.0;
    }
}

配料类 ----(抽象装饰角色)

public abstract class Component extends Pancake{
    Pancake pancake;
   public abstract String getDesc();
}

鸡蛋配料----(具体装饰角色)

public class Egg extends Component {

    public Egg(Pancake pancake){
        this.pancake=pancake;
    }

    public String getDesc() {
        return "鸡蛋+"+pancake.getDesc();
    }

    public double cost() {
        return pancake.cost()+1.5;
    }
}

热狗配料 ---- (具体装饰角色)

public class HotDog extends Component {

    public HotDog (Pancake pancake){
        this.pancake = pancake;
    }

    public String getDesc() {
        return "热狗+"+pancake.getDesc();
    }

    public double cost() {
        return pancake.cost()+1.0;
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        // 1、来一份手抓饼
        HandPancke handPancke = new HandPancke();
        System.out.println(handPancke.getDesc()+" "+handPancke.cost() + "元");
        //2、来一份山东杂粮煎饼
        System.out.println(new ShandongPancake().getDesc()+""+new ShandongPancake().cost()+"元");

        System.out.println("====");
        //3、来一份鸡蛋杂粮煎饼
        Pancake pancake = new ShandongPancake();
        pancake = new Egg(pancake);
        System.out.println(pancake.getDesc()+" " + pancake.cost() + "元");

        //4、来一份鸡蛋、热狗手抓饼
        Pancake handPancke1 = new HandPancke();
        handPancke1 = new Egg(handPancke1);
        handPancke1 = new HotDog(handPancke1);
        handPancke1 = new HotDog(handPancke1);
        handPancke1 = new Egg(handPancke1) ;
        System.out.println(handPancke1.getDesc()+ " "+ handPancke1.cost() + "元");
    }
}

输出结果:

image-20210630164331823

三、总结

3.1 装饰者模式和继承区别

  • 饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。

  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

3.2 装饰者模式和代理模式区别

  • 相同地方:
    • 1、都需要实现目标类相同的业务接口
    • 2、在两个类中都要声明目标对象
    • 3、都可以在不修改目标类的前提下增强目标方法
  • 不同地方:
    • 1、目的不同:装饰者是为了增强目标对象,代理是为了保护和隐藏目标对象
    • 2、对象构件不同:装饰者通过构造方法传递,静态代理在代理类内部创建。

3.3 最后

装饰者模式就是套娃模式,以后看到这个模式就可以联想到本文的套娃图片 和 手抓饼案例就能回顾起来了,设计模式本身在应用中是有一定难度的,需要一定的业务经验积累,如果哪一天你在哪个底层源码中突然领略到哪个你看过的模式,就值了,如果哪一天,你在面对业务捉急的时候,突然把脑门一拍,哎,要不试试装饰者模式,那你可能就顿悟了,希望那一天离你更近一点。

我是潇雷,如果你觉得本文对你有帮助或启发,帮忙点个赞,这将是我创作的最好动力!