配套视频课程已更新完毕,大家可通过以下两种方式观看视频讲解:

关注公众号:爱编程的大丙,或者进入大丙课堂学习。


1. 马歇尔·D·蒂奇

在海贼世界中,马歇尔·D·蒂奇绰号黑胡子,他是黑胡子海贼团的提督、新世界四皇之一,自然系暗暗果实和超人系震震果实能力者,据说他还可以吃下第三个恶魔果实(就目前剧情而言尚未盖棺定论)。

对于黑胡子来说,它拥有的恶魔果实能力并不是与生俱来的,也就是后天获得,恶魔果实能力是对黑胡子原有实力的加成。黑胡子获得的这种恶魔果实能力和设计模式中的装饰模式差不多,都是动态的给一个对象绑定额外的属性(比如:能力、职责、功能等)。

关于装饰模式也可以称之为封装模式,所谓的封装就是在原有行为之上进行拓展,并不会改变该行为,看下面的例子:

  1. 在进行网络通信的时候,数据是基于IOS七层或四层网络模型(某些层合并之后就是四层模型)进行传输,通过下图可得知从应用层到物理层,数据每向下走一层就会被封装一层,最后将封装好的数据以比特流的方式发送给接收端。封装之后数据只是变得更复杂了, 并没有改变它是数据的本质。

在这里插入图片描述

  1. A端和B端进行网络通信,默认数据是在网络环境中裸奔的,如果想要对数据进行装饰(也就是封装)就可以在发送数据之前对其加密,接收端收到数据之后对其解密。加解密是对数据的装饰,但是没有改变数据的本质。
  2. 平时都是穿长衣长裤,疫情来袭之后穿上了防护服。防护服是对人的装饰,没有改变本体是人的本质。

上述例子中都是对原有行为进行了拓展,但是并没有改变原有行为,就好比饿了去煮面条,为了使其更美味最终会对其进行装饰,做成打卤面、炸酱面、热干面、油泼面等,不论怎么处理最终得到的还是面条,最终不可能得到一锅酱牛肉,大方向是不会变的。

2. 解构黑胡子

从概念上对装饰模式有了一定的了解之后,继续分析黑胡子的个人战斗力,根据文章开头对他的介绍,可以知道他的能力来自三个不同的方向:

  1. 与生俱来加上后天努力练就的本领
  2. 来自自然系·暗暗果实的能力
  3. 来自超人系·震震果实的能力

所以这两个恶魔果实的能力就是对黑胡子个人战力的装饰(加成)。另外,还需要明确一点,对于恶魔果实来说不是只有黑胡子能吃,谁吃了都会拥有对应的果实能力,这样这个人的战斗力也就提升了。

2.1 战魂

人自身是拥有战斗力的,而恶魔果实又可以给人附加战斗力,所以我们就可以定义一个战士的抽象类(这个抽象类不能被实例化)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 战士的抽象类
class Soldier
{
public:
Soldier() {}
Soldier(string name) : m_name(name) {}
string getName()
{
return m_name;
};
virtual void fight() = 0;
virtual ~Soldier() {}
protected:
string m_name = string();
};

有了这个抽象类就可以对某个人,或者某个恶魔果实的战力进行具体的实现,也就是说它们需要继承这个抽象类。

  • 所有的战士都可以战斗 – fight()
  • 所有的战士都有自己的名字
    • 设置名字 – 构造函数Soldier(string name)
    • 获取名字 – string getName()

2.2 黑胡子

上面的战士是一个抽象类,如果想要对它进行实例化就需要写一个子类继承这个抽象类,下面我们定义一个黑胡子类:

1
2
3
4
5
6
7
8
9
10
// 黑胡子(Marshall·D·Teach)
class Teach : public Soldier
{
public:
using Soldier::Soldier;
void fight() override
{
cout << m_name << "依靠惊人的力量和高超的体术战斗..." << endl;
}
};

在黑胡子类中主要是重写了从父类继承的纯虚函数,这样黑胡子这个类就可以被实例化,得到对应的黑胡子对象了。

2.3 附魔

如果黑胡子想要让自己的实力再提升一个层次,就需要得到外部力量的辅助,可行的方案就是吃恶魔果实,这样就相当于给自己附魔了。恶魔果实的作用是对战士的战力进行装饰,使其战力得到大大的提升,所以恶魔果实类也可以先继承战士这个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 抽象的恶魔果实
class DevilFruit : public Soldier
{
public:
// 指定要给哪个人吃恶魔果实 -- 附魔
void enchantment(Soldier* soldier)
{
m_human = soldier;
m_name = m_human->getName();
}
virtual ~DevilFruit() {}
protected:
Soldier* m_human = nullptr;
};

上面的恶魔果实类DevilFruit继承了战士类Soldier之后还是一个抽象类,关于这个类有以下几点需要说明:

  1. DevilFruit 类中没有重写父类Soldier的纯虚函数fight(),所以它还是抽象类
  2. 恶魔果实有很多种类,每种恶魔果实能力不同,所以战斗方式也不同,因此需要在恶魔果实的子类中根据每种果实能力去重写作战函数fight()的行为。
  3. 恶魔果实DevilFruit 类的作用是给某个Soldier的子类对象附魔,所以在类内部提供了一个附魔函数enchantment(Soldier* soldier),参数就是即将要得到恶魔果实能力的那个战士。

