设计模式(四)单例和简单工厂的融合

前面三篇文章介绍了三种模式:单例、简单工厂和工厂方法。

其中,简单工厂和工厂方法都是基于同一个实际问题。但是,这个例子里有几个问题:

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++没有,所以,具体问题需要根据使用语言不同具体对待。

到这里,肯定会有人跳出来,说这违反了开闭原则。

我想说的是,一切原则,只是一个指导,当这个原则成为具体问题的阻碍时,直接忽视它!理解原则,灵活运用,切不可成为原则的奴隶,而应该成为它的主人!

这世界上没有任何东西适用于所有场景,人民币也不行,到了原始森林,人民币也不管用了,反而刀子更有用。

下一篇文章,就介绍一些面向对象编程里常听到的指导原则,以及这些原则如何应用于实际软件设计中。

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值