设计模式(十):装饰者模式

Published on 2024-03-11 16:05 in 分类: 博客 with 狂盗一枝梅
分类: 博客

一、装饰者模式定义

装饰模式(Decorator Pattern)是一种比较常见的模式,其定义如下:Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。)

装饰者模式的类图如下所示

装饰者模式通用类图.drawio

Component:抽象构件,Component是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象。在装饰模式中,必然有一个最基本、最核心、最原始的接口或抽象类充当Component抽象构件。

ConcreteComponent:具体构件,ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现,你要装饰的就是它。

Decorator:装饰角色,一般是一个抽象类,做什么用呢?实现接口或者抽象方法,它里面可不一定有抽象的方法呀,在它的属性里必然有一个private变量指向Component抽象构件。

ConcreteDecorator:具体装饰类,你要把你最核心的、最原始的、最基本的东西装饰成其他东西。

只是看定义,真的是非常生涩啊,谁能看的懂啊。。。但是,没关系,看看下面的案例就明白了。

二、装饰者模式案例:来个手抓饼吧

相比我们都是吃过手抓饼(hand cake)的吧,吃手抓饼必须得打个鸡蛋(egg),加根肠(sausage),最后再来一包卫龙辣条(spicy strips)。。哦不,得两包卫龙辣条,手抓饼3块,鸡蛋2块,肠2块,两包卫龙辣条2块,一共是3+2+2+2=9块钱,用代码如何模拟实现这个过程呢?直接看使用装饰者模式如何模拟实现这个过程,类图如下

装饰者模式-手抓饼案例.drawio

什么个意思呢,在装饰者模式中,有个很重要的角色,就是“装饰者”,在上面的图中,HandCake是被装饰者,“配料装饰者”ToppingsDecorator是装饰者,它继承HandCake,同时它内部又维护着一个成员变量HandCake的实例,以此扩展HandCake的功能:Egg、Sausage、SpicyStrips都是它的子类。

我想新增一个手抓饼类型,就继承HandCake就可以了;如果我想新增一个配料,比如鸡柳,那我就继承ToppingsDecorator就可以了。

装饰者模式通过包装对象并动态添加功能,提供了一种灵活的方式来扩展对象的行为,同时遵循开闭原则(Open-Closed Principle)和单一责任原则(Single Responsibility Principle)。

代码实现

1、被装饰者HandCake

public abstract class HandCake {

    private Integer price = 0;

    private String name = "";

    public Integer getPrice() {
        return price;
    }

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

    public String getName() {
        return name;
    }

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

    public abstract Integer cost();
}

2、被装饰者具体实现ShanDongHandCake

public class ShanDongHandCake extends HandCake {

    public ShanDongHandCake() {
        setName("山东手抓饼");
        setPrice(3);
    }

    @Override
    public Integer cost() {
        return super.getPrice();
    }
}

3、装饰者ToppingsDecorator

public class ToppingsDecorator extends HandCake {

    private HandCake handCake;

    public ToppingsDecorator(HandCake handCake) {
        this.handCake = handCake;
    }

    @Override
    public String getName() {
        return handCake.getName() + "-" + this.getName();
    }

    @Override
    public Integer cost() {
        return super.getPrice() + handCake.cost();
    }
}

4、三种配料

public class Egg extends ToppingsDecorator {
    public Egg(HandCake handCake) {
        super(handCake);
        setName("鸡蛋");
        setPrice(2);
    }
}

public class Sausage extends ToppingsDecorator {
    public Sausage(HandCake handCake) {
        super(handCake);
        setName("烤肠");
        setPrice(2);
    }
}

public class SpicyStrips extends ToppingsDecorator{
    public SpicyStrips(HandCake handCake) {
        super(handCake);
        setName("卫龙辣条");
        setPrice(1);
    }
}

5、启动类

public class Main {

    public static void main(String[] args) {
        HandCake order = new ShanDongHandCake();
        System.out.println("一个手抓饼价格:" + order.cost());

        order = new Egg(order);
        System.out.println("加一个鸡蛋的价格:" + order.cost());

        order = new Sausage(order);
        System.out.println("加一根香肠的价格:" + order.cost());

        order = new SpicyStrips(order);
        System.out.println("加一包卫龙的价格:" + order.cost());

        order = new SpicyStrips(order);
        System.out.println("再加一包卫龙的价格:" + order.cost());
    }
}

运行结果:

一个手抓饼价格:3
加一个鸡蛋的价格:5
加一根香肠的价格:7
加一包卫龙的价格:8
再加一包卫龙的价格:9

实际上上面的代码等价于下面一行代码:

HandCake order =  new SpicyStrips(new SpicyStrips(new Sausage(new Egg(new ShanDongHandCake()))));

如果我想再加一个鸡蛋,那就

order = new Egg(order);

调用查询价格的方法cost(),则是递归查询的,因为它们之间是一个聚合的关系

装饰者模式-手抓饼案例-递归查询价格.drawio

三、JAVA I/O框架中的装饰者模式

装饰者模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了,由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现,而如果采用装饰者模式,那么类的数目就会大大减少,性能的重复也可以减至最少,因此装饰者模式是Java I/O库的基本模式

在Java的IO结构中,FilterInputStream 扮演的就是装饰者的角色

装饰者模式-Java IO框架中的装饰者模式.drawio

从类图上来看,很明显在JAVA IO框架中使用了装饰者模式:

InputStream是Component,ByteArrayInputStream和FileInputStream是ConcreteComponent,FilterInputStream是装饰者,即Decorator,DataInputStream、BufferedInputStream以及LineNumberInputStream是ConcreteDecorator。

看看下面的代码:

InputStream in  = new DataInputStream(new FileInputStream(""));
InputStream in  = new BufferedInputStream(new FileInputStream(""));

InputStream in  = new DataInputStream(new ByteArrayInputStream(""));
InputStream in  = new BufferedInputStream(new ByteArrayInputStream(""));

DataInputStream作为装饰者类,可以包装任意InputStream的子类,方便的处理其字节数据,增强了InputStream类的功能;BufferedInputStream作为装饰者类,包装了InputStream类,并提供了缓冲功能。它通过在内部维护一个缓冲区,减少了对底层输入流的直接读取次数,从而提高了读取性能。




END.


#设计模式
目录