Head First 设计模式笔记 3.装饰者模式

文章目录

  • 摘要
  • 一杯咖啡引起的血案
  • 装饰者模式
  • 用装饰者模式点缀咖啡
  • 小结

前后文
1.策略模式
2.观察者模式

摘要

这篇博客简要通过一个订单系统案例,各种调料装饰饮品,最后计算咖啡价格。由此介绍了对扩展开放-对修改关闭的设计原则和装饰者模式的基本概念,它的基础类图,最后用设计者模式将调味料作为装饰者,将饮料作为被装饰者。解决了这一问题并且实现了对应代码。

一杯咖啡引起的血案

叮叮叮,我们万能的小明又接到任务了。这次甲方爸爸的要求是一个咖啡厅的订单系统。咖啡店中,顾客可以加入各种调料,例如,蒸奶(Steam Milk),豆浆(Soy),摩卡(Mocha)或者奶泡。咖啡厅会根据加入的调料收取不同的费用。所以订单系统必须要考虑调料价格。
还是那句话,作为一名优秀的程序员,小明很快想出来了解决方法。他将各种调料设计为实例bool类型变量,表示它们是否加上了对应的调料。这是他的基类Beverage(饮料的意思)图。

Head First 设计模式笔记 3.装饰者模式_第1张图片
图1 基类图
小明利用继承,实现了以下几个类。
Head First 设计模式笔记 3.装饰者模式_第2张图片
图2 各类咖啡图

但是,很快小明发现,写代码很容易,维护这些代码却异常地困难。参考博客

  • 调料价钱的改变会使我们改变现有代码
  • 一旦出现新的调料,我们就需要加上新的方法,并改变超类中的 cost() 方法
  • 以后可能会开发出新饮料。对这些饮料而言(例如:冰茶),某些调料可能并不适合,但是在这个设计方式中,Tea (茶)子类仍然将继承那些不适合的方法,例如:hasWhip() (加奶泡)
  • 万一顾客想要双倍摩卡或咖啡,怎么办?
  • 调料价钱随着具体饮料而改变
  • 饮料基础价钱随着大中小被的不同而改变

此刻,小明面临了最重要的设计原则开放-关闭原则

类应该对扩展开放,对修改关闭

什么意思呢?就是说我们的设计应该使得类容易扩展,在不修改现有代码的情况下,就可以搭配新的行为。两个例子

  • 我们之前学过的观察者模式,在无需修改主题的情况下,就可以增减观察者的个数。
  • 策略模式,我们无需修改鸭子的代码,只需要给它注入不同的翅膀和叫声就行了。这样的设计具有弹性可以应对变化。

装饰者模式

这里小明想到了一个好办法,就是像套娃一样,用一层层的调料去装饰饮料。例如顾客想要一倍加了摩卡和奶泡的烘焙咖啡。要做的就是

  1. 新建一个深培咖啡(DarkRoast)
  2. 用摩卡(Mocha)装饰它
  3. 用奶泡(Whip)装饰它
  4. 调用cost()方法,依赖委托将调料的价格加上去。
    计算价格的过程如图所示
Head First 设计模式笔记 3.装饰者模式_第3张图片
图3 价格计算流程

这就是一个装饰者模式的实例。

装饰者模式动态地将责任添加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案

我们可以从这个实例中发现装饰者模式的特点

  • 装饰者和被装饰者有同样的超类型
  • 可以用一个或者多个装饰者包装一个对象
  • 装饰类可以在所委托的被装饰类的行为之前(或之后),加上自己的行为,以达到特定的目的
  • 装饰者可以在任何时候装饰,所以可以在运行的时候动态地,任意数量的装饰者来装饰对象

下面是装饰者模式的类图

Head First 设计模式笔记 3.装饰者模式_第4张图片
图4 装饰者模式类图

然而,装饰者模式同样是有它的阴暗面的,这像套娃一样一层套一层的会增加代码的复杂度,滋生很多的小类。尤其是在嵌套的装饰者多了之后,理解和调试代码都是一件麻烦事。而且当被装饰者依赖某种类型时,引入装饰者就可能出现状况。

用装饰者模式点缀咖啡

知道了装配这模式的框架,那么让我们用咖啡订单系统套一套。

Head First 设计模式笔记 3.装饰者模式_第5张图片
图4 咖啡系统订单

后面都是代码,不感兴趣的朋友可以直接跳到小结。

让我们来实现Beverage类

public abstract class Beverage {
     
	String description = "Unknown Beverage";
  
	public String getDescription() {
     
		return description;
	}
 
	public abstract double cost();
}

Condiment调料类

public abstract class CondimentDecorator extends Beverage {
     
	Beverage beverage;
	public abstract String getDescription();
}

饮料Espresso代码,其他饮料HouseBlend, DarkRoast, Espresso, Decaf都类似,这里就不照抄了

public class Espresso extends Beverage {
     
  
	public Espresso() {
     
		description = "Espresso";
	}
  
	public double cost() {
     
		return 1.99;
	}
}

调料Mocha代码,其他的调料Milk, Soy, Whip等都类似,这里就不抄写了。


public class Mocha extends CondimentDecorator {
     
	public Mocha(Beverage beverage) {
     
		this.beverage = beverage;
		// 装饰者的构造函数需要被装饰者被赋值
	}
 
	public String getDescription() {
     
		return beverage.getDescription() + ", Mocha";
	}
 
	public double cost() {
     
		return .20 + beverage.cost();
	}
}

让我们写一个测试代码测试一下,这里的代码有些类没有实现。需要实现了才能运行

package headfirst.designpatterns.decorator.starbuzz;

public class StarbuzzCoffee {
     
 
	public static void main(String args[]) {
     
		Beverage beverage = new Espresso();
		System.out.println(beverage.getDescription() 
				+ " $" + beverage.cost());
 		//制造一个DarkRoast对象,并且装饰上两个摩卡,一个奶泡
 		//这样最后就能够返回一杯加上了两份摩卡和一个奶泡的烘焙咖啡的价格
		Beverage beverage2 = new DarkRoast();
		beverage2 = new Mocha(beverage2);
		beverage2 = new Mocha(beverage2);
		beverage2 = new Whip(beverage2);
		System.out.println(beverage2.getDescription() 
				+ " $" + beverage2.cost());
 		
 		//返回一份加了Whip,Mocha,Soy的HouseBlend的价格
		Beverage beverage3 = new HouseBlend();
		beverage3 = new Soy(beverage3);
		beverage3 = new Mocha(beverage3);
		beverage3 = new Whip(beverage3);
		System.out.println(beverage3.getDescription() 
				+ " $" + beverage3.cost());
	}
}

假如读者不想写代码,这里有代码的链接。
咖啡厅代码链接

小结

在这篇博客中,我们又get到了新的设计原则:

扩展开放-对修改关闭

装饰者模式被我们加入了工具箱

装饰者模式动态地将责任添加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案

装饰者模式具有以下特点

  • 由于共属于一个超类,装饰者模式允许我们用无数个装饰者装饰组件。只要我们需要,我们可以在咖啡中加上任意调味品装饰。
  • 装饰类可以在所委托的被装饰类的行为之前(或之后),加上自己的行为,以达到特定的目的。我们就是这样计算出加上调味品的咖啡的价格的
  • 然而,过多的装饰者会导致代码变得很复杂难以理解和调试。请小心使用。

Java.io包中的典型的装饰者模式如下。

Head First 设计模式笔记 3.装饰者模式_第6张图片
图5 典型的装饰者模式

谢谢你的阅读。你们的阅读点赞是我更新最大的动力 (๑◕ܫ←๑)

你可能感兴趣的:(Head,First设计模式,设计模式,java)