前一篇文章介绍了简单工厂模式,留下了一个疑问,如果需要扩展员工等级,有没有不修改既有接口的方式?
答案就是工厂方法模式。
既然员工的职级可以扩展,那创建对应职级员工的工厂是不是也可以扩展?工厂基类提供一个接口获取具体的产品,一旦有新的产品,就创建一个与之对应的工厂类。
看代码(visual studio 2019):
#pragma once
#include <iostream>
#include <list>
//等级基类和工厂基类
class GradeBase
{
public:
GradeBase(const std::string& strName) :m_strName(strName) {}
virtual ~GradeBase() = default;
std::string GetName() const { return m_strName; }
virtual double GetSalary() = 0;
protected:
std::string m_strName; //姓名
};
class FactoryBase
{
public:
virtual std::unique_ptr<GradeBase> GetLevel(const std::string& strName) = 0;
};
//等级1及其创建工厂类
class Grade_1 : public GradeBase
{
public:
using GradeBase::GradeBase;
double GetSalary() override {
return 5000;
}
};
class Factory_Grade1 : FactoryBase
{
public:
std::unique_ptr<GradeBase> GetLevel(const std::string& strName) override {
return std::make_unique<Grade_1>(strName);
}
};
//等级2及其创建工厂类
class Grade_2 : public GradeBase
{
public:
using GradeBase::GradeBase;
double GetSalary() override {
return 8000;
}
};
class Factory_Grade2 : FactoryBase
{
public:
std::unique_ptr<GradeBase> GetLevel(const std::string& strName) override{
return std::make_unique<Grade_2>(strName);
}
};
int main()
{
std::list<std::unique_ptr<GradeBase>> lstEmployee;
lstEmployee.push_back(Factory_Grade1().GetLevel("David"));
lstEmployee.push_back(Factory_Grade2().GetLevel("Tom"));
for (const auto& employee : lstEmployee)
{
if (!employee)
continue;
std::cout << employee->GetName() << "薪资:" << employee->GetSalary() << "\n";
}
system("pause");
return 0;
}
上面的例子很简单,脱胎于上一篇的例子。工厂和等级都有一个基类,工厂基类定义了一个虚函数GetLevel,获取对应的等级。
需要注意的是,这个虚函数的返回值,只能是指针或者引用。原因很简单,C++的多态特性决定的,如果不了解,需要先补补C++的基础知识,这里不赘述。
等级Grade_1有其专门的创建工厂Factory_Grade1,继承自FactoryBase,重写GetLevel函数,创建Grade_1的对象并返回。
Grade_2类似,也有其专门的创建工厂Factory_Grade2。
如果需要扩展职工等级,例如Grade_3,只需要再创建一个它专属的工厂Factory_Grade3,重写GetLevel虚函数即可,非常简答。
主函数里,调用不同工厂的GetLevel函数,获取不同的职工等级。
需要注意的是,这里的GetLevel不是static函数,而是类的成员函数。因为,只有成员函数,才能实现多态的特性。
所以,在调用工厂创建具体的职工等级时,需要创建一个临时对象,再用这个临时对象调用成员成员。
可以看出来,工厂方法模式有个弊端:一旦有新的等级类,就必须创建一个新的工厂类,如此,会导致类的数量急剧增加。
有没有什么方法能避免这种情况吗?答案是当然有!
C++的模板就能做到,请看下面代码(visual studio 2019):
#pragma once
#include <iostream>
#include <list>
//等级基类和工厂基类
class GradeBase
{
public:
GradeBase(const std::string& strName) :m_strName(strName) {}
virtual ~GradeBase() = default;
std::string GetName() const { return m_strName; }
virtual double GetSalary() = 0;
protected:
std::string m_strName; //姓名
};
//等级1
class Grade_1 : public GradeBase
{
public:
using GradeBase::GradeBase;
double GetSalary() override {
return 5000;
}
};
//等级2
class Grade_2 : public GradeBase
{
public:
using GradeBase::GradeBase;
double GetSalary() override {
return 8000;
}
};
template<typename T>
class Factory
{
public:
std::unique_ptr<GradeBase> GetLevel(const std::string& strName) {
return std::make_unique<T>(strName);
}
};
int main()
{
std::list<std::unique_ptr<GradeBase>> lstEmployee;
lstEmployee.push_back(Factory<Grade_1>().GetLevel("David"));
lstEmployee.push_back(Factory<Grade_2>().GetLevel("Tom"));
for (const auto& employee : lstEmployee)
{
if (!employee)
continue;
std::cout << employee->GetName() << "薪资:" << employee->GetSalary() << "\n";
}
system("pause");
return 0;
}
这里和上面最大的不同,在于只有一个工厂类Factory,它是一个模板类,根据传入的模板不同,可实例化不同的类对象。
只有一个前提条件,这些类必须继承自同一个基类。
客户端调用时,也不需要知道有哪些工厂,只需要知道有哪些员工等级就行。
执行结果如下:
这个模式虽然看起来很不错,但其实际应用,没有简单工厂普遍,为什么呢?
下一篇就会介绍,一些真正的面向对象程序的编程习惯和参考准则。