前面三篇文章介绍了三种模式:单例、简单工厂和工厂方法。
其中,简单工厂和工厂方法都是基于同一个实际问题。但是,这个例子里有几个问题:
1、职工的创建在main函数中,不合理,可能多个函数都会需要访问职工信息;
2、职工是程序直接创建的,不合理,一旦出现新的职工入职,就需要改代码,应该改为文件方式;
3、没有最基本的职工查找功能。
除了以上三点,还有很多其他的待完善地方,这里先考虑这三点。
代码如下(visual studio 2019):
#pragma once
#include <iostream>
#include <list>
class Employee
{
public:
Employee(const std::string& strName) :m_strName(strName) {}
virtual ~Employee() = default;
std::string GetName() const { return m_strName; }
virtual double GetSalary() const = 0;
protected:
std::string m_strName; //姓名
};
class Employee1 : public Employee
{
public:
using Employee::Employee;
double GetSalary() const override {
return 5000;
}
};
class Employee2 : public Employee
{
public:
using Employee::Employee;
double GetSalary() const override {
return 8000;
}
};
class Employee3 : public Employee
{
public:
using Employee::Employee;
double GetSalary() const override {
return 10000;
}
};
/**
* @breif 职工管理类
*/
class EmployeeManage
{
public:
static EmployeeManage& GetInstance() {
static EmployeeManage cInstance;
return cInstance;
}
/// @brief 获取指定员工信息
const Employee* GetEmplyee(const std::string& strName){
for (const auto& p : m_lstEmployee)
if (strName == p->GetName())
return p;
return nullptr;
}
//从文件中读取员工信息到内存中
void InitData()
{
if (m_bIsInit)
return;
//为了尽可能让例子简单,lstInfo作为模拟读取文件的内容
std::list<std::pair<std::string, int>> lstInfo;
lstInfo.push_back({ "David", 2 });
lstInfo.push_back({ "Tom", 3 });
lstInfo.push_back({ "Lucas", 1 });
lstInfo.push_back({ "Job", 4 });
//根据文件内容,创建相应实体
for (const auto& data : lstInfo)
{
if (1 == data.second)
m_lstEmployee.push_back(new Employee1(data.first));
else if(2 == data.second)
m_lstEmployee.push_back(new Employee2(data.first));
else if(3 == data.second)
m_lstEmployee.push_back(new Employee3(data.first));
else //错误处理
{
std::cout << "员工信息错误,无职级 " << data.second << "\n";
}
}
m_bIsInit = true; //防止重复初始化
}
protected:
EmployeeManage() = default;
~EmployeeManage() {
for(auto& p : m_lstEmployee)
if (p)
{
delete p;
p = nullptr;
}
}
private:
bool m_bIsInit{ false };
std::list<Employee*> m_lstEmployee;
};
void PrintInfo(const std::string& strName)
{
const Employee* pEmploee = EmployeeManage::GetInstance().GetEmplyee(strName);
if (!pEmploee)
{
std::cout << "很抱歉,未找到员工\" " << strName << "\"的信息\n";
return;
}
std::cout << pEmploee->GetName() << "的薪资是:" << pEmploee->GetSalary() << "\n";
}
int main()
{
//初始化员工信息(时机视具体情况而定,放到)
EmployeeManage::GetInstance().InitData();
PrintInfo("David");
PrintInfo("Lucas");
PrintInfo("Mark");
system("pause");
return 0;
}
以上代码略长,加上注释和换行,一百行多一点,如果阅读上面的代码都吃力,那说明学习设计模式对你来说为时尚早,需要先看基础书籍。
这里比之前更加规范一些,职工信息类改成了Employee,从其派生出了三个子类,分别表示1~3职级(这种方式实现并不好,后面文章中还会继续优化)。
注意,这个例子的核心是EmployeeManage类,这个类,首先是一个单例!其次,还兼有简单工厂的职责。
这是一个职工管理类,有一个m_lstEmployee的成员变量,存放职工的信息。这个类的功能,才能看出来单例模式的必要性。
m_lstEmployee里的对象是指针,这里故意没用智能指针,就会涉及到一个内存释放的问题。什么时候释放合适?当然是EmployeeManage析构的时候,所以析构函数里有释放内存的代码。
另外,这个类还有查找员工信息和初始化员工信息的功能,一般而言,员工信息肯定保存在文件中(正常来说是数据库格式保存),只有当程序加载时,才从文件中读取出来。当然,你要说存在服务器里面,服务器不断电,我也没办法。
如果有新的职工等级出现,只需要修改InitData函数即可,同时,这个函数还对不存在的职工等级进行了错误处理。
这里为了尽可能简单,没有增加员工的功能。
PrintInfo函数模仿一个客户端的行为,查找职工信息,找到了就打印员工,没找到打印提示信息。
执行结果如下:
这个例子,才有一丁点儿设计模式的味道。
从这个例子可以看出不来,简单工厂比工厂方法用得更多。因为如果从文件中读取员工的信息,职级,要么是用int来表示,要么是用字符串来表示,不管用什么,最后,都需要转换为具体的职工等级,所以肯定会有if...else...的判断。而简单工厂,就是为这个量身定制的。
java有反射机制,C++没有,所以,具体问题需要根据使用语言不同具体对待。
到这里,肯定会有人跳出来,说这违反了开闭原则。
我想说的是,一切原则,只是一个指导,当这个原则成为具体问题的阻碍时,直接忽视它!理解原则,灵活运用,切不可成为原则的奴隶,而应该成为它的主人!
这世界上没有任何东西适用于所有场景,人民币也不行,到了原始森林,人民币也不管用了,反而刀子更有用。
下一篇文章,就介绍一些面向对象编程里常听到的指导原则,以及这些原则如何应用于实际软件设计中。