基于C++的《Head First设计模式》笔记——装饰者模式

目录

一.专栏简介

二.星巴兹咖啡

三.开放-关闭原则

四.结识装饰者模式

五.用装饰者构造饮料订单

六.装饰者模式的定义

七.装饰我们的饮料

八.样例测试代码

九.装饰者模式的缺点

十.扩展(含最终代码)


一.专栏简介

本专栏是我学习《head first》设计模式的笔记。这本书中是用Java语言为基础的,我将用C++语言重写一遍,并且详细讲述其中的设计模式,涉及是什么,为什么,怎么做,自己的心得等等。希望阅读者在读完我的这个专题后,也能在开发中灵活且正确的使用,或者在面对面试官时,能够自信地说自己熟悉常用设计模式。

在策略模式和观察者模式的学习之后,本章将开始装饰者模式的学习。

二.星巴兹咖啡

书中以星巴兹咖啡为例,这里的星巴兹就是以星巴克为原型的。它是一家扩张速度极快的咖啡店,所以着急更新其下单系统,以匹配他们的饮料供应需求。他们订单系统中的饮料类一开始设计像这样:

根据这个图我们就懂得了饮料类的继承关系,非常的简单。值得一提的是,description在基类Beverage中应该是protected的,这样子类在拿到后可以在构造函数中修改它,成为子类具体饮料自己的描述。

另外,除了咖啡本身,我们还要求各种调料,例如:蒸奶(steamed milk)、豆奶(soy)、摩卡(mocha)或覆盖奶泡。星巴兹会对每个调料收取一点费用,所以需要把这些调料构造进订单系统。这是第一次尝试:

每个cost方法计算咖啡加上订单上其他调料的价格。

What f**k!!!这简直就是class boom(类爆炸)!于我而言,这就是一种可怕的硬编码,后期的维护和扩展将非常麻烦。想想看,增加一种配料或者增加一种咖啡,那都是笛卡尔积数量的增加。违反了封装变化这一设计原则。

以上方法非常之愚蠢,现在我们使用实例变量和继承来跟踪调研。从Beverage基类开始,加上实例变量代表是否加上调料(牛奶、豆奶、摩卡、奶泡......)

类图与介绍如下:

加上子类的类图与介绍如下:

代码如下:

Beverage.h:

cpp 复制代码
#pragma once
#include <iostream>
#include <string>

using namespace std;

class Beverage
{
public:
	Beverage();
	string& getDescription();
	virtual double cost() = 0;

	bool hasMilk();
	void setMilk(bool add);
	bool hasSoy();
	void setSoy(bool add);
	bool hasMocha();
	void setMocha(bool add);
	bool hasWhip();
	void setWhip(bool add);
protected:
	string description;
	bool milk;
	bool soy;
	bool mocha;
	bool whip;
};

class DarkRoast : public Beverage
{
public:
	DarkRoast();
	double cost() override;
private:
};

Beverage.cpp:

cpp 复制代码
#include "Beverage.h"

Beverage::Beverage():
    description("星巴兹饮料"), milk(false), soy(false), mocha(false), whip(false)
{

}

string& Beverage::getDescription()
{
    return description;
}

double Beverage::cost()
{
    return milk ? 0.1 : 0 + soy ? 0.3 : 0 + mocha ? 0.5 : 0 + whip ? 0.2 : 0;
}

bool Beverage::hasMilk()
{
    return milk;
}

void Beverage::setMilk(bool add)
{
    milk = add;
}

bool Beverage::hasSoy()
{
    return soy;
}

void Beverage::setSoy(bool add)
{
    soy = add;
}

bool Beverage::hasMocha()
{
    return mocha;
}

void Beverage::setMocha(bool add)
{
    mocha = add;
}

bool Beverage::hasWhip()
{
    return whip;
}

void Beverage::setWhip(bool add)
{
    whip = add;
}

DarkRoast::DarkRoast()
{
    description = "Most Excellent Dark Roast";
}

double DarkRoast::cost()
{
    return Beverage::cost() + 7.5;
}