2.4 群魔乱舞

黑胡子目前一共吃下了两颗恶魔果实:自然系暗暗果实和超人系震震果实,所以需先定义两个恶魔果实的子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 暗暗果实
class DarkFruit : public DevilFruit
{
public:
void fight() override
{
m_human->fight();
// 使用当前恶魔果实的能力
cout << m_human->getName()
<< "吃了暗暗果实, 可以拥有黑洞一样的无限吸引力..." << endl;
warning();
}
private:
void warning()
{
cout << m_human->getName()
<< "你要注意: 吃了暗暗果实, 身体元素化之后不能躲避攻击,会吸收所有伤害!" << endl;
}
};

// 震震果实
class QuakeFruit : public DevilFruit
{
public:
void fight() override
{
m_human->fight();
cout << m_human->getName()
<< "吃了震震果实, 可以在任意空间引发震动, 摧毁目标...!" << endl;
}
};

关于这两个恶魔果实子类需要说明以下几点:

  1. 在重写父类的fight()函数的时候,用当前恶魔果实能力和战士的自身能力进行了加成,调用了战士对象的作战函数 m_human->fight(),在原有基础上提升了其战斗力。
  2. 在两个恶魔果实子类中,可以根据实际需要定义类独有的方法,比如:DarkFruit 类中有 warning() 方法,QuakeFruit 类中却没有
  3. 再次强调,这两个子类都继承了父类的附魔函数enchantment(Soldier* soldier),这样就可以完成对战士战力的加成(装饰)了。

假设黑胡子确实是可以吃下第三个恶魔果实,并且发现了一颗神奇的超人系恶魔果实大饼果实,可以将身边的一切物体变成大饼,帮助自己和队友快速回血。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 大饼果实
class PieFruit : public DevilFruit
{
public:
void fight() override
{
m_human->fight();
cout << m_human->getName()
<< "吃了大饼果实, 获得大饼铠甲...!" << endl;
ability();
}

void ability()
{
cout << "最强辅助 -- 大饼果实可以将身边事物变成大饼, 帮助自己和队友回血..." << endl;
}
};

使用装饰模式,可以非常方便地给任意一个战士增加战斗技能,而无需修改原有代码,完全符合开放 – 封闭原则。

2.5 六边形战士

最后展示一下无敌的四皇之一的黑胡子的战斗力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main()
{
Teach* teach = new Teach("马歇尔·D·蒂奇");
DarkFruit* dark = new DarkFruit;
QuakeFruit* quake = new QuakeFruit;
PieFruit* pie = new PieFruit;
// 黑胡子吃了暗暗果实
dark->enchantment(teach);
// 黑胡子又吃了震震果实
quake->enchantment(dark);
// 黑胡子又吃了大饼果实
pie->enchantment(quake);
// 战斗
pie->fight();
delete teach;
delete dark;
delete quake;
delete pie;
return 0;
}

输出的结果如下:

1
2
3
4
5
6
马歇尔·D·蒂奇依靠惊人的力量和高超的体术战斗...
马歇尔·D·蒂奇吃了暗暗果实, 可以拥有黑洞一样的无限吸引力...
马歇尔·D·蒂奇你要注意: 吃了暗暗果实, 身体元素化之后不能躲避攻击,会吸收所有伤害!
马歇尔·D·蒂奇吃了震震果实, 可以在任意空间引发震动, 摧毁目标...!
马歇尔·D·蒂奇吃了大饼果实, 获得大饼铠甲...!
最强辅助 -- 大饼果实可以将身边事物变成大饼, 帮助自己和队友回血...

关于装饰模式就是在原有基础上一层一层进行包装,对于黑胡子的能力也是如此,不论是Teach 类 还是恶魔果实类的子类DarkFruit、QuakeFruit、PieFruit它们都是Soldier 类的子类,所以新的恶魔果实对象是可以为旧的恶魔果实附魔的,因为在恶魔果实内部都绑定了一个实体,他就是黑胡子的对象,最终所有恶魔果实的能力都集中在了这个黑胡子对象身上。

1
2
3
4
5
6
// 给黑胡子附魔
dark->enchantment(teach);
// 继续附魔
quake->enchantment(dark);
// 继续附魔
pie->enchantment(quake);

3. 结构图

最后根据黑胡子吃恶魔果实的这个例子把装饰模式对应的UML类图画一下(等学会了装饰模式之后,需要先画UML类图再写程序)。

image-20220906114501155

恶魔果实类DevilFruit就是装饰模式中的装饰类的基类,并且恶魔果实类和父类 Soldier之间还是聚合关系,通过它的派生类DarkFruit、QuakeFruit、PieFruit最终实现了对Teach 类的装饰,使黑胡子这个主体有了三种恶魔果实能力,最终是战力fight()得到了加成效果。

img