适配器模式:讲一个类的接口转换成客户期待的另一个接口,从而从根本上解决两个类无法在一起工作的情况。
最近在做一个任务管理的项目,那么任务管理就应该有任务的基本属性进行配置、修改、获取属性信息等基本操作。咱们主要查看任务的基本信息的获取这部分内容。
首先定义一个任务信息获取的接口类:
//定义信息基类
class IInfo {
public:
IInfo();
~IInfo();
//获取任务ID
virtual int getID();
//获取任务名称
virtual std::string getName();
//获取任务的开始日期
virtual std::string getStartDate();
//获取任务的结束日期
virtual std::string getEndDate();
};
//定义信息基类
class IInfo {
public:
IInfo();
~IInfo();
//获取任务ID
virtual int getID();
//获取任务名称
virtual std::string getName();
//获取任务的开始日期
virtual std::string getStartDate();
//获取任务的结束日期
virtual std::string getEndDate();
};
然后定义一个关于项目信息的结构体:
struct ProjInfo {
int projID;//项目ID
std::string projName;//项目名称
std::string projStartDate;//项目开始日期
std::string projEndDate;//项目结束日期
};
定义项目类:
//项目类
class ProjectInfo : public IInfo {
public:
ProjectInfo();
ProjectInfo(int id, std::string name, std::string startDate, std::string endDate);
//获取任务ID
int getID()override;
//获取任务名称
std::string getName()override;
//获取任务的开始日期
std::string getStartDate()override;
//获取任务的结束日期
std::string getEndDate()override;
private:
ProjInfo* _projInfo = nullptr;
};
ProjectInfo::ProjectInfo(int id, std::string name, std::string startDate, std::string endDate) {
_projInfo = new ProjInfo;
_projInfo->projID = id;
_projInfo->projName = name;
_projInfo->projStartDate = startDate;
_projInfo->projEndDate = endDate;
}
ProjectInfo::ProjectInfo() {
}
int ProjectInfo::getID() {
if (_projInfo!=nullptr) {
return _projInfo->projID;
}
return -1;
}
std::string ProjectInfo::getName() {
if (_projInfo != nullptr) {
return _projInfo->projName;
}
return "";
}
std::string ProjectInfo::getStartDate() {
if (_projInfo != nullptr) {
return _projInfo->projStartDate;
}
return "";
}
std::string ProjectInfo::getEndDate() {
if (_projInfo != nullptr) {
return _projInfo->projEndDate;
}
return "";
}
看看如何使用:
int main() {
IInfo* info1 = new ProjectInfo(1, "工程1", "2018-1-1", "2018-12-30");
printf("当前正在建的工程有:%s 开始日期:%s 结束日期:%s\n"
, info1->getName().c_str()
, info1->getStartDate().c_str()
, info1->getEndDate().c_str());
return 0;
}
运行一下,可以查看接项目的一些基本情况:
但是呢,现在公司要和别的单位进行合作共同开发这个软件项目,他们同样也有任务这个概念,但是他们的任务定义和自己的任务定义是不一样的,怎样才能很好的把它们结合在一起呢?
先看看他们的任务定义:
class TrueProject {
public:
TrueProject();
~TrueProject();
void setProjectName(std::string name);
std::string getProjectName();
void setStartYear(int y);
int getStartYear();
void setStartMonth(int m);
int getStartMonth();
void setStartDay(int d);
int getStartDay();
void setEndYear(int y);
int getEndYear();
void setEndMonth(int m);
int getEndMonth();
void setEndDay(int d);
int getEndDay();
protected:
std::string _name;
int _startYear;
int _startMonth;
int _startDay;
int _endYear;
int _endMonth;
int _endDay;
};
TrueProject::TrueProject() {
}
TrueProject::~TrueProject() {
}
void TrueProject::setProjectName(std::string name) {
_name = name;
}
std::string TrueProject::getProjectName() {
return _name;
}
void TrueProject::setStartYear(int y) {
_startYear = y;
}
int TrueProject::getStartYear() {
return _startYear;
}
void TrueProject::setStartMonth(int m) {
_startMonth = m;
}
int TrueProject::getStartMonth() {
return _startMonth;
}
void TrueProject::setStartDay(int d) {
_startDay = d;
}
int TrueProject::getStartDay() {
return _startDay;
}
void TrueProject::setEndYear(int y) {
_endYear = y;
}
int TrueProject::getEndYear() {
return _endYear;
}
void TrueProject::setEndMonth(int m) {
_endMonth = m;
}
int TrueProject::getEndMonth() {
return _endMonth;
}
void TrueProject::setEndDay(int d) {
_endDay = d;
}
int TrueProject::getEndDay() {
return _endDay;
}
有两种办法可以实现:
1. 继承
同时继承接口基类和客户类,并且实现接口类中的函数:
class ProjectAdaper :public IInfo, public TrueProject {
public:
ProjectAdaper();
~ProjectAdaper();
//获取任务ID
int getID()override;
//获取任务名称
std::string getName()override;
//获取任务的开始日期
std::string getStartDate()override;
//获取任务的结束日期
std::string getEndDate()override;
};
ProjectAdaper::ProjectAdaper() {
}
ProjectAdaper::~ProjectAdaper() {
}
int ProjectAdaper::getID() {
return -1;
}
std::string ProjectAdaper::getName() {
return _name;
}
std::string ProjectAdaper::getStartDate() {
char ch[32] = {};
sprintf_s(ch, "%d-%d-%d", _startYear, _startMonth, _startDay);
return std::string(ch);
}
std::string ProjectAdaper::getEndDate() {
char ch[32] = {};
sprintf_s(ch, "%d-%d-%d", _endYear, _endMonth, _endDay);
return std::string(ch);
}
看看客户端调用:
int main() {
ProjectAdaper* info2 = new ProjectAdaper();
info2->setProjectName("工程2");
info2->setStartYear(2018);
info2->setStartMonth(2);
info2->setStartDay(18);
info2->setEndYear(2019);
info2->setEndMonth(2);
info2->setEndDay(2);
printf("当前正在建的工程有:%s 开始日期:%s 结束日期:%s\n"
, info2->getName().c_str()
, info2->getStartDate().c_str()
, info2->getEndDate().c_str());
return 0;
}
在不改变客户端代码的情况下,同样可以实现使用同一的接口查看这个任务对象的基本信息。
2. 委托
使用委托的实现也需要继承接口基类:
class ProjectAdapter2 : public IInfo {
public:
ProjectAdapter2(TrueProject*proj);
~ProjectAdapter2();
//获取任务ID
int getID()override;
//获取任务名称
std::string getName()override;
//获取任务的开始日期
std::string getStartDate()override;
//获取任务的结束日期
std::string getEndDate()override;
private:
TrueProject* _trueProject;
};
ProjectAdapter2::ProjectAdapter2(TrueProject*proj) {
_trueProject = proj;
}
ProjectAdapter2::~ProjectAdapter2() {
}
int ProjectAdapter2::getID() {
return -1;
}
std::string ProjectAdapter2::getName() {
return _trueProject->getProjectName();
}
std::string ProjectAdapter2::getStartDate() {
char ch[32] = {};
sprintf_s(ch, "%d-%d-%d", _trueProject->getStartYear(), _trueProject->getStartMonth(), _trueProject->getStartDay());
return std::string(ch);
}
std::string ProjectAdapter2::getEndDate() {
char ch[32] = {};
sprintf_s(ch, "%d-%d-%d", _trueProject->getEndYear(), _trueProject->getEndMonth(), _trueProject->getEndDay());
return std::string(ch);
}
客户端调用:
int main() {
TrueProject* trueProj = new TrueProject;
trueProj->setProjectName("工程3");
trueProj->setStartYear(2018);
trueProj->setStartMonth(4);
trueProj->setStartDay(18);
trueProj->setEndYear(2019);
trueProj->setEndMonth(5);
trueProj->setEndDay(5);
ProjectAdapter2* info3 = new ProjectAdapter2(trueProj);
printf("当前正在建的工程有:%s 开始日期:%s 结束日期:%s\n"
, info2->getName().c_str()
, info2->getStartDate().c_str()
, info2->getEndDate().c_str());
return 0;
}
总结:
无论是通过继承方式还是通过委托方式实现接口的装换适配,类
必须遵循里氏替代和依赖倒置原则
。只有这样在使用适配器的场合才能做最小的改动,如果没有遵循以上两个原则,即使在适用适配器的场合项目也会做出很大的改造。
以上的示例就是因为遵循了这两个原则,只有在刚开始创建任务对象的时候会有所不同,在具体使用这个对象(查看这个对象基本信息)的时候,没有什么不同,都是一样的。
咱们把这两种方式放在一起看一下:
int main() {
IInfo* info1 = new ProjectInfo(1, "工程1", "2018-1-1", "2018-12-30");
printf("当前正在建的工程有:%s 开始日期:%s 结束日期:%s\n"
, info1->getName().c_str()
, info1->getStartDate().c_str()
, info1->getEndDate().c_str());
ProjectAdaper* info2 = new ProjectAdaper();
info2->setProjectName("工程2");
info2->setStartYear(2018);
info2->setStartMonth(2);
info2->setStartDay(18);
info2->setEndYear(2019);
info2->setEndMonth(2);
info2->setEndDay(2);
printf("当前正在建的工程有:%s 开始日期:%s 结束日期:%s\n"
, info2->getName().c_str()
, info2->getStartDate().c_str()
, info2->getEndDate().c_str());
TrueProject* trueProj = new TrueProject;
trueProj->setProjectName("工程3");
trueProj->setStartYear(2018);
trueProj->setStartMonth(4);
trueProj->setStartDay(18);
trueProj->setEndYear(2019);
trueProj->setEndMonth(5);
trueProj->setEndDay(5);
ProjectAdapter2* info3 = new ProjectAdapter2(trueProj);
printf("当前正在建的工程有:%s 开始日期:%s 结束日期:%s\n"
, info2->getName().c_str()
, info2->getStartDate().c_str()
, info2->getEndDate().c_str());
return 0;
}
通过以上代码实例可以得知,在获取项目基本信息的时候,使用的接口都是一样的。
适配器模式的优点:
1.可以使不相干的两个类可以一起工作;
2.隐藏了目标类的细节;
3.提高了类的复用性。
再次提醒一下:
类的设计一定要符合里氏替代原则和依赖倒置原则
。