这里有个要点,子类不能在构造函数的初始化列表里初始化从父类继承过来的变量,因为这会导致这个成员变量被初始化两次,这是不被允许的。子类若非想在初始化列表里初始化从父类继承过来的成员变量,就调用父类的有参构造函数,而不是默认调用父类的无参或者默认构造函数。

经过这次优化,只有五个类了,确实不错,但是还是有很多潜在的问题:

  • 调料价格改变会迫使我们更改现有的代码。
  • 新的调料会迫使我们添加新的方法,并改变超类中的cost方法。
  • 可能有新的饮料。对于其中一些饮料(冰茶?),调料可能不适合,然而,Tea(茶)子类任将继承像hasWhip()(是否加奶泡)这样的方法。(这就是前面博客说过的继承的缺点:基类的变更会不经意地影响其它子类)
  • 顾客想要双倍摩卡,怎么办?

三.开放-关闭原则

我们在面对最重要的设计原则之一:

我们的目标是允许类容易扩展以扩容新的行为,而不用修改已有代码。达成这个目标,有什么好处?这样的设计可以弹性应对变化,有足够弹性接受新的功能来应对改变的需求。

需要注意的是,我们不能使得我们的代码处处都遵循开闭原则,首先这非常难,其次还会让代码很抽象,增加代码的复杂度。我们只需要将代码中最有可能改变的区域应用开闭原则。

四.结识装饰者模式

我们将从饮料开始并在运行时用调料"装饰"它。例如,如果顾客要一份带摩卡和奶泡的深度烘培,那么我们将:

  • 从DarkRoast(深度烘培)对象开始。
  • 用一个Mocha(摩卡)对象装饰它。
  • 用一个Whip对象装饰它。
  • 调用cost()方法,依靠委托来累加调料价钱。

但如何"装饰"一个对象,委托是怎么来的?提示:把装饰者对象想成"包装者"。我们来看看这如何运作......

五.用装饰者构造饮料订单

咱们直接看书里的图吧,书里说得好为什么不用呢是吧。

现在我们知道了装饰者模式的一些信息:

  • 装饰者有着和所装饰对象同样的超类型。
  • 你可以用一个或多个装饰者包裹一个对象。
  • 鉴于装饰者有着和所装饰对象同样的超类型,在需要原始对象的场合,我们可以传递一个被装饰的对象。
  • 装饰者在委托给所装饰对象之前或之后添加自己的行为,来做剩下的工作。
  • 对象可以在任何时候被装饰,因此我们可以在运行时用任意数量的装饰者动态地装饰对象,只要我们乐意。

六.装饰者模式的定义

装饰者模式动态地将额外责任附加到对象上。对于扩展功能,装饰者提供子类化之外的弹性替代方案。

七.装饰我们的饮料

我们使用装饰者模式来重做星巴兹饮料。

代码如下

Beverage.h:

cpp 复制代码
class Beverage
{
public:
	virtual string getDescription();
	virtual double cost() = 0;
protected:
	string description;
};

class HouseBlend : public Beverage
{
public:
	HouseBlend();
	double cost() override;
};

class DarkRoast : public Beverage
{
public:
	DarkRoast();
	double cost() override;
};

class Espresso : public Beverage
{
public:
	Espresso();
	double cost() override;
};

class Decaf : public Beverage
{
public:
	Decaf();
	double cost() override;
};

class CondimentDecorator : public Beverage
{
public:

protected:
	Beverage* beverage = nullptr;
};

class Milk : public CondimentDecorator
{
public:
	Milk(Beverage* b);
	double cost() override;
	string getDescription() override;
private:
};

class Mocha : public CondimentDecorator
{
public:
	Mocha(Beverage* b);
	double cost() override;
	string getDescription() override;
private:
};

class Soy : public CondimentDecorator
{
public:
	Soy(Beverage* b);
	double cost() override;
	string getDescription() override;
private:
};

class Whip : public CondimentDecorator
{
public:
	Whip(Beverage* b);
	double cost() override;
	string getDescription() override;
private:
};

Beverage.cpp:

cpp 复制代码
string Beverage::getDescription()
{
	return description;
}

HouseBlend::HouseBlend()
{
	description = "HouseBlend饮料";
}

double HouseBlend::cost()
{
	return 8.1;
}

