C++设计模式 – 适配器模式详解

  • Post author:
  • Post category:其他



适配器模式:讲一个类的接口转换成客户期待的另一个接口,从而从根本上解决两个类无法在一起工作的情况。

最近在做一个任务管理的项目,那么任务管理就应该有任务的基本属性进行配置、修改、获取属性信息等基本操作。咱们主要查看任务的基本信息的获取这部分内容。

首先定义一个任务信息获取的接口类:

//定义信息基类

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.提高了类的复用性。

再次提醒一下:

类的设计一定要符合里氏替代原则和依赖倒置原则



版权声明:本文为wb175208原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。