外观模式 - 桑尼号的狮吼炮


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

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


1. 开炮

桑尼号是草帽一伙的第二艘海贼船,设计者是弗兰奇,使用的材料是价值不菲、世界上最强的巨树“亚当”。主要特色是狮子造形的船头及“士兵船坞系统”,此外还有草皮做成的甲板,附有大型水族馆的房间、图书馆等许多方便的设备。

桑尼号船头的狮吼炮是一个非常厉害的武器。它能够从狮嘴发射出威力超强的加农炮,可利用狮头内部操控室的“狙击圈”自由网罗目标。要发动狮吼炮必须同时使用两桶可乐的能量‘风来喷射’才可以避免后座力让船飞退。

想要发射狮吼炮,操作很简单:瞄准目标,拉动拉杆就可以发射了。但是看似简单的加农炮发射,其底层是需要很多个系统协同配合才能完成的:

  1. 可乐注入系统
  2. 能量转换系统,将注入的可乐转换成能量
  3. 瞄准系统
  4. 目标锁定系统
  5. 加农炮控制系统
  6. 风来炮稳定系统,抵消后坐力,让船体稳定。

这么复杂的系统对于使用者来说其实就是一个按钮加一个拉杆。不论狮吼炮的设计者弗兰奇有没有学过设计模式,但他确实用到了一种设计模式:外观模式。外观模式就是给很多复杂的子系统提供一个简单的上层接口,并在这些接口中包含用户真正关心的功能。

关于外观模式的应用,在实际生活中也有有很多的场景:

  1. 通过电商平台下单,就可以收到自己需要的商品。
    • 上层接口:用于下单、查看物流信息、确认收货的用户界面
    • 底层模块:供货商、仓库、包装、送货、支付处理、售后等
  2. 购买基金
    • 上层接口:可供用户操作的UI界面
    • 底层模块:基金分类、购买股票、购买国债、购买企业债
  3. 音频转换工具
    • 上层接口:可供用户操作的UI界面
    • 底层模块:MP3编解码模块、FALC编解码模块、APE编解码模块、等。。。
  4. 智能家居系统
    • 上层接口:可供用户操作的UI界面
    • 底层模块:电视、灯、热水器、空调、路由器、。。。。

2. 庖丁解牛

2.1 UML类图

关于桑尼号的狮吼炮的组成前边已经描述过了,我们需要通过外观模式对其进行封装,如果仔细分析一下可以得知,上层的接口和底层的各个模块之间应该是关联关系(因为类之间没有继承关系,也不是整体和部分这种结构,因此排除了聚合和组合,并且它们之间具有包含和被包含的关系,所以确定的关系是关联关系),下面是狮吼炮对应的UML类图:

2.2 子系统

根据上面提供的类图就可以把对应的程序写出来了:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 乐可系统
class CokeSystem
{
public:
void immitCoke()
{
cout << "狮吼炮原料<可乐>已经注入完毕..." << endl;
}
};

// 能量转换系统
class EnergySystem
{
public:
void energyConvert()
{
cout << "已经将所有的可乐转换为了能量..." << endl;
}
};

// 目标锁定系统
class AimLockSystem
{
public:
void aimLock()
{
cout << "已经瞄准并且锁定了目标..." << endl;
}
};

// 加农炮发射系统
class Cannon
{
public:
void cannonFire()
{
cout << "狮吼炮正在向目标开火..." << endl;
}
};

// 风来炮稳定系统
class WindCannon
{
public:
void windCannonFire()
{
cout << "发射风来炮抵消后坐力稳定船身..." << endl;
}
};

这些子系统都是可以独立工作的,并且都提供了供外部调用的接口。

2.3 狮吼炮

狮吼炮是上层接口,需要协调上面的这些子系统使它们能够相互配合协同工作。

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
32
33
34
35
36
37
38
39
40
// 上层接口
class LionCannon
{
public:
LionCannon()
{
m_coke = new CokeSystem;
m_energy = new EnergySystem;
m_aimLock = new AimLockSystem;
m_cannon = new Cannon;
m_windCannon = new WindCannon;
}
~LionCannon()
{
delete m_coke;
delete m_energy;
delete m_aimLock;
delete m_cannon;
delete m_windCannon;
}
// 瞄准并锁定目标
void aimAndLock()
{
m_coke->immitCoke();
m_energy->energyConvert();
m_aimLock->aimLock();
}
// 开炮
void fire()
{
m_cannon->cannonFire();
m_windCannon->windCannonFire();
}
private:
CokeSystem* m_coke = nullptr;
EnergySystem* m_energy = nullptr;
AimLockSystem* m_aimLock = nullptr;
Cannon* m_cannon = nullptr;
WindCannon* m_windCannon = nullptr;
};

在狮吼炮上层接口类中只提供了两个方法:瞄准锁定 aimAndLock()开火 fire()。这样对于狮吼炮的操作难度瞬间变成了傻瓜级,只要有手并且眼睛不瞎就可以操作。

2.4 开炮

最后需要展示一下狮吼炮的威力:

1
2
3
4
5
6
7
8
9
int main()
{
// 发射狮吼炮
LionCannon* lion = new LionCannon;
lion->aimAndLock();
lion->fire();
delete lion;
return 0;
}

输出的结果为:

1
2
3
4
5
狮吼炮原料<可乐>已经注入完毕...
已经将所有的可乐转换为了能量...
已经瞄准并且锁定了目标...
狮吼炮正在向目标开火...
发射风来炮抵消后坐力稳定船身...

外观模式是一个很重要、平时也经常使用的设计模式,其核心思想就是化繁为简,封装底层逻辑,将使用者真正关心的功能通过上层接口呈现出来。