【学习笔记】C/C++ 设计模式 - 工厂模式(下)
介绍说明
这篇笔记承接《【学习笔记】C/C++ 设计模式 - 工厂模式(上)》文章,主要记录 “抽象工厂设计模式” 的学习笔记,上一次是以音频播放器来作为例子,主要是想体现出的是接口标准化的优势,但不适用于 “抽象工厂设计模式” 的示例,因此这里改为台式电脑作为例子。
上文说到工厂模式属于 “创建型设计模式” ,但其中的 “创建” 的优势并不明显,那么 “抽象工厂模式” 对 “创建” 具有很好的体现。原因看下面的举例说明。
举例说明
现实生活中,我们所购买的电脑大部分都是OEM/ODM产品,例如联想的电脑,没有一个组件是由联想自己设计生产,都是根据市场需求来选用各家的组件搭配而成。
台式电脑基本由主板、CPU、内存、硬盘、显示屏、键盘、鼠标、电源组成,每个组件都有各自的用处,也都有不同的厂商设计制造。如 主板 有华硕主板、技嘉主板,CPU 有 Intel 、有 Amd,内存有金士顿、三星,这些不同的品牌有的性价比高,有的性能高寿命长,各有各的特点,设计也都共同遵循行业标准,将这些有效的组合,就形成了不同配置不同价格的电脑了。消费者不需要任何硬件基础,根据自身实际所需选择其中一种配置的电脑就好,不会导致玩游戏的人买了只够日常办公的电脑,而只需日常办公的人却买了发烧级的游戏电脑,白白浪费钱。
我们想购买一台期望性价比高的台式电脑,通常都会请熟悉这些的朋友推荐多种方案的配置单,里面记录了要求使用什么样主板、什么样的CPU、什么样的内存,以及这些组件都在哪里购买,大致需要多少价钱,性能如何。而我们就会拿着这份配置单,去电脑城购买。抽象工厂就相当于你的朋友给你提供多种配置方案的配置单,业务逻辑就相当于去购买并且去使用这些组件来组装电脑。
即由 “抽象工厂模式” 决定如何选择这些组件,并提供相应组件的创建方法,而外部逻辑借助这些创建方法,来决定如何使用这些组件,并且不需要了解组件的具体实现。
应用场景
某个功能可以通过多个对象关联共同实现,而多个对象各自有不同的实现,且选择不同实现的对象,可以让功能产生变化从而适用于多种应用场景。
如上例,同样的是电脑,同样都是由CPU、内存、硬盘等组成(多个对象),但是把 CPU 由单核更换为多核、把内存由 1G 更换为 32G、把硬盘由机械硬盘改为固态硬盘(不同的实现),性能就完全不一样,同时价格也上涨很多,把原来只能用于普通办公环境的电脑,变成了可以玩大型游戏的电脑(适用于办公运行环境变为适用于可以玩游戏的环境)。
代码实现
编写步骤:
- 将电脑的接口抽象出来(Computer)
- 将电脑组成的各个组件接口抽象出来(MainBoard、Cpu、Memory、Disk...)
- 依次实现各个组件(ASUS\MSI、Intel\AMD...)
- 根据不同的需求从各个组件中选择合适的组件形成多份电脑硬件配置(联想电脑、小米电脑)
- 根据需要选择联想,或者小米的配置进行使用(参考单元测试)
电脑以及各个组件的抽象方法
首先把电脑的各个组件以及电脑抽象出来,统一接口标准:
#ifndef __COMPUTER_H
#define __COMPUTER_H
// 主板
class MainBoard
{
public:
virtual const char* getMakeName() = 0; // 制造商名称
virtual const char* getModelName() = 0; // 型号名称
};
// CPU
class Cpu
{
public:
virtual const char* getMakeName() = 0; // 制造商名称
virtual const char* getModelName() = 0; // 型号名称
virtual float getFrequency() = 0; // 获取运行频率
virtual int getCoreNumber() = 0; // 获取核心数
};
// 内存
class Memory
{
public:
virtual const char* getMakeName() = 0; // 制造商名称
virtual const char* getModelName() = 0; // 型号名称
virtual int getFrequency() = 0; // 获取工作频率
virtual int getCapacity() = 0; // 获取容量大小
};
// 硬盘
class Disk
{
public:
virtual const char* getMakeName() = 0; // 制造商名称
virtual const char* getModelName() = 0; // 型号名称
virtual int getSpeed() = 0; // 获取读写速度
virtual int getCapacity() = 0; // 获取容量大小
};
// 显示器
class Display
{
public:
virtual const char* getMakeName() = 0; // 制造商名称
virtual const char* getModelName() = 0; // 型号名称
virtual const char* getResolution() = 0; // 分辨率
};
// 键盘
class Keyboard
{
public:
virtual const char* getMakeName() = 0; // 制造商名称
virtual const char* getModelName() = 0; // 型号名称
};
// 鼠标
class Mouse
{
public:
virtual const char* getMakeName() = 0; // 制造商名称
virtual const char* getModelName() = 0; // 型号名称
};
// 计算机
class Computer
{
public:
Computer() {};
virtual ~Computer() {};
virtual const char* getMakeName() = 0; // 制造商名称
virtual const char* getModelName() = 0; // 型号名称
virtual MainBoard* createMainBoard() = 0;
virtual Cpu* createCpu() = 0;
virtual Memory* createMemory() = 0;
virtual Disk* createDisk() = 0;
virtual Display* createDisplay() = 0;
virtual Keyboard* createKeyboard() = 0;
virtual Mouse* createMouse() = 0;
};
#endif
各个部件的具体实现
限于篇幅,每个组件只提供一个具体实现,完整的代码参考附件。
这是实现具体的华硕主板 B360M:
#ifndef __MAINBOARD_ASUS_B360M_H
#define __MAINBOARD_ASUS_B360M_H
#include "../../Computer.h"
class MainBoardAsus_B360M : public MainBoard
{
public:
virtual const char* getMakeName() override; // 制造商名称
virtual const char* getModelName() override; // 型号名称
};
#endif
#include "MainBoardAsus_B360M.h"
const char* MainBoardAsus_B360M::getMakeName()
{
return "ASUS";
}
const char* MainBoardAsus_B360M::getModelName()
{
return "B360M";
}
实现具体的 AMD CPU 3990X:
#ifndef __CPU_AMD_3990X_H
#define __CPU_AMD_3990X_H
#include "../../Computer.h"
// CPU
class CpuAmd_3990X : public Cpu
{
public:
virtual const char* getMakeName() override; // 制造商名称
virtual const char* getModelName() override; // 型号名称
virtual float getFrequency() override; // 获取运行频率
virtual int getCoreNumber() override; // 获取核心数
};
#endif
#include "CpuAmd_3990X.h"
const char* CpuAmd_3990X::getMakeName()
{
return "AMD";
}
const char* CpuAmd_3990X::getModelName()
{
return "3990X";
}
float CpuAmd_3990X::getFrequency()
{
return 5.0f;
}
int CpuAmd_3990X::getCoreNumber()
{
return 64;
}
实现具体的内存 金士顿
#ifndef __MEMORY_KINGSTON_DDR42400_H
#define __MEMORY_KINGSTON_DDR42400_H
#include "../../Computer.h"
class MemoryKingstonDDR42400 : public Memory
{
public:
virtual const char* getMakeName() override; // 制造商名称
virtual const char* getModelName() override; // 型号名称
virtual float getFrequency() override; // 获取工作频率
virtual int getCapacity() override; // 获取容量大小
};
#endif
#include "MemoryKingston_DDR42400.h"
const char* MemoryKingstonDDR42400::getMakeName()
{
return "Kingston";
}
const char* MemoryKingstonDDR42400::getModelName()
{
return "DDR42400";
}
int MemoryKingstonDDR42400::getFrequency()
{
return 2133;
}
int MemoryKingstonDDR42400::getCapacity()
{
return 32;
}
实现具体的硬盘 西数:
#ifndef __DISK_WD_1000GB_H
#define __DISK_WD_1000GB_H
#include "../../Computer.h"
class DiskWD1000GB : public Disk
{
public:
virtual const char* getMakeName() override; // 制造商名称
virtual const char* getModelName() override; // 型号名称
virtual int getSpeed() override; // 获取读写速度
virtual int getCapacity() override; // 获取容量大小
};
#endif
#include "DiskWD_1000GB.h"
const char* DiskWD1000GB::getMakeName()
{
return "WesternDigital";
}
const char* DiskWD1000GB::getModelName()
{
return "HUS722T1TALA604";
}
int DiskWD1000GB::getSpeed()
{
return 300;
}
int DiskWD1000GB::getCapacity()
{
return 1000;
}
实现具体的三星显示屏:
#ifndef __DISPLAY_SAMSUNG_C32JG52QQC_H
#define __DISPLAY_SAMSUNG_C32JG52QQC_H
#include "../../Computer.h"
class DisplaySamsungC32JG52QQC : public Display
{
public:
virtual const char* getMakeName() override; // 制造商名称
virtual const char* getModelName() override; // 型号名称
virtual const char* getResolution() override; // 分辨率
};
#endif
#include "DisplaySamsung_C32JG52QQC.h"
const char* DisplaySamsungC32JG52QQC::getMakeName()
{
return "Samsung";
}
const char* DisplaySamsungC32JG52QQC::getModelName()
{
return "C32JG52QQC";
}
const char* DisplaySamsungC32JG52QQC::getResolution()
{
return "2560×1440";
}
实现具体的樱桃键盘:
#ifndef __KEYBOARD_CHERRY_MX80_H
#define __KEYBOARD_CHERRY_MX80_H
#include "../../Computer.h"
class KeyboardCherryMX80 : public Keyboard
{
public:
virtual const char* getMakeName() override; // 制造商名称
virtual const char* getModelName() override; // 型号名称
};
#endif
#include "KeyboardCherry_MX80.h"
const char* KeyboardCherryMX80::getMakeName()
{
return "Cherry";
}
const char* KeyboardCherryMX80::getModelName()
{
return "MX-Board 8.0";
}
实现具体的罗技鼠标:
#ifndef __MOUSE_LOGITECH_G502_H
#define __MOUSE_LOGITECH_G502_H
#include "../../Computer.h"
class MouseLogitechG502 : public Mouse
{
public:
virtual const char* getMakeName() override; // 制造商名称
virtual const char* getModelName() override; // 型号名称
};
#endif
#include "MouseLogitech_G502.h"
const char* MouseLogitechG502::getMakeName()
{
return "Logitech";
}
const char* MouseLogitechG502::getModelName()
{
return "G502";
}
配置完整的电脑
各个组件现在都已经有具体的实现了,与之前介绍的工厂模式没啥两样,都是根据定义好的标准接口去实现。接下来假设小米和联想根据市场需求各自提供一份电脑配置。
备注说明:考虑到篇幅问题,本文中只给出了小米所选择所有组件的具体实现代码,完整实现工厂可以看附件打包好的代码。
联想提供的电脑配置单以及对应组件的创建方法:
#ifndef __COMPUTER_LENOVO_H
#define __COMPUTER_LENOVO_H
#include <iostream>
#include "Computer.h"
class ComputerLenovo : public Computer
{
public:
ComputerLenovo() { printf("ComputerLenovo\n"); }
~ComputerLenovo() { printf("~ComputerLenovo\n"); }
virtual const char* getMakeName() override;
virtual const char* getModelName() override;
virtual MainBoard* createMainBoard() override;
virtual Cpu* createCpu() override;
virtual Memory* createMemory() override;
virtual Disk* createDisk() override;
virtual Display* createDisplay() override;
virtual Keyboard* createKeyboard() override;
virtual Mouse* createMouse() override;
};
#endif
#include "ComputerLenovo.h"
#include "MainBoard/Msi/MainBoardMsi_B450M.h"
#include "Cpu/Intel/CpuIntel_10980XE.h"
#include "Memory/Kingston/MemoryKingston_DDR42400.h"
#include "Disk/Seagate/DiskSeagate_500GB.h"
#include "Display/AOC/DisplayAoc_I2490VXH.h"
#include "Keyboard/Alienware/KeyboardAlienware_AW768.h"
#include "Mouse/Lenovo/MouseLenovo_M120.h"
const char* ComputerLenovo::getMakeName()
{
return "Lenove";
}
const char* ComputerLenovo::getModelName()
{
return "刃7000";
}
MainBoard* ComputerLenovo::createMainBoard()
{
return new MainBoardMsi_B450M();
}
Cpu* ComputerLenovo::createCpu()
{
return new CpuIntel_10980XE();
}
Memory* ComputerLenovo::createMemory()
{
return new MemoryKingstonDDR42400();
}
Disk* ComputerLenovo::createDisk()
{
return new DiskSeagate500GB();
}
Display* ComputerLenovo::createDisplay()
{
return new DisplayAocI2490VXH();
}
Keyboard* ComputerLenovo::createKeyboard()
{
return new KeyboardAlienwareAW768();
}
Mouse* ComputerLenovo::createMouse()
{
return new MouseLenovoM120();
}
小米提供的电脑配置单以及对应组件的创建方法:
#ifndef __COMPUTER_MI_H
#define __COMPUTER_MI_H
#include <iostream>
#include "Computer.h"
class ComputerMi : public Computer
{
public:
ComputerMi() { printf("ComputerMi\n"); }
~ComputerMi() { printf("~ComputerMi\n"); }
virtual const char* getMakeName() override;
virtual const char* getModelName() override;
virtual MainBoard* createMainBoard() override;
virtual Cpu* createCpu() override;
virtual Memory* createMemory() override;
virtual Disk* createDisk() override;
virtual Display* createDisplay() override;
virtual Keyboard* createKeyboard() override;
virtual Mouse* createMouse() override;
};
#endif
#include "ComputerMi.h"
#include "MainBoard/Asus/MainBoardAsus_B360M.h"
#include "Cpu/Amd/CpuAmd_3990X.h"
#include "Memory/Kingston/MemoryKingston_DDR42400.h"
#include "Disk/WD/DiskWD_1000GB.h"
#include "Display/Samsung/DisplaySamsung_C32JG52QQC.h"
#include "Keyboard/Cherry/KeyboardCherry_MX80.h"
#include "Mouse/Logitech/MouseLogitech_G502.h"
const char* ComputerMi::getMakeName()
{
return "XiaoMi";
}
const char* ComputerMi::getModelName()
{
return "Mi9890";
}
MainBoard* ComputerMi::createMainBoard()
{
return new MainBoardAsus_B360M();
}
Cpu* ComputerMi::createCpu()
{
return new CpuAmd_3990X();
}
Memory* ComputerMi::createMemory()
{
return new MemoryKingstonDDR42400();
}
Disk* ComputerMi::createDisk()
{
return new DiskWD1000GB();
}
Display* ComputerMi::createDisplay()
{
return new DisplaySamsungC32JG52QQC();
}
Keyboard* ComputerMi::createKeyboard()
{
return new KeyboardCherryMX80();
}
Mouse* ComputerMi::createMouse()
{
return new MouseLogitechG502();
}
单元测试
现在联想和小米都确定好了组件配置,下面就选择联想和小米的配置,来演示如何使用这些配置提供的组件:
#ifndef __TEST_COMPUTER_H
#define __TEST_COMPUTER_H
#include "Computer.h"
class TestComputer
{
private:
void ComputerInfoDump(Computer* pComputer);
public:
int test();
};
#endif
#include <iostream>
#include <iomanip>
#include "test_Computer.h"
#include "ComputerLenovo.h"
#include "ComputerMi.h"
#define WIDTH 10
using namespace std;
void TestComputer::ComputerInfoDump(Computer* pComputer)
{
MainBoard *pMainBoard = pComputer->createMainBoard();
Cpu *pCpu = pComputer->createCpu();
Memory *pMemory = pComputer->createMemory();
Disk *pDisk = pComputer->createDisk();
Display *pDisplay = pComputer->createDisplay();
Keyboard *pKeyboard = pComputer->createKeyboard();
Mouse *pMouse = pComputer->createMouse();
printf("--------------------------------------------------------------------------------------------------------\n");
printf("品牌商: %-10s 型号:%s\n", pComputer->getMakeName(), pComputer->getModelName());
printf("-------------------------------\n");
printf("主板信息 -> 制造商: %-16s 型号: %s\n", pMainBoard->getMakeName(), pMainBoard->getModelName());
printf("CPU 信息 -> 制造商: %-16s 型号: %-20s 频率: %-18.2f 核数: %d\n", pCpu->getMakeName(), pCpu->getModelName(), pCpu->getFrequency(), pCpu->getCoreNumber());
printf("内存信息 -> 制造商: %-16s 型号: %-20s 频率: %-18d 容量: %dGB\n", pMemory->getMakeName(), pMemory->getModelName(), pMemory->getFrequency(), pMemory->getCapacity());
printf("硬盘信息 -> 制造商: %-16s 型号: %-20s 速度: %-18d 容量: %dGB\n", pDisk->getMakeName(), pDisk->getModelName(), pDisk->getSpeed(), pDisk->getCapacity());
printf("显示屏 -> 制造商: %-16s 型号: %-20s 分辨率: %s\n", pDisplay->getMakeName(), pDisplay->getModelName(), pDisplay->getResolution());
printf("键盘信息 -> 制造商: %-16s 型号: %s\n", pKeyboard->getMakeName(), pKeyboard->getModelName());
printf("鼠标信息 -> 制造商: %-16s 型号: %s\n", pMouse->getMakeName(), pMouse->getModelName());
printf("--------------------------------------------------------------------------------------------------------\n");
return;
}
int TestComputer::test()
{
Computer* pComputer = nullptr;
// 选择联想电脑
printf("========================================================================================================\n");
pComputer = new ComputerLenovo();
ComputerInfoDump(pComputer);
delete pComputer;
printf("========================================================================================================\n\n\n");
// 选择小米电脑
printf("========================================================================================================\n");
pComputer = new ComputerMi();
ComputerInfoDump(pComputer);
delete pComputer;
printf("========================================================================================================\n\n\n");
return 0;
}
执行的结果:
优点缺点
优点
从单元测试代码当中,可以了解到,业务逻辑不需要关心电脑各个组件的选型以及其具体的实现细节,只需要根据使用场景来选择合适的电脑配置,从而使用里面已经确定好的组件。
无论是增加新的组件的实现(如再增加一个三星品牌的内存),还是新增加电脑(如小米再增加一个电脑或者华为也来增加一个电脑),只需要新增对于的接口实现,不需要改变原有的接口,即可灵活切换使用。
缺点
只要基类产生修改,所涉及到的子类都将会连锁反应,出现各种错误,需要修改所有的子类。
1. 如本文代码说实现的例子,其实台式电脑还有一个很重要的组件,那就是电源。这时候如果我想把电源加进去,那么就需要修改 Computer.h 文件,增加电源的抽象接口,这时候,还需要为所有已经实现的电脑配置,增加电源组件进去,并且需要改动主业务逻辑,调用电源相关的接口(因为没有电,电脑不能工作呀),改动非常大。
2. 如果修改某个组件,新增、修改或删除某个接口,都会影响该组件的所有实现,如在 MainBoard 中增加获取尺寸的接口:
也可以将纯虚函数改为虚函数,给出默认实现来规避这种问题,但不适用于所有的情况,也破坏只定义接口不具体实现的规则。