14

【设计模式】第八篇:喝豆浆就是装饰者模式吗?

 3 years ago
source link: https://segmentfault.com/a/1190000038302243
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

2qmUJr.png!mobile

一 引言

不管是学生,还是上班族,好多人压点起床出门总是常事,楼下小摊小店急匆匆的扒拉几个包子一杯豆浆就冲向了地铁或者公车,就这杯小小的豆浆,讲究那可就大了,什么大豆豆浆,五谷豆浆,黑芝麻豆浆种类繁多,要是还想加点配料那可更是花样百出,喜欢甜的加点蜂蜜,喜欢吃枣的还能加几粒红枣,竟可能的满足你的需要

其实从这一个豆浆的例子就能看出来,豆浆(好几种,大豆、五谷等等)本身是一个现有的产品,而添加蜂蜜也好,红枣也好,都是属于在现有产品上增加新的功能或者美化,这就是我们今天要讲的装饰者模式

进一步说到技术层次上,有时候一些组件核心代码是固定的,但是想不改变原有结构的基础上,进行一定的动态扩展,也是用如此的方式

下面接着用一个豆浆例子的具体代码引入其写法,后面再对其理论进行阐述

二 代码演示

既然是豆浆,那就属于饮品,首先定义一个抽象层面的饮品类

/**
 * 抽象饮品类,被豆浆等子类继承
 */
public abstract class Drink {
    private String name; //饮品名称
    private float price = 0.0f;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    // 计算费用
    public abstract float calcCost();
}

既然有了抽象的饮品,那么豆浆类就可以出现了,别忘了实现父类中计算饮品费用的抽象方法

/**
 * 饮品子类:豆浆类
 */
public class SoyaBeanMilk extends Drink {
    @Override
    public float calcCost() {
        return super.getPrice();
    }
}

而我们有各种豆浆,例如黄豆豆浆,五谷豆浆,黑芝麻豆浆

/**
 * 豆浆子类:黄豆豆浆
 */
public class SoybeansSoyaBeanMilk extends SoyaBeanMilk {
    public SoybeansSoyaBeanMilk() {
        setName("黄豆豆浆");
        setPrice(3.0f);
    }
}
/**
 * 豆浆子类:五谷豆浆
 */
public class GrainSoyaBeanMilk extends SoyaBeanMilk {
    public GrainSoyaBeanMilk() {
        setName("五谷豆浆");
        setPrice(5.0f);
    }
}
/**
 * 豆浆子类:黑芝麻豆浆
 */
public class BlackSesameSoyaBeanMilk extends SoyaBeanMilk {
    public BlackSesameSoyaBeanMilk() {
        setName("黑芝麻豆浆");
        setPrice(6.0f);
    }
}

其实现在,我们已经可以拿到几种口味的豆浆了,但是为了满足需要,我们还需要增加一些因人而异的配料,例如蜂蜜和红枣

首先,创建一个装饰类 Decorator ,其通俗意义就是我们对于所有配料的一个总称,下面会有一些具体的配料子类来继承它

注:这个地方为了重写描述和费用计算的方法,所以要继承 Drink,注意分清楚哪些调用的是父类的,哪些是自己的

/**
 * 装饰类
 */
public class Decorator extends Drink {
    private Drink drink;

    public Decorator(Drink drink) {
        this.drink = drink;
    }

    @Override
    public String getName() {
        return drink.getName() + " + " + super.getName() + "(单价"  + getPrice() + ")";
    }

    @Override
    public float calcCost() {
        return super.getPrice() + drink.calcCost();
    }
}

下面就是具体的配料子类了

/**
 * 配料子类:蜂蜜配料
 */
public class Honey extends Decorator{
    public Honey(Drink drink) {
        super(drink);
        setName("蜂蜜");
        setPrice(2.0f);
    }
}
/**
 * 配料子类:红枣配料
 */
public class RedJujube extends Decorator{
    public RedJujube(Drink drink) {
        super(drink);
        setName("红枣");
        setPrice(3.0f);
    }
}

测试一下

public class Test {
    public static void main(String[] args) {
        // 点1杯大豆豆浆
        Drink drink = new SoybeansSoyaBeanMilk();
        System.out.println("购买:" + drink.getName() + " --- 费用: " + drink.calcCost());

        // 点1杯大豆豆浆 + 1份蜂蜜
        drink = new Honey(drink);
        System.out.println("购买:" + drink.getName() + " --- 费用: " + drink.calcCost());

        // 点1杯大豆豆浆 + 1份蜂蜜 + 1份红枣
        drink = new RedJujube(drink);
        System.out.println("购买:" + drink.getName() + " --- 费用: " + drink.calcCost());

        // 点1杯大豆豆浆 + 1份蜂蜜 + 2份红枣
        drink = new RedJujube(drink);
        System.out.println("购买:" + drink.getName() + " --- 费用: " + drink.calcCost());
    }
}

运行结果:

购买:黄豆豆浆 --- 费用: 3.0

购买:黄豆豆浆 + 蜂蜜(单价2.0) --- 费用: 5.0

购买:黄豆豆浆 + 蜂蜜(单价2.0) + 红枣(单价3.0) --- 费用: 8.0

购买:黄豆豆浆 + 蜂蜜(单价2.0) + 红枣(单价3.0) + 红枣(单价3.0) --- 费用: 11.0

到这里,在基础豆浆种类上面,增加各种配料并且计算总价已经可以完成了

这就是装饰者模式,下面我们来讲讲其理论

三 装饰者模式理论

(一) 定义和理解

定义:装饰模式就是在不改变现有对象结构的情况下,动态地给该对象增加一些职责

就像上面豆浆加配料的例子中,装饰模式就是为已经存在的内容,添加一些更加丰富的内容

还有例如当你的系统需要新的功能的时候,向旧代码中添加一些新的代码,这些新的代码就装饰了原有类的核心职责或主要行为

这些新加入的内容,本质上自只是为了满足一些在特定情况下才会需要的情况,例如并不是所有人都想要加蜂蜜等这些配料,对于这种需求,装饰模式就是一种比较好的解决方案

从上面代码中也可以看出,当你需要在原有内容(豆浆)的基础上添加装饰内容(配料),只需要 把每个要装饰的内容放在一个单独的类中,然后包裹要装饰的对象 ,当需要执行添加配料这样一种特殊行为的时候,就可以有选择的进行装饰,例如添加蜂蜜: drink = new Honey(drink); 就是用蜂蜜包裹了上面 new 出来的饮品 drink (这里代表豆浆)

而且,当你想要添加新种类的豆浆的时候,例如红豆豆浆,只要继承豆浆这个父类,然后就可以享受到添加所有配料的权利

(二) 优缺点

优点:

  • 有效的把类的核心职责和装饰功能区分开了,而且可以去除相关类中重复的装饰逻辑
  • 装饰是继承的有力补充,比继承灵活,动态扩展,即插即用
  • 不同顺序排列组合装饰类可以实现不同效果
  • 遵守开闭原则

缺点:

  • 会增加许多子类,使得程序较复杂

(三) 结构

QVf2ya.png!mobile

  • 抽象构件(Component)角色:定义一个抽象接口(类)可以给这些对象动态的添加指责
  • 具体构件(ConcreteComponent)角色:实现抽象构件,即一个具体的构件
  • 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能,也就是上面配料那个抽象类
  • 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,也就是具体的配料,例如蜂蜜,红枣

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK