设计模式之装饰器模式

经典咖啡厅问题

小明的公司又接到了新的项目,这次是要做程序的是一家咖啡厅,他们需要把他们的咖啡好好的管理起来,小明了解需求以后,很快就根据咖啡店的情况设计出了第一版咖啡厅程序:
decorate

很快,新的需求来了,由于顾客的增多,有许多客户对咖啡有特殊的要求,如加奶、加糖,巧克力风味的等等,加了不同料的咖啡价格是不一样的。小明很快就着手开始新建更多类,加奶焦糖玛奇朵,巧克力摩卡……但这时的他突然发现有问题:相同的咖啡,因为加料的不同出现了很多的配搭,会导致严重的类剧增。

小明立即请教了主管,并提出了新的解决方案,给Coffee接口增加方法,如setMilk()、hasMilk()等方法来确保某一个咖啡是加了哪些其他调料的,但是一个问题直接就否定了这样设计:如果我要加双倍调料呢?而且如果出现了一种新的不能加任何调料的饮料时,就会违背接口隔离原则,小明陷入了很大的困难之中,这时主管让小明去了解一下装饰器模式,然后再做出新的设计,小明再了解之后,很快做出了新的设计:

decoratetwo

定义

小明用的设计模式就叫装饰者模式:动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

角色

  • 抽象组件(Component)
  • 具体组件(Concrete Component)
  • 装饰(Decorator)
  • 具体装饰角色(Concrete Decorator)

注意:装饰者模式并非针对具体的组件进行装饰

真实世界的装饰者:Java I/O

输入流

这是Java I/O中字节输入流的类图结构,我们发现和咖啡厅的设计几乎是一样的,不同的是InputStream是通过抽象类实现,而非接口。我们把左边的三个类(FileInputStream、 StringBufferInputStream、ByteArrayInputStream,不止这是三个)在装饰者模式中称为组件,而把继承至FilterInputStream的类称之为装饰者,装饰者就是用来装饰组件的。看看I/O内部BufferedInputStream是怎么修饰的呢?我们可以用BufferedInputStream来修饰FileInputStream,如果不用BufferedInputStream修饰,直接使用FileInputStream,在调用read()方法时,我们每读出一个字节,就需要做处理,使用了BufferedInputStream,它会给我们提供一个缓冲区,这个缓冲区可以我们自定义大小,也可以使用BufferedInputStream的默认8兆的大小,缓冲区都作用就是将读取出来的数据缓存起来供我们使用,而不是读取一个字节就要使用。

装饰者模式的优缺点和体现的原则

优点:

  1. 不用继承扩展了类的功能
  2. 装饰者可以叠加使用,创造不同的功能,体现了不针对实现的编程

缺点:

  1. 程序复杂性会增加
  2. 会出现很多小类
  3. 针对抽象组件编程,而非具体组件

原则

  1. 多有组合,少用继承
  2. 开闭原则:对扩展开发,对修改关闭
坚持原创分享,您的支持将鼓励我不断前行!