2017-09-04 1 views
0

캡슐화를 실제로 이해할 수 없다는 사실에 귀결되는 디자인 문제가 있습니다. 버그를 포함 할 수있는 테스트하지 않은 C++ 코드를 살펴 보자 :캡슐화와 대용량 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); 
     } 
    } 
} 

의 I는 포인터에 의존 왜 이유 각 vertex이 큰 객체가 될 수 있기 때문이다 (예를 들어 각 정점 10000 개 다른 정점에 인접한 각은 일부 대형을 포함 사람들의 인물 사진 1MB와 같은 데이터).

1) 한편으로는 외부 객체가 꼭지점과 그 데이터를 수정할 수 있고 꼭지점이 그래프의 구성 요소이기 때문에 그래프를 수정할 수 있다는 의미에서 나쁜 캡슐화라고 생각합니다.

2) 또 다른 손에서, 그래프 오브젝트가 실제로 숨기려고하는 것은 그것이 맵을 통해 정점 컬렉션을 구현하는 방법이라고 주장 할 수 있습니다. 그래프의 API를 일관되게 유지하고이 맵을 포인터로 반환하지 않는 한 (개별 정점을 반환해야하는 경우 포인터를 반환하거나 모든 정점이 필요한 경우 세트를 반환하는 경우)지도를 비공개로 만드는 목적이 제공됩니다.

캡슐화의 이러한 인수/정의 중 올바른 것은 어느 것입니까?

개체에 여러 개의 큰 개체가있는 경우 개체를 반환하는 가장 좋은 방법은 무엇입니까?

많은 정점을 처리하려면 응용 프로그램이 빠르다고 가정합니다.

사이드 트랙 논의로 인한 주된 질문은 캡슐화가 무엇인지, 그리고 위의 코드가 어떻게 메모리에 관한 코드 구현, 라이브러리 선택, 구문 ...에 관한 코드 구현 방법이 아닌지에 대한이 원칙을 위반/철저히 고려하지 않고 현장에서 구성됩니다.

+2

캡슐화해야 할 것은 디자인 결정입니다. 객체를 숨기는 것이 디자인 목표의 반대이므로 객체에 대한 액세스를 허용하기 위해'std :: vector'를 잘못 처리 할 수 ​​없습니다. 메모리 관리 숨기기입니다. 디자인 목표가 없으면 이러한 질문에 답할 수 없습니다. – nwp

+0

uhmn ... 감사합니다, @nwp. 캡슐화가 상대적 개념 즉, 캡슐화 할 디자인을 결정할 때 어떤 결정을 내릴지에 따라 달라질 수 있다고 말하는 것이 맞습니까? – TuanDT

+2

"디자인 결정"이 잘못된 용어 일 수 있습니다. "외부 요구 사항"이 더 좋을 것입니다. 프로그램이나 라이브러리 사용자는 일반적인 사용 사례에 대한 메모리/CPU/학습의 오버 헤드가 적은 그래프를 수학적으로 저장, 탐색 및 조작해야 할 수 있습니다. 그래서 그것이 당신이 그들에게 주어야 할 것입니다. 개인적으로 나는 사용자가지도에 액세스 할 수있게하려고합니다. 사용자에게 많은 힘을줍니다. 'Graph'의 불변량을 망칠 너무 쉽게하기 때문에 그렇게 할 수 없다면 가능한 한 가깝게하려고합니다 (const_iterator 쌍을 반환 하시겠습니까?). – nwp

답변

0

음, 디자인을 개선 할 수있는 방법이 있습니다.

먼저 데이터에 대한 읽기 전용 액세스 만 허용하거나 단순히 const 개체를 반환하는 "인터페이스"또는 프록시를 정의 할 수 있습니다. 선택할 수있는 옵션은 실제로 수행 할 수있는 작업, 가능한 성능 영향 및 필요한 변경에 대한 보호 정도에 따라 다릅니다.

둘째, 스마트 포인터를 사용하여 정점이 외부 참조 인 경우 맵에서 제거 될 때 정점이 제거되지 않도록 할 수 있습니다. 단일 스레드 코드를 가정

, 당신은에지도 선언 변경할 수 있습니다 : const 보호가 충분히이라고 가정

std::map<int, std::shared_ptr<Vertex>> mapVertexIdToVertexPointer; 

을, 당신이 당신의 기능을 변경할 수 있습니다

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; 
}; 

이 방법은 일반적인 작업 코드가 복잡 할 경우 가장 좋은 방법 일 수 있습니다.

+0

고마워요. 많은 통찰력이 있습니다. 결국,'Graph' 객체를 구현하는 방법에 어떤 선택도 영향을 미치지 않습니다. 단지'map'을'vector'로 변경하고'getAllVertices' 함수를 다시 구현할 수 있다는 것입니다 – TuanDT

+0

귀하의 의견을 이해할 수 없으므로 포인터를 반환하지 않는다고 말한 적이 있습니다 – Phil1970

+0

필자는 반복자를 의미했습니다 : p 죄송합니다, typo – TuanDT