工资设计
#include <ctime>
#include <string>
#include <iostream>
#include <fstream>
#include <climits>
using namespace std;
enum EmpLevel
{
enumOfficer = 1,
enumStaff = 2
};
class Employee
{
public:
Employee(string strName, int nYear):m_strName(strName),m_nYear(nYear)
{}
public:
string GetName() const
{
return m_strName;
}
int GetYear() const
{
return m_nYear;
}
EmpLevel GetLevel() const
{
return m_nLevel;
}
virtual int GetSalary() = 0;
protected:
int GetWorkTime() const
{
time_t t = time(0);
struct tm* now = localtime(&t);
return now->tm_year + 1900 - m_nYear + 1;
}
protected:
string m_strName;
int m_nYear;
EmpLevel m_nLevel;
};
class Officer : public Employee
{
public:
Officer(string strName, int nYear):Employee(strName, nYear)
{
m_nLevel = enumOfficer;
}
public:
virtual int GetSalary() override
{
return GetWorkTime() * 5000;
}
};
class Staff : public Employee
{
public:
Staff(string strName, int nYear):Employee(strName, nYear)
{
m_nLevel = enumStaff;
}
public:
virtual int GetSalary() override
{
return GetWorkTime() * 1000;
}
};
在员工类及其派生类的实现中,全面体现了面向对象的三大特征。
- 首先,我们将所有员工,包括高级员工和普通员工的共有属性和行为封装成员工类 Employee 这个基类,这里体现的是类对属性和行为的封装;
- 然后使用面向对象的继承机制从员工类 Employee 中派生出高级员工类 Officer 和普通员工类 Staff,这样使得这两个派生类可以复用基类的代码,例如员工的姓名和入职时间等共有属性,以及供外界访问的 GetName()等接口函数,派生类无须重复定义而通过继承就直接拥有了。
- 派生类所要做的, 只是实现自己特有的属性和行为。例如,两个派生类各自对工资的计算方式不同,所以利用面向对象的多态机制,它们对基类提供的用于计算工资的 GetSalary()纯虚函数进行重写,各自完成了自己特殊的工资计算方式。
完成了具体的员工类的实现,接下来就是用它们创建具体的员工对象并交由最核心的 SalarySys类对其进行管理。
const int MAX = 100000;
class SalarySys
{
public:
SalarySys():m_nCount(0),m_strFileName("SalaryData.txt")
{
for(long i = 0; i < MAX; ++i)
{
m_arrEmp[i] = nullptr;
}
Read();
}
~SalarySys()
{
Write();
for(long i = 0; i < m_nCount; ++i)
{
delete m_arrEmp[i];
m_arrEmp[i] = nullptr;
}
}
public:
int Read()
{
string strName = "";
int nLevel = 0;
int nYear = 0;
int i = 0;
ifstream in(m_strFileName);
if(in.is_open())
{
while(true)
{
in>>strName>>nLevel>>nYear;
if(!in)
{
break;
}
if(enumOfficer == nLevel)
{
m_arrEmp[i] = new Officer(strName, nYear);
++i;
}
else if(enumStaff == nLevel)
{
m_arrEmp[i] = new Staff(strName, nYear);
++i;
}
if(i >= MAX)
{
break;
}
}
in.close();
}
cout<<"there is " << i << "employee" << endl;
m_nCount = i;
return i;
}
void Write()
{
ofstream o(m_strFileName);
if (o.is_open())
{
for(int i = 0; i < m_nCount; ++i)
{
Employee* p = m_arrEmp[i];
o<<p->GetName()<<"\t"
<<p->GetLevel()<<"\t"
<<p->GetYear()<<endl;
}
o.close();
}
}
int Input()
{
cout<<"please input employee information(name, level(1-Officer,2-Staff) year), Eg:XiongDa 1 2008"<<endl;
cout<<"-1 to finish input";
int i = m_nCount;
for(; i < MAX; ++i)
{
cout<<"please input number" << i <<"employee information:" << endl;
string strName = "";
int nL = 0;
int nY = 0;
cin>>strName>>nL>>nY;
if(!cin)
{
cout<<"Error, please input again"<<endl;
cin.clear();
cin.sync();
--i;
continue;
}
else
{
if("-1" == strName)
{
break;
}
if(enumOfficer == nL)
m_arrEmp[i] = new Officer(strName, nY);
else if(enumStaff == nL)
m_arrEmp[i] = new Staff(strName, nY);
else
{
cout<<"wrong level, please input again"<<endl;
--i;
cin.clear();
cin.sync();
continue;
}
}
}
m_nCount = i;
return m_nCount;
}
Employee* GetMax()
{
Employee* pMax = nullptr;
int nMax = INT_MIN;
for(int i = 0; i < m_nCount ; ++i)
{
if(m_arrEmp[i]->GetSalary() > nMax)
{
pMax = m_arrEmp[i];
nMax = pMax->GetSalary();
}
}
return pMax;
}
void Find()
{
while(true)
{
string strName = "";
cout<<"please input the name of employee(-1 to finish):"<<endl;
cin>>strName;
if(!cin)
{
cout<<"input error, please input again"<<endl;
cin.clear();
cin.sync();
continue;
}
else if ("-1" == strName)
{
cout<<"find over, thanks!"<<endl;
break;
}
bool bFind = false;
for(int i = 0; i < m_nCount; ++i)
{
Employee* p = m_arrEmp[i];
if (strName == p->GetName())
{
cout<<"employee name:"<<p->GetName()<<endl;
cout<<"employee salary:"<<p->GetSalary()<<endl;
bFind = true;
break;
}
}
if(!bFind)
{
cout<<"can't find the name "<<strName<<endl;
cout<<"please check the name and input again"<<endl;
}
}
}
private:
const string m_strFileName;
Employee* m_arrEmp[MAX];
int m_nCount;
};
完成了工资系统类 SalarySys 之后,接下来就只需要在主函数中运用上面创建的这些类来完成需求设计中的各个用例。
int main()
{
SalarySys sys;
sys.Input();
Employee* pMax = sys.GetMax();
if(nullptr != pMax)
{
cout<<"the employee with the most high salary is: "<<endl;
cout<<"name: "<<pMax->GetName()<<endl;
cout<<"salary: "<<pMax->GetSalary()<<endl;
}
sys.Find();
return 0;
}
there is 0 employee
please input employee information(name, level(1-Officer,2-Staff) year), Eg:XiongDa 1 2008
-1 to finish inputplease input number 0 employee information:
a 2 2011
please input number 1 employee information:
b 2 2012
please input number 2 employee information:
c 1 2015
please input number 3 employee information:
d 1 2016
please input number 4 employee information:
-1
-1
0
the employee with the most high salary is:
name: c
salary: 35000
please input the name of employee(-1 to finish):
a
employee name:a
employee salary:11000
please input the name of employee(-1 to finish):
b
employee name:b
employee salary:10000
please input the name of employee(-1 to finish):
c
employee name:c
employee salary:35000
please input the name of employee(-1 to finish):
d
employee name:d
employee salary:30000
please input the name of employee(-1 to finish):
e
can't find the name e
please check the name and input again
please input the name of employee(-1 to finish):
-1
find over, thanks!
总结
- 封装,可以让函数和它所操作的数据捆绑在一起成为对象,可以起到很好的数据保护的作用;
- 继承,可以复用共同的属性和行为,起到代码复用的作用。同时还可以很方便地对其进行扩展, 从而支持更多更新的需求;
- 多态,让我们可以以一致的调用方式,实现不同的操作行为。从而使得我们在设计中考虑得更多的是接口问题,而不用担心后面的实现问题。
补充-设计模式
设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,尽量避免会引起麻烦的紧耦合,以增强软件设计适应变化的能力。这样可以让我们的软件具有良好的结构, 能够适应外部需求的变化,能够避免软件因为不断增加新功能而显得过于臃肿,最后陷入需求变化的深渊。另外一方面,设计模式都是前人优秀设计成果的总结,在面对相似问题的时候,直接复用这些经过实践检验的设计方案,不仅可以保证我们设计的质量,还可以节省设计时间,提高开发效率。
单件模式
单件模式就是让某个类在任何时候都只能创建唯一的一个对象。
比如,我们要设计开发一个打印程序,我们只希望有一个 Print Spooler 对象,以避免两个打印动作同时输送至打印机中;在数据库连接中,我们也同样希望在程序中只有唯一的一个数据库连接以节省资源;在上面工资程序中的 SalarySys 类,也同样需要保证它在整个程序中只有唯一的一个实例对象,要不然每个人的工资在不同的 SalarySys 对象中就可能会产生冲突。
对象的创建是通过构造函数来完成的,所以单件模式的实现关键是将类的构造函数设定为 private 访问级别,让外界无法通过构造函数自由地创建这个类的对象。取而代之的是,它会提供一个公有的静态的创建函数来负责对象的创建,而在这个创建函数中,我们就可以判断唯一的对象是否已经创建。如果尚未创建,则调用自己的构造函数创建对象并返回,如果已经创建,则直接返回已经创建的对象。这样,就保证了这个类的对象的唯一性。
// 使用单件模式实现的 SalarySys 类
class SalarySys
{
// 省略 SalarySys 类的其他属性和行为
//...
// 将构造函数私有化(private)
private:
SalarySys():m_nCount(0),m_strFileName("SalaryData.txt")
{
// …
}
public:
// 提供一个公有的( public,为了让客户能够访问)静态的(static,为了让
// 客户可以在不创建对象的情况下直接访问)创建函数,
// 供外界获取 SalarySys 的唯一对象
// 在这个函数中,对对象的创建行为进行控制,以保证对象的唯一性
static SalarySys* getInstance()
{
// 如果唯一的实例对象还没有创建,则创建实例对象
if ( nullptr == m_pInstance )
m_pInstance = new SalarySys();
// 如果已经创建实例对象,则直接返回这个实例对象
return m_pInstance;
};
private:
// 静态的对象指针,指向唯一的实例对象
// 为静态的唯一实例对象指针赋初始值,表示对象尚未创建
static SalarySys* m_pInstance = nullptr;
};
// …
int main()
{
// 第一次调用 getInstance()函数,唯一的 SalarySys 对象尚未创建,
// 则创建相应的对象并返回指向这个对象的指针
SalarySys* pSalarySys1 = SalarySys::getInstance();
// …
// 第二次调用 getInstance()函数,这时 SalarySys 的对象已经创建,
// 则不再创建新对象而直接返回指向那个已创建对象的指针,保证对象的唯一性
SalarySys* pSalarySys2 = SalarySys::getInstance();
// …
// 释放已创建的对象, pSalarySys1 和 pSalarySys2 指向的是同一个对象,
// 使用 pSalarySys1 或 pSalarySys2 释放这个对象是等效的,并只需要释放一次
delete pSalarySys1;
pSalarySys1 = pSalarySys2 = nullptr;
return 0;
}
经过单件模式的改写, SalarySys 类的构造函数已经变成私有的,在主函数中就不能直接使用 new 关键字来创建一个实例对象,而只能通过它提供的公有的 getInstance()函数来获得这个类的唯一实例对象。