【设计模式】 模式PK:装饰模式VS适配器模式

时间:2023-01-05 22:00:55

1、概述

装饰模式和适配器模式在通用类图上没有太多的相似点,差别比较大,但是它们的功能有相似的地方:都是包装作用,都是通过委托方式实现其功能。不同点是:装饰模式包装的是自己的兄弟类,隶属于同一个家族(相同接口或父类),适配器模式则修饰非血缘关系类,把一个非本家族的对象伪装成本家族的对象,注意是伪装,因此它的本质还是非相同接口的对象。

大家都应该听过丑小鸭的故事吧,我们今天就用这两种模式分别讲述丑小鸭的故事。话说鸭妈妈有5个孩子,其中4个孩子都是黄白相间的颜色,而最小的那只也就是叫做丑小鸭的那只,是纯白色的,与兄弟姐妹不相同,在遭受了诸多的嘲讽和讥笑后,最终丑小鸭变成了一只美丽的天鹅。那我们如何用两种不同模式来描述这一故事呢?

2、装饰模式描述丑小鸭

2.1 类图

用装饰模式来描述丑小鸭,首先就要肯定丑小鸭是一只天鹅,只是因为她小或者是鸭妈妈的无知才没有被认出是天鹅,经过一段时间后,它逐步变成一个漂亮、自信、优美的白天鹅。根据分析我们可以这样设计,先设计一个丑小鸭,然后根据时间先后来进行不同的美化处理,怎么美化呢?先长出漂亮的羽毛,然后逐步展现出异于鸭子的不同行为,如飞行,最终在具备了所有的行为后,它就成为一只纯粹的白天鹅了。类图比较简单,非常标准的装饰模式。

【设计模式】 模式PK:装饰模式VS适配器模式

2.2 代码

2.2.1 天鹅接口

我们按照故事的情节发展一步一步地实现程序。初期的时候,丑小鸭表现得很另类,叫声不同,外形不同,致使周围的亲戚、朋友都对她鄙视,那我们来建立这个过程,由于丑小鸭的本质就是一个天鹅,我们就先生成一个天鹅的接口。

class CISwan
{
public:
CISwan(){};
~CISwan(){};

//天鹅会飞
virtual void mvFly() = 0;
//天鹅会叫
virtual void mvCry() = 0;
//天鹅都有漂亮的外表
virtual void mvDesAppaearance() = 0;
};

2.2.2 丑小鸭

我们定义了天鹅的行为,都会飞行、会叫,并且可以描述她们漂亮的外表。丑小鸭是一只白天鹅,是"is-a"的关系,也就是需要实现这个接口了。

class CUglyDuckling : public CISwan
{
public:
CUglyDuckling(){};
~CUglyDuckling(){};

//丑小鸭的叫声
void mvCry() { cout << "叫声是克噜——克噜——克噜" << endl; };

//丑小鸭的外形
void mvDesAppaearance() { cout << "外形是脏兮兮的白色, 毛茸茸的大脑袋" << endl; }

//丑小鸭还比较小,不能飞
void mvFly(){ cout << "不能飞行" << endl; };
};

2.2.3 抽象装饰类

丑小鸭具备了天鹅的所有行为和属性,因为她本来就是一只白天鹅,只是因为她太小了还不能飞行,也不能照顾自己,所以丑丑的,在经过长时间的流浪生活后,丑小鸭长大了。终于有一天,她发现自己竟然变成了一只美丽的白天鹅,有着漂亮、洁白的羽毛,而且还可以飞行,这完全是一种升华行为。我们来看看她的行为(飞行)和属性(外形)是如何加强的。

class CDecorator : public CISwan
{
public:
CDecorator(CISwan
*opSwan){ mopSwan = opSwan; };
~CDecorator(){};

virtual void mvCry() { mopSwan->mvCry(); }
virtual void mvFly() { mopSwan->mvFly(); }
virtual void mvDesAppaearance() { mopSwan->mvDesAppaearance(); }

private:
CISwan
*mopSwan;
};

2.2.4 外形装饰

这是一个非常简单的代理模式。我们再来看丑小鸭是如何开始变得美丽的,变化是由外及里的,有了漂亮的外表才有内心的实质变化。

