我有一个设计问题,归结为我不太了解封装这一事实。考虑未经测试的C ++代码,它可能包含错误:
class Graph{
private:
map<int, Vertex*> mapVertexIdToVertexPointer;
public:
Vertex* findVertexById(int id){
return mapVertexIdToVertexPointer.find(id)->second;
}
void getAllVertexPtrs(set<Vertex*>& setVertices){
for(const auto& it : mapVertexIdToVertexPointer)
{
setVertices.insert(it.second);
}
}
}
我诉诸指针的原因是因为每个vertex
可能是一个大对象(比如说每个顶点与10000个其他顶点相邻,或者每个顶点都包含一些大数据,例如1MB的人物图片&#39; s肖像)。
1)一方面,我认为这是一个糟糕的封装,因为外部对象可以修改顶点及其数据,然后修改图形,因为顶点只是图形的组成部分。
2)另一方面,我也可以说Graph对象真正试图隐藏的是它如何通过地图实现其顶点集合。只要我们保持图形的API一致并且不将此贴图作为指针返回(因此,如果需要返回单个顶点则返回指针,或者如果需要所有顶点则返回集合),将地图设为私有的目的是
封装的哪些参数/定义是正确的?
如果对象包含多个大对象,返回对象的最佳做法是什么?
假设应用程序应该快速处理许多顶点。
注意由于侧面讨论:主要问题是什么是封装,以及上面的代码如何违反/违反此原则而不是如何实现有关内存,库的选择,语法的代码...因为它是在没有经过深思熟虑的情况下当场弥补。
答案 0 :(得分:0)
嗯,有一些方法可以改善设计。
首先,您可以定义一个“接口”或代理,它只提供对数据的只读访问权限,或只返回const
个对象。选择哪个选项实际上取决于您希望能够做什么,可能的性能影响以及对所需更改的保护程度。
其次,你可以使用智能指针来确保当它们是对它们的任何外部引用时,从地图中删除顶点时不会销毁顶点。
假设单线程代码,您可以将地图声明更改为:
std::map<int, std::shared_ptr<Vertex>> mapVertexIdToVertexPointer;
假设const
保护足够,您可以将功能更改为:
std::shared_ptr<const Vertex> findVertexById(int id);
void getAllVertexPtrs(std::set<shared_ptr<Vertex>>& setVertices);
我不确定您是否需要在这种情况下重新定义比较... 可疑您返回set
指针。也许你想确保它们是独一无二的......另外,你是否想要修改返回的set
。如果没有,那么返回迭代器将提供更好的封装。
相反,作为替代方案,您可以考虑使用一个函数来为每个顶点应用函数。类似的东西:
void forEachVertex(std::func<const Vertex &> fnToApply);
或者,如果代码很简单并且您希望获得最佳性能,那么您可能更喜欢
template <class F>
void forEachVertex(F fnToApply);
或者,如果virtual
函数开销是可接受的并且您有很多外部操作,那么抽象类可能是一个很好的解决方案:
class AbstractVertexAction
{
public:
virtual void DoAction(const Vertext &) = 0;
};
如果典型操作的代码很复杂,这种方法可能是最好的方法。