概述
面向对象编程中,常见的设计原则有六个:
单一职责原则、开闭原则、合成原则、里氏替换原则、依赖倒转原则、接口原则、迪米特法则。
这里只介绍三个,下一篇文章介绍另三个。
单一职责原则
这个很简单,就是一个类的职责只做一类型的事情。注意,是一类型,而不是一件事。
比如上一篇文章中的EmployeeManage类,它的作用就是管理职工,所以里面可以有:初始化职工信息、查询职工、添加职工、删除职工等接口,这些接口属于同一类型,都是对员工的管理。
如果员工需要换等级呢?这个接口当然也可以!
那什么类型的接口不行呢?
比如说获取指定员工的薪水,有些人为了访问方便,直接在这个类里加一个接口,传入员工姓名,返回他的薪水。这就违反了单一职责原则。
程序设计中,这条原则,一定要时刻牢记!重中之重!
程序设计之初,还感觉不太出来,随着程序功能的扩展,尤其是维护的人员有变动的时候,不遵循这条原则的代码,会显得非常换乱不堪,根本没法维护。
里氏替换原则
这个原则可以说是面向对象设计和多态基石。
用人话翻译一下就是:所有用父类的地方,都可以用其子类替代。但是用子类的地方,不一定能用父类替换。
很容易理解,子类是对父类的派生,其功能肯定是大于等于父类的,所以子类的地方不能用父类替换。
为什么一定要强调父类的地方可以用子类替代呢?
这就要从你准备继承的时候说起,父类和子类是不是真的有继承的关系?这个关系,不是语法上的,而是功能上的。
后面和合成原则一起给出例子。
合成原则
这个原则很有用!
这个原则用人话说就是:对于复杂功能的类,尽量用组合,不要用继承。
什么意思呢?
还是用前面那个职工等级的例子来说,其实那个例子写得太过简单,我们不妨稍微扩展一下:
1、员工有部门,部门对于大公司来说,是一个独立的个体,有部门名称、部门预算等等;
2、职级不仅和工资有关,也和员工的权限有关,不同职级,权限不同。
初学面向对象,尤其是C++还可以多重继承,很多人可能会想到如下实现方式:
有个部门类Department;还有有职级类Grade,Grade派生Grade1、Grade2等不同的职级;最后才是职工类Employee,对其也进行派生Employee1、Employee2,不同的职工,多继承自Department核对应的具体Grade类。
这是我在工作中见到的真实例子,虽然不是职工、部门,但功能与这个几乎一致,而且是一个工作了20年+的老工程师写的。看到代码的那一刻,我差点一口老血喷出五米远。
首先,这么设计,就不满足里氏替换原则。
所有涉及到部门的地方,都能用职工替换掉?代码上逻辑可能没错误,但是实际功能,根本不是这么回事。部门的预算,你怎么用员工替换?部门领导换人,怎么用员工替换?
这种设计,是典型的为了面向对象而面向对象,仅仅只是了解了面向对象的语法知识,而根本不知道其实际内涵。
很可惜,现在的软件市场上,尽是这种人。某种语言的偏门语法用得贼溜,以此来炫技,到几万甚至几千行的功能设计时,写出了一坨翔。
上面的功能,应该怎么做呢?其实想想合成原则,等级,说白了只是员工的一个属性而已,这个属性会随着员工能力的提升而变动。另外,部门,也只是员工的一个属性,大一点的公司,员工换部门的事情常有发生。除了这些,员工还有一些基本信息,比如姓名、性别、联系方式等等,这些,才是独属于这个员工不变的,部门、职级等都会改变。
如果把部门和等级当做员工的一个成员变量,多个类合成在一起,组成员工类,是不是更好?
这就是合成原则的内涵。
看下面的代码,还是基于之前的员工功能,做一些扩展优化:
#pragma once
#include <iostream>
#include <list>
#include <string>
#include "iomanip"
class Employee;
/**
* @brief 部门类
*/
class Department
{
public:
Department(const std::string& strName) :m_strName(strName) {}
/// @brief 获取部门名称
std::string GetName() const { return m_strName; }
void AddEmpplyee(const std::string& strNum);
protected:
friend class DepartmentManage;
std::list<const Employee*> m_lstEmployee; //部门的所有员工
std::string m_strName; //部门名称
};
/// @brief 部门管理类
class DepartmentManage
{
public:
static DepartmentManage& GetInstance() {
static DepartmentManage cInstance;
return cInstance;
}
/// @brief 查找某个部门
Department* GetDepartment(const std::string& strName) const {
auto iter_find = std::find_if(m_lstDepartment.begin(), m_lstDepartment.end(),
[strName](const std::unique_ptr<Department>& p) { return strName == p->m_strName; });
if (m_lstDepartment.end() == iter_find)
{
//实际项目中此处要添加报错处理
return nullptr;
}
return iter_find->get();
}
Department* AddDepartment(const std::string& strName)
{
Department* pNew = const_cast<Department*>(GetDepartment(strName));
if (!pNew) //部门不存在,添加
{
m_lstDepartment.push_back(std::make_unique<Department>(strName));
pNew = m_lstDepartment.back().get();
}
return pNew;
}
private:
std::list<std::unique_ptr<Department>> m_lstDepartment;
};
/**
* @brief 职级类
*/
class Grade
{
public:
virtual ~Grade() = default;
/**
* 初始化数据
*/
virtual void InitData() = 0;
/// @brief 获取薪水
double GetSalary(double dRatio) const {
return m_dPay_Base + m_dPay_Performance * dRatio;
}
/// @brief 修改数据库的权限
bool Authority_ChangeDB() const { return m_bChangeDB; }
std::string GetName() const { return m_strName; }
protected:
bool m_bChangeDB{ false };
double m_dPay_Base; //基本工资
double m_dPay_Performance;
std::string m_strName; //职级名称,作为职级的唯一标识(也可以用int标识)
};
/**
* 职级1
*/
class Grade1 : public Grade
{
public:
void InitData() override {
m_bChangeDB = false;
m_dPay_Base = 5000;
m_dPay_Performance = 3000;
m_strName = "P1";
}
};
/**
* 职级2
*/
class Grade2 : public Grade
{
public:
void InitData() override {
m_bChangeDB = true;
m_dPay_Base = 8000;
m_dPay_Performance = 5000;
m_strName = "P2";
}
};
/**
*@brief 职级管理类
*/
class GradeManage
{
public:
static GradeManage& GetInstance() {
static GradeManage cInstance;
return cInstance;
}
void InitData()
{
//实际项目中,此处极可能是用配置文件实现
m_lstGrade.push_back(std::make_unique<Grade1>());
m_lstGrade.back()->InitData();
m_lstGrade.push_back(std::make_unique<Grade2>());
m_lstGrade.back()->InitData();
}
const Grade* GetGrade(const std::string& strName) const{
auto iter_find = std::find_if(m_lstGrade.begin(), m_lstGrade.end(),
[strName](const std::unique_ptr<Grade>& p) {return strName == p->GetName(); });
if (iter_find == m_lstGrade.end())
{
//此处需要添加报错
return nullptr;
}
return (*iter_find).get();
}
//缺少添加、删除和修改职级的接口
private:
std::list<std::unique_ptr<Grade>> m_lstGrade;
};
/**
* @brief 员工类
*/
class Employee
{
public:
Employee(const std::string& strName, const std::string& strNum):m_strName(strName), m_strNumber(strNum) {}
/// @brief 设置职级
bool SetGrade(const std::string& strName) {
m_pGrade = GradeManage::GetInstance().GetGrade(strName);
return (m_pGrade != nullptr);
}
/// @brief 设置部门
bool SetDepartment(const std::string& strName) {
Department* p = DepartmentManage::GetInstance().GetDepartment(strName);
if (!p) //无对应部门
return false;
p->AddEmpplyee(m_strNumber);
m_pDepartment = p;
return true;
}
std::string GetName() const { return m_strName; }
std::string GetNumber() const{ return m_strNumber; }
double GetSalary(double dRatio) {return m_pGrade->GetSalary(dRatio);}
const Department* GetDepartment() const { return m_pDepartment; }
protected:
friend class EmployeeManage;
std::string m_strName; //姓名
std::string m_strNumber; //员工编号(员工的唯一标志)
const Grade* m_pGrade{ nullptr }; //等级
const Department* m_pDepartment{ nullptr }; //所属部门
};
class EmployeeManage
{
public:
static EmployeeManage& GetInstance() {
static EmployeeManage cInstance;
return cInstance;
}
/**
* @brief 添加员工
* @param strName 员工姓名
* @param strGrade 职级
* @param strDepartment 部门
*/
void AddEmployee(const std::string& strName, const std::string& strGrade, const std::string& strDepartment) {
std::string strNum = std::to_string(++m_iCount);
while (strNum.size() < 9)
strNum = "0" + strNum;
m_lstEmployee.push_back(std::make_unique<Employee>(strName, strNum));
m_lstEmployee.back()->SetGrade(strGrade);
m_lstEmployee.back()->SetDepartment(strDepartment);
}
/// @brief 查找职工
Employee* GetEmployee(const std::string& strNum) {
auto iter_find = std::find_if(m_lstEmployee.begin(), m_lstEmployee.end(),
[strNum](const std::unique_ptr<Employee>& p) {return strNum == p->m_strName; });
if (iter_find == m_lstEmployee.end())
return nullptr;
return iter_find->get();
}
const std::list<std::unique_ptr<Employee>>& GetAllEmployee() const { return m_lstEmployee; }
protected:
EmployeeManage() = default;
private:
std::list<std::unique_ptr<Employee>> m_lstEmployee;
int m_iCount{ 100 };
};
void Department::AddEmpplyee(const std::string& strNum) {
const Employee* p = EmployeeManage::GetInstance().GetEmployee(strNum);
if (p)
m_lstEmployee.push_back(p);
else
{
//此处添加报错信息
}
}
void InitDepartment()
{
DepartmentManage& rManage = DepartmentManage::GetInstance();
rManage.AddDepartment("财务部");
rManage.AddDepartment("研发部");
rManage.AddDepartment("综管部");
rManage.AddDepartment("测试部");
rManage.AddDepartment("财务部");
}
int main()
{
using std::cout;
InitDepartment();
GradeManage::GetInstance().InitData();
EmployeeManage& rManage = EmployeeManage::GetInstance();
rManage.AddEmployee("David", "P1", "财务部");
rManage.AddEmployee("Lucas", "P1", "测试部");
rManage.AddEmployee("Tom", "P2", "研发部");
auto printInfo = [&rManage](const std::string& strName, double dRatio){
Employee* p = rManage.GetEmployee(strName);
if (p)
{
cout << "姓名:" << std::setw(8) << p->GetName();
cout << ",编号:" << p->GetNumber();
cout << ", 所属部门:" << p->GetDepartment()->GetName();
cout << ", 薪资:" << p->GetSalary(dRatio) << "n";
}
else
{
std::cout << "【Error】未找到员工“" << strName << "”的信息!n";
}
};
printInfo("David", 1.1);
printInfo("Lucas", 0.8);
printInfo("Tom", 0.8);
cout << "------------------------------------------------------n";
rManage.GetEmployee("David")->SetGrade("P2");
printInfo("David", 1);
system("pause");
return 0;
}
运行结果如下:
这段代码略上,接近300行,但不难,可以说非常简单。
首先,定义了一个部门类Department和部门管理类DepartmentManage,部门类Department中有一个list,list中存放的是当前部门的职工信息。但是需要注意,职工的创建和管理并不是由部门类实现。为什么?原因有二:1、员工的创建和管理不属于部门的职能,本着单一职能原则,所以不能放在这里;2、员工和部门并没有必然的关系,员工可以换部门。
接着便是定义职级类和职级管理类,职级类有三个:Grade、Grade1、Grade2,当然,如果仅仅是本例中的功能,职级类完全不需要搞得这么复杂,甚至不需要职级类都行。但后面的例子会对职级进行扩展,慢慢就会发现定义成类的好处了。
最后定义的是员工类和员工管理类,管理类负责员工的创建、查找等。员工类里有等级和部门,注意,这里面用的是指针。为何如此?有两个原因:1、直接用类对象的话比较耗空间;2、万一部门发生一些变化,比如部门的名称换了、领导换了,所以员工岂不是都要跟着改?
这段程序稍稍有一些设计的意味在里面,比如员工类里面存放的是部门和职级的指针而不是对象等等,但是还有许多可以修改完善的地方,大的结构已经大差不差。如果有更好的设计,欢迎交流。
最后
以上就是危机店员为你收集整理的设计模式(五)三个设计原则 单一职责、里氏替换原则、合成原则单一职责原则里氏替换原则合成原则的全部内容,希望文章能够帮你解决设计模式(五)三个设计原则 单一职责、里氏替换原则、合成原则单一职责原则里氏替换原则合成原则所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复