class CBeautifyAppearance : public CDecorator
{
public:
CBeautifyAppearance(CISwan
*opSwan) : CDecorator(opSwan) {};

// 外表美化处理
void mvDesAppaearance() override { cout << "外表是纯白色的,非常惹人喜爱!" << endl; };
};

2.2.5 行为装饰

丑小鸭最后发现自己还能飞行,这是一个行为突破,是对原有行为“不会飞行”的一种强化。

class CStrongBehavior : public CDecorator
{
public:
CStrongBehavior(CISwan
*opSwan) : CDecorator(opSwan) {};
~CStrongBehavior(){};

// 会飞行了
void mvFly() override { cout << "会飞行了!" << endl; };
};

2.2.6 场景调用

所有的故事元素我们都具备了,就等有人来讲故事了。

int main()
{
//很久很久以前, 这里有一个丑陋的小鸭子
cout << "===很久很久以前, 这里有一只丑陋的小鸭子===" << endl;

CISwan
*op_duck = new CUglyDuckling;
//展示一下小鸭子
op_duck->mvDesAppaearance();
op_duck
->mvCry();
op_duck
->mvFly();

cout
<< "===小鸭子终于发现自己是一只天鹅====" << endl;
//首先外形变化了
op_duck = new CBeautifyAppearance(op_duck);
//其次行为也发生了改变
op_duck = new CStrongBehavior(op_duck);
//虽然还是叫丑小鸭, 但是已经发生了很大变化
op_duck->mvDesAppaearance();
op_duck
->mvCry();
op_duck
->mvFly();

return 0;
}

2.2.7 执行结果

【设计模式】 模式PK:装饰模式VS适配器模式

使用装饰模式描述丑小鸭蜕变的过程是如此简单,它关注了对象功能的强化,是对原始对象的行为和属性的修正和加强,把原本被人歧视、冷落的丑小鸭通过两次强化处理最终转变为受人喜爱、羡慕的白天鹅。

3、适配器模式实现丑小鸭

3.1 类图

采用适配器模式实现丑小鸭变成白天鹅的过程要从鸭妈妈的角度来分析,鸭妈妈有5个孩子,它认为这5个孩子都是她的后代,都是鸭类,但是实际上是有一只(也就是丑小鸭)不是真正的鸭类,她是一只小白天鹅,因为太小,差别太细微,很难分辨,导致鸭妈妈认为她是一只鸭子,从鸭子的审美观来看,丑小鸭是丑陋的。通过分析,我们要做的就是要设计两个对象:鸭和天鹅,然后鸭妈妈把一只天鹅看成了小鸭子,最终时间到来的时候丑小鸭变成了白天鹅。

【设计模式】 模式PK:装饰模式VS适配器模式

类图非常简单,我们定义了两个接口:鸭类接口和天鹅类接口,然后建立了一个适配器UglyDuckling,把一只白天鹅封装成了小鸭子。

3.2 代码

3.2.1 鸭类接口

class CIDuck
{
public:
CIDuck(){};
~CIDuck(){};

//会叫
virtual void mvCry() = 0;

//鸭子的外形
virtual void mvDesAppearance() = 0;

//描述鸭子的其他行为
virtual void mvDesBehavior() = 0;
};

鸭类有3个行为,一个是鸭会叫,一个是外形描述,还有一个是综合性的其他行为描述,例如会游泳等。

3.2.2 小鸭子

class CDuckling : public CIDuck
{
public:
CDuckling(){};
~CDuckling(){};

void mvCry() { cout << "叫声是嘎——嘎——嘎" << endl; };
void mvDesAppearance() { cout << "外形是黄白相间,嘴长" << endl; };

// 鸭子的其他行为, 如游泳
void mvDesBehavior() { cout << "会游泳" << endl; };
};

3.2.3 白天鹅

4只正宗的小鸭子形象已经清晰地定义出来了。鸭妈妈还有一个孩子,就是另类的丑小鸭,她实际是一只白天鹅。我们先定义出白天鹅。

