前言
对于可控的事情,要保持谨慎;
对于不可控的事情,要保持乐观。
人只能做自己能力范围内的事情,要接受这个事实,并且以乐观的态度,去应对这一切。。
❝将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。 ❞
简单说,就是已经存在稳定的数据类。由于外部需求,需要访问特定的类成员。希望在不改变原数据类接口,仅通过增加外部模块实现需求。此模式,是行为模式中最复杂的一种模式。
此模式主要用于在存在多个同类型的数据类情况下,统一对这些数据类某个成员属性的访问方式。有助于将数据代码与业务代码解耦,可在不修改数据类的情况下自由增加访问方式业务。
上述分析了,此模式多用于存在多个同类型数据类,只访问这些类某个成员属性。例:电脑管家检测电脑,要一项一项检测,先功能检测、再驱动检测。其中功能检测,只检测各配件(GPU、声卡)的功能是否正常。驱动检测,只检测各配件驱动是否正常。(具体如何检测的,这里不做关注)
由上述电脑管家检测场景,可构建类图:
「数据源类」: GPU(CPartGpu)、声卡(CPartSoundCard),两者可抽象出基类电脑组件(CComputePartBase); 「访问类」: 功能访问(CVisitorFunction)、驱动访问(CVisitorDriver),两者可抽象访问基类(CVisitorBase); 「管理类」: 电脑管家(CSafeMgr)。
注: 在最初的访问者模式类图没有管理类的角色,这里为了方便客户端使用接口,才增加此类。实际场景中,只要运用到访问者模式思想即可,没有必要参照其实现方式生搬硬套。
「编程环境」
「工程结构」
Visitor/
├── compute_part_base.h
├── main.cc
├── Makefile
├── part_gpu.cc
├── part_gpu.h
├── part_sound_card.cc
├── part_sound_card.h
├── safe_manager.cc
├── safe_manager.h
├── visitor_base.h
├── visitor_driver.cc
├── visitor_driver.h
├── visitor_function.cc
└── visitor_function.h
「源数据接口」
class CComputePartBase
{
public:
CComputePartBase() {}
virtual ~CComputePartBase() {}
virtual std::string GetName() = 0;
virtual void Accept(CVisitorBase *visitor) = 0;
};
class CPartGpu : public CComputePartBase
{
public:
explicit CPartGpu(std::string name, int driver, int function);
~CPartGpu();
std::string GetName();
int CheckDriver();
int CheckFunction();
void Accept(CVisitorBase *visitor);
private:
std::string mName;
int mDriver;
int mFunction;
};
其内部实现了Accept()接口,看下Accept()做了什么事情。
void CPartGpu::Accept(CVisitorBase *visitor)
{
if (NULL != visitor) {
visitor->VisitGpu(this);
} else {
GPU_LOGE("visitor is NULL!\n");
}
}
「访问者接口」
class CVisitorBase
{
public:
virtual std::string GetName() = 0;
virtual int VisitGpu(CPartGpu *gpu) = 0;
virtual int VisitSoundCard(CPartSoundCard *soundCard) = 0;
};
class CVisitorDriver : public CVisitorBase
{
public:
CVisitorDriver();
~CVisitorDriver();
std::string GetName();
int VisitGpu(CPartGpu *gpu);
int VisitSoundCard(CPartSoundCard *soundCard);
private:
std::string mName;
};
驱动访问者是具体的访问者类,其会实现VisitGpu、VisitSoundCard但是其只关心Gpu和SoundCard的驱动属性。而功能访问者只关心Gpu和SoundCard的功能属性。
查看下VisitGpu()、VisitSoundCard()接口的实现:
int CVisitorDriver::VisitGpu(CPartGpu *gpu)
{
if (gpu->CheckDriver() <= 0) {
DRV_LOG("%s of Gpu Failed!\n", this->GetName().c_str());
} else {
DRV_LOG("%s of Gpu Success!\n", this->GetName().c_str());
}
return 0;
}
int CVisitorDriver::VisitSoundCard(CPartSoundCard *soundCard)
{
if (NULL == soundCard) {
DRV_LOGE("soundCard is NULL!\n");
}
if (soundCard->CheckDriver() <= 0) {
DRV_LOG("%s of SoundCard Failed!\n", this->GetName().c_str());
} else {
DRV_LOG("%s of SoundCard Success!\n", this->GetName().c_str());
}
return 0;
}
「管理类」
typedef enum
{
ITEM_DRIVER = 0,
ITEM_FUNCTION,
ITEM_ALL,
}ECheckItem;
class CSafeMgr
{
public:
CSafeMgr();
~CSafeMgr();
void AddPartArray(CComputePartBase *);
void Accept(CVisitorBase *visitor);
void CheckItem(ECheckItem item);
private:
CPartGpu *mpGpu;
CPartSoundCard *mpSoundCard;
CVisitorDriver theDriverCheck;
CVisitorFunction theFunctionCheck;
std::vector<CComputePartBase *> mPartArray;
};
电脑管家就是管理类的作用,主要用于整理所有源数据对象与访问者对象。实现各个场景接口,为客户端提供简单易用的接口。
具体实现:
void CSafeMgr::AddPartArray(CComputePartBase *pPart)
{
if (NULL == pPart) {
MGR_LOGE("pPart is NULL!\n");
return ;
}
mPartArray.push_back(pPart);
}
void CSafeMgr::Accept(CVisitorBase *visitor)
{
vector<CComputePartBase *>::iterator it;
for (it = mPartArray.begin(); it != mPartArray.end(); ++it) {
(*it)->Accept(visitor);
}
}
void CSafeMgr::CheckItem(ECheckItem item)
{
switch (item)
{
case ITEM_DRIVER:
Accept(&theDriverCheck);
break;
case ITEM_FUNCTION:
Accept(&theFunctionCheck);
break;
case ITEM_ALL:
Accept(&theDriverCheck);
Accept(&theFunctionCheck);
break;
default:
MGR_LOGW("No this item!\n");
break;
}
}
「客户端代码」
int main(int argc, char *argv[])
{
CSafeMgr theSafeMgr;
theSafeMgr.CheckItem(ITEM_ALL);
return 0;
}
因为设计了CSafeMgr接口,main代码就简单易懂,只是运用CSafeMgr检测指定项即可。其无需关注有多少配件和多少测试项,只需关注结果。
用心感悟,认真记录,写好每一篇文章,分享每一框干货。
更多文章内容包括但不限于C/C++、Linux、开发常用神器等,可进入“开源519公众号”聊天界面输入“文章目录” 或者 菜单栏选择“文章目录”查看。公众号后台聊天框输入本文标题,在线查看源码。