DarkRoast::DarkRoast()
{
	description = "DarkRoast饮料";
}

double DarkRoast::cost()
{
	return 7.7;
}

Espresso::Espresso()
{
	description = "Espresso饮料";
}

double Espresso::cost()
{
	return 6.6;
}

Decaf::Decaf()
{
	description = "Decaf饮料";
}

double Decaf::cost()
{
	return 7.7;
}

Milk::Milk(Beverage* b)
{
	beverage = b;
}

double Milk::cost()
{
	return beverage->cost() + 1.1;
}

string Milk::getDescription()
{
	return beverage->getDescription() + ", milk";
}

Mocha::Mocha(Beverage* b)
{
	beverage = b;
}

double Mocha::cost()
{
	return beverage->cost() + 2.2;
}

string Mocha::getDescription()
{
	return beverage->getDescription() + ", mocha";
}

Soy::Soy(Beverage* b)
{
	beverage = b;
}

double Soy::cost()
{
	return beverage->cost() + 3.3;
}

string Soy::getDescription()
{
	return beverage->getDescription() + ", soy";
}

Whip::Whip(Beverage* b)
{
	beverage = b;
}

double Whip::cost()
{
	return beverage->cost() + 4.4;
}

string Whip::getDescription()
{
	return beverage->getDescription() + ", whip";
}

在代码中,CondimentDecorator也继承了Beverage,但是这里是**使用继承达到类型匹配,但不使用继承来获得行为。**实际上我们还是通过将对象组合在一起获得新的行为。

八.样例测试代码

我们来点一杯双倍摩卡豆奶加牛奶,main.cpp代码如下:

cpp 复制代码
#include "Beverage.h"

int main()
{
	Beverage* dr = new DarkRoast();
	Beverage* m1 = new Mocha(dr);
	Beverage* m2 = new Mocha(m1);
	Beverage* s = new Soy(m2);
	Beverage*  m = new Milk(s);
	cout << m->cost() << endl;

	return 0;
}

运行结果:

调用cost()方法时的流程图:

这就像递归一样,太美妙了。

九.装饰者模式的缺点

  1. 不适用于针对特定类型的代码
  2. 引入了大量的小对象,可能让代码混乱或者难以理解。

十.扩展(含最终代码)

代码如下:

Beverage.h:

cpp 复制代码
#pragma once
#include <iostream>
#include <string>

using namespace std;

class Beverage
{
public:
	enum Size
	{
		TALL,
		GRANDE,
		VENTI
	};
	virtual string getDescription();
	virtual double cost() = 0;
	void setSize(Size sz);
	virtual Size getSize();
protected:
	string description;
	Size size = TALL;
};

class HouseBlend : public Beverage
{
public:
	HouseBlend();
	double cost() override;
};

class DarkRoast : public Beverage
{
public:
	DarkRoast();
	double cost() override;
};

class Espresso : public Beverage
{
public:
	Espresso();
	double cost() override;
};

class Decaf : public Beverage
{
public:
	Decaf();
	double cost() override;
};

class CondimentDecorator : public Beverage
{
public:
	Size getSize() override;
protected:
	Beverage* beverage = nullptr;
};

class Milk : public CondimentDecorator
{
public:
	Milk(Beverage* b);
	double cost() override;
	string getDescription() override;
private:
};

class Mocha : public CondimentDecorator
{
public:
	Mocha(Beverage* b);
	double cost() override;
	string getDescription() override;
private:
};

class Soy : public CondimentDecorator
{
public:
	Soy(Beverage* b);
	double cost() override;
	string getDescription() override;
private:
};

class Whip : public CondimentDecorator
{
public:
	Whip(Beverage* b);
	double cost() override;
	string getDescription() override;
private:
};

Beverage.cpp:

cpp 复制代码
#include "Beverage.h"

string Beverage::getDescription()
{
	return description;
}

void Beverage::setSize(Size sz)
{
	size = sz;
}

Beverage::Size Beverage::getSize()
{
	return size;
}

HouseBlend::HouseBlend()
{
	description = "HouseBlend饮料";
}

double HouseBlend::cost()
{
	return 8.1;
}