class CISwan
{
public:
CISwan(){};
~CISwan(){};

//天鹅会飞
virtual void mvFly() = 0;
//天鹅会叫
virtual void mvCry() = 0;
//天鹅都有漂亮的外表
virtual void mvDesAppaearance() = 0;
};

class CWhiteSwan : public CISwan
{
public:
CWhiteSwan(){};
~CWhiteSwan(){};

//白天鹅的叫声
void mvCry() { cout << "叫声是克噜——克噜——克噜" << endl; };

//白天鹅的外形
void mvDesAppaearance() { cout << "外形是纯白色,惹人喜爱" << endl; }

//天鹅是能够飞行的
void mvFly(){ cout << "能够飞行" << endl; };
};

3.2.4 当成鸭子的白天鹅

但是,鸭妈妈却不认为自己这个另类的孩子是白天鹅,它从自己的观点出发,认为她很丑陋,有碍自己的脸面,于是驱赶她——鸭妈妈把这只小天鹅误认为一只鸭。

class CUglyDuckling : public CWhiteSwan, public CIDuck
{
public:
CUglyDuckling(){};
~CUglyDuckling(){};

void mvCry() { CWhiteSwan::mvCry(); };
void mvDesAppearance() { CWhiteSwan::mvDesAppaearance(); };
void mvDesBehavior()
{
cout
<< "会游泳!" << endl;
CWhiteSwan::mvFly();
}
};

3.2.5 调用

天鹅被看成了鸭子,有点暴殄天物的感觉。我们再来创建一个场景类来描述这一场景。

int main()
{
//鸭妈妈有5个孩子, 其中4个都是一个模样
cout << "===妈妈有五个孩子, 其中四个模样是这样的: ===" << endl;
CIDuck
*op_duck = new CDuckling();
op_duck
->mvCry();
op_duck
->mvDesAppearance();
op_duck
->mvDesBehavior();
cout
<< "===一只独特的小鸭子, 模样是这样的: ===" << endl;
CIDuck
*op_ugly_duck = new CUglyDuckling;
op_ugly_duck
->mvCry();
op_ugly_duck
->mvDesAppearance();
op_ugly_duck
->mvDesBehavior();

return 0;
}

3.2.6 执行结果

【设计模式】 模式PK:装饰模式VS适配器模式

小天鹅被认为是一只丑陋的小鸭子...采用适配器模式讲述丑小鸭的故事,我们首先观察到的是鸭与天鹅的不同点,建立了不同的接口以实现不同的物种,然后在需要的时候(根据故事情节)把一个物种伪装成另外一个物种,实现不同物种的相同处理过程,这就是适配器模式的设计意图。

 4、总结

我们用两个模式实现了丑小鸭的美丽蜕变。我们发现:这两个模式有较多的不同点。

● 意图不同

装饰模式的意图是加强对象的功能,例子中就是把一个怯弱的小天鹅强化成了一个美丽、自信的白天鹅,它不改变类的行为和属性,只是增加(当然了,减弱类的功能也是可能存在的)功能,使美丽更加美丽,强壮更加强壮,安全更加安全;而适配器模式关注的则是转化,它的主要意图是两个不同对象之间的转化,它可以把一个天鹅转化为一个小鸭子看待,也可以把一只小鸭子看成是一只天鹅,它关注转换。

● 施与对象不同

装饰模式装饰的对象必须是自己的同宗,也就是相同的接口或父类,只要在具有相同的属性和行为的情况下,才能比较行为是增加还是减弱;适配器模式则必须是两个不同的对象,因为它着重于转换,只有两个不同的对象才有转换的必要,如果是相同对象还转换什么?!

● 场景不同

装饰模式在任何时候都可以使用,只要是想增强类的功能,而适配器模式则是一个补救模式,一般出现在系统成熟或已经构建完毕的项目中,作为一个紧急处理手段采用。

● 扩展性不同

装饰模式很容易扩展!今天不用这个修饰,好,去掉;明天想再使用,好,加上。这都没有问题。而且装饰类可以继续扩展下去;但是适配器模式就不同了,它在两个不同对象之间架起了一座沟通的桥梁,建立容易,去掉就比较困难了,需要从系统整体考虑是否能够撤销。