DarkRoast::DarkRoast()
{
	description = "DarkRoast饮料";
}

double DarkRoast::cost()
{
	return 7.7;
}

Espresso::Espresso()
{
	description = "Espresso饮料";
}

double Espresso::cost()
{
	return 6.6;
}

Decaf::Decaf()
{
	description = "Decaf饮料";
}

double Decaf::cost()
{
	return 7.7;
}

Milk::Milk(Beverage* b)
{
	beverage = b;
}

double Milk::cost()
{
	double cost = beverage->cost();
	switch (getSize())
	{
	case TALL: cost += 9; break;
	case GRANDE: cost += 7; break;
	case VENTI: cost += 5; break;
	default: break;
	}
	return cost;
}

string Milk::getDescription()
{
	return beverage->getDescription() + ", milk";
}

Mocha::Mocha(Beverage* b)
{
	beverage = b;
}

double Mocha::cost()
{
	double cost = beverage->cost();
	switch (getSize())
	{
	case TALL: cost += 17; break;
	case GRANDE: cost += 15; break;
	case VENTI: cost += 12; break;
	default: break;
	}
	return cost;
}

string Mocha::getDescription()
{
	return beverage->getDescription() + ", mocha";
}

Soy::Soy(Beverage* b)
{
	beverage = b;
}

double Soy::cost()
{
	double cost = beverage->cost();
	switch (getSize())
	{
	case TALL: cost += 22; break;
	case GRANDE: cost += 17; break;
	case VENTI: cost += 12; break;
	default: break;
	}
	return cost;
}

string Soy::getDescription()
{
	return beverage->getDescription() + ", soy";
}

Whip::Whip(Beverage* b)
{
	beverage = b;
}

double Whip::cost()
{
	double cost = beverage->cost();
	switch (getSize())
	{
	case TALL: cost += 20; break;
	case GRANDE: cost += 15; break;
	case VENTI: cost += 10; break;
	default: break;
	}
	return cost;
}

string Whip::getDescription()
{
	return beverage->getDescription() + ", whip";
}

CondimentDecorator::Size CondimentDecorator::getSize()
{
	return beverage->getSize();
}

main.cpp:

cpp 复制代码
#include "Beverage.h"

int main()
{
	Beverage* dr = new DarkRoast();
	dr->setSize(Beverage::GRANDE);
	Beverage* m1 = new Mocha(dr);
	Beverage* m2 = new Mocha(m1);
	Beverage* s = new Soy(m2);
	Beverage* m = new Milk(s);
	cout << m->cost() << endl;
	cout << m->getDescription() << endl;

	return 0;
}

输出结果:

用计算器计算结果,答案正确,测试通过!!!

另外的扩展,想象一个CondimentPrettyPrint装饰者,它解析最后的描述,并打印"Mocha.Whip,Mocha"为"Whip,Double Mocha"。这样我们就得到一个漂亮的输出。注意,getDescription()可以返回一个描述的AttrayList。这样更容易编码。

相关推荐
程序炼丹师1 分钟前
C++ 中的 std::tuple (元组)的使用
开发语言·c++
有一个好名字1 分钟前
力扣-最大连续1的个数III
c++·算法·leetcode
less is more_09309 分钟前
文献学习——极端高温灾害下电缆型配电网韧性提升策略研究
笔记·学习·算法
小芒果_019 分钟前
P8662 [蓝桥杯 2018 省 AB] 全球变暖
c++·算法·蓝桥杯·信息学奥赛
菩提小狗9 分钟前
vulnhub靶场实战系列-1.靶场实战平台介绍|课程笔记|网络安全|
笔记·安全·web安全
闪电麦坤9521 分钟前
多线程:按序打印问题(信号量)
c++·多线程·leecode
sxlishaobin23 分钟前
设计模式之装饰器模式
java·设计模式·装饰器模式
Cephas、25 分钟前
Autosar —— Rte 层
笔记
YJlio36 分钟前
RAMMap 学习笔记(15.2):Processes / Priority / Summary——从“谁在用”和“谁更重要”看物理内存
开发语言·笔记·python·学习·django·pdf·硬件架构
Howrun77736 分钟前
C++11新特性
开发语言·c++