2013-07-04 1 views
2

순수 가상 메서드가있는 클래스 피쳐가 있습니다.여러 클래스가 다양한 API를 사용하여 부모 클래스를 구현합니다.

class Feature { 
public: 
    virtual ~Feature() {} 
    virtual const float getValue(const vector<int>& v) const = 0; 
}; 

이 클래스는 FeatureA 및 FeatureB와 같은 여러 클래스로 구현됩니다. 별도의 컴퓨터 클래스 (단순화 된)는 getValue 메서드를 사용하여 계산을 수행합니다.

class Computer { 
public: 
    const float compute(const vector<Feature*>& features, const vector<int>& v) { 
    float res = 0; 
    for (int i = 0; i < features.size(); ++i) { 
     res += features[i]->getValue(v); 
    } 
    return res; 
    } 
}; 

는 지금, 나는 FeatureC을 구현하고 싶은 생각하지만 난이을 getValue 방법에 대한 자세한 내용이 필요하다는 것을 알고 있습니다. 나는 물론 기능에 getValue의 서명을 수정할 수 있습니다

const float getValue(const vector<int>& v, const vector<int>& additionalInfo) const; 

처럼 FeatureC의 방법이 보인다 FeatureA, FeatureB 매개 변수로 additionalInfo을하고 또한 계산 방법에서 매개 변수로 additionalInfo을 추가 할 수 있습니다. 그러나 나중에 추가 정보가 필요한 FeatureD를 구현하려는 경우 나중에 해당 서명을 모두 수정해야 할 수도 있습니다. 이것에 대한 좀 더 우아한 해결책이 있는지 또는 추가 읽기를 위해 나를 가리킬 수있는 알려진 디자인 패턴이 있는지 궁금합니다.

답변

2

당신은 적어도 두 가지 옵션이 있습니다을 :

또 다른 옵션이 더 많은 옵션을 가지고 기본 클래스 Feature을 확장하는 것이다

  1. 단일 벡터를 getValue()으로 전달하는 대신 struct를 전달하십시오. 이 구조체에서는 오늘 벡터와 더 많은 데이터를 넣을 수 있습니다. 물론, 프로그램의 특정 실행에 여분의 필드가 필요하지 않으면 계산할 필요가 낭비 일 수 있습니다. 어쨌든 모든 데이터를 항상 계산해야하는 경우 (즉, 항상 하나의 FeatureC가있는 경우) 성능상의 불이익을 부과하지 않습니다.
  2. getValue()에 전달하면 필요한 데이터를 가져 오는 메소드가있는 객체에 대한 참조가됩니다. 이 개체는 컴퓨터 자체 일 수도 있고 더 간단한 프록시 일 수도 있습니다. 그런 다음 getValue() 구현물은 필요한 것을 정확히 요청할 수 있으며 지연 계산이 가능합니다. 게으름은 어떤 경우에는 낭비되는 계산을 제거하지만, 이런 식으로하는 전반적인 구조는 다양한 데이터를 얻기 위해 (가상의) 함수를 호출해야하기 때문에 약간의 일정한 오버 헤드를 부과합니다.
+0

감사합니다. 훌륭한 옵션! – needsaname

+0

대단히 환영합니다. 도움이되는 답변을 모두 선택하여 투표하십시오. (궁극적으로 가장 관련성이 높은 답변을 수락하십시오). StackOverflow에 오신 것을 환영합니다. –

1

클래스를 기반으로 다른 메소드를 호출하기 위해 Feature 클래스 계층의 사용자에게 다형성을 무시하도록 요구합니다. dynamic_cast<>()을 시작하면 디자인을 다시 생각해야합니다.

하위 클래스가 호출자로부터 만 얻을 수있는 정보를 필요로하는 경우 getValue() 메서드를 변경하여 additionalInfo 인수를 변경하고 중요하지 않은 클래스에서 해당 정보를 무시해야합니다.

FeatureC가 다른 클래스 나 함수를 호출하여 additionalInfo를 얻을 수있는 경우, 일반적으로 더 알아 두어야 할 클래스의 수를 제한하기 때문에 더 나은 방법입니다. 아마도 데이터는 해당 생성자를 통해 또는 싱글 톤 객체를 통해 액세스 권한이 부여 된 객체에서 사용할 수 있거나 함수를 호출하여 계산할 수 있습니다. 최선의 접근법을 찾는 것은 사례에 대한 좀 더 많은 지식이 필요합니다.

+0

"서브 클래스는 단지 호출자에서 얻을 수있는 정보가 필요한 경우, 당신은 additionalInfo 인수를 취할 수있는 getValue() 메소드를 변경해야 , 중요하지 않은 클래스에서 그 정보를 단순히 무시합니다. ": 예, 그게 내가 피하고 싶습니다. "그렇지 않으면, FeatureC가 다른 클래스 나 함수를 호출하여 additionalInfo를 얻을 수 있다면 더 나은 접근 방법입니다." FeatureC가 constructor에서 additionalInfo를 얻는 것을 의미합니까? – needsaname

0

가상 함수는 올바르게 작동하려면 정확히 동일한 "서명"(동일한 매개 변수와 동일한 반환 유형)이 있어야합니다. 그렇지 않으면, 당신은 단지 당신이 원하는 것이 아닌 "새로운 멤버 함수"를 얻습니다.

여기서 중요한 질문은 "호출 코드가 추가 정보가 필요한 것을 어떻게 알 수 있습니까?"입니다.

몇 가지 방법으로 문제를 해결할 수 있습니다. 첫 번째 방법은 필요 여부에 관계없이 항상 const vector <int>& additionalInfo을 전달하는 것입니다.

FeatureC의 경우를 제외하고 모든 additionalInfo 없기 때문에 즉, 가능하지 않으면, 당신은 "옵션"매개 변수를 가질 수 - 벡터 (vector<int>* additionalInfo)에 대한 포인터를 사용하는 의미 NULL 인 경우 값 사용할 수 없습니다.

물론 additionalInfo이 FeatureC 클래스에 저장할 수있는 값이면 물론 작동 할 수도 있습니다.

class Feature { 
public: 
    virtual ~Feature() {} 
    virtual const float getValue(const vector<int>& v) const = 0; 
    virtual const float getValue(const vector<int>& v, const vector<int>& additionalInfo) { return -1.0; }; 
    virtual bool useAdditionalInfo() { return false; } 
}; 

다음과 같은 루프 뭔가합니다 :

for (int i = 0; i < features.size(); ++i) { 
    if (features[i]->useAdditionalInfo()) 
    { 
     res += features[i]->getValue(v, additionalInfo); 
    } 
    else 
    { 
    res += features[i]->getValue(v); 
    } 
} 
1

이 문제는 C++ 코딩 표준 (Sutter, Alexandrescu)의 39 번째 항목 인 "가상 기능을 비공개로 만들고 공개 기능을 비 가상으로 만드는 것을 고려하십시오"에서 설명합니다. 특히

은 비 가상 인터페이스 디자인 패턴을 다음의 동기 중 하나는 자연의 형태를 취할 수

각 인터페이스로 언급된다 (이 항목이 모든 것입니다) : 때 우리 공용 인터페이스 을 사용자 정의 인터페이스와 분리하면 각각 쉽게 자연스럽게 양식을 취할 수 있습니다. 은 일치하는 표식을 찾기 위해 시도하는 대신 받아 들여야합니다. . 흔히 두 인터페이스는 다른 수의 함수 및/또는 다른 매개 변수를 필요로합니다. [...]

이 변화

이 경우 매우 유용한 또 다른 설계 패턴의 높은 비용베이스 클래스에서

특히 유용한 것은 방문자 패턴 . NVI의 경우 기본 클래스 (전체 계층 구조는 물론)의 변경 비용이 높을 때 적용됩니다. 이 디자인 패턴에 대해 많은 논의를 찾을 수 있습니다. (옆에있는) 매우 사용하기 쉬운 Visitor 기능을 사용하는 방법에 대한 훌륭한 통찰력을 제공하는 Modern C++ (Alexandrescu)의 관련 장을 읽어 보시기 바랍니다. in loki

이 자료를 모두 읽은 다음 질문을 편집하여 더 나은 답변을 줄 수 있도록 제안합니다. 우리는 모든 종류의 솔루션을 제안 할 수 있습니다 (예 : 필요에 따라 클래스에 추가 매개 변수를 제공하는 추가 방법 사용).

는 다음과 같은 질문을 해결하기 위해 시도 :

  1. 템플릿 기반 솔루션은 문제 맞는 것?
  2. 함수를 호출 할 때 간접 참조의 새 계층을 추가하는 것이 가능합니까?
  3. "푸시 인수"- "푸시 인수"-...- "푸시 인수"- "호출 함수"메서드가 도움이 될까요?
  4. 은 어떻게 계획입니까 (이 처음에는 매우 이상하게 보일 수도 있지만, 는 "ENDL"는 "통화 기능"이다, "< < ENDL, 인수 < <, 인수 < <, 인수 cout을 < <"처럼 뭔가 생각) Computer :: compute에서 함수를 호출하는 방법을 구별 할 수 있습니까?

우리는 약간의 "이론"의는 방문자 패턴을 사용하여 연습을 목표로 할 수 있었다 지금 :

#include <iostream> 

using namespace std; 

class FeatureA; 
class FeatureB; 

class Computer{ 
    public: 
    int visitA(FeatureA& f); 

    int visitB(FeatureB& f); 
}; 

class Feature { 
public: 
    virtual ~Feature() {} 
    virtual int accept(Computer&) = 0; 
}; 

class FeatureA{ 
    public: 
    int accept(Computer& c){ 
     return c.visitA(*this); 
    } 
    int compute(int a){ 
     return a+1; 
    } 
}; 

class FeatureB{ 
    public: 
    int accept(Computer& c){ 
     return c.visitB(*this); 
    } 
    int compute(int a, int b){ 
     return a+b; 
    } 
}; 

int Computer::visitA(FeatureA& f){ 
     return f.compute(1); 
} 

int Computer::visitB(FeatureB& f){ 
     return f.compute(1, 2); 
} 

int main() 
{ 
    FeatureA a; 
    FeatureB b; 
    Computer c; 
    cout << a.accept(c) << '\t' << b.accept(c) << endl; 
} 

당신이 코드 here을 시도 할 수 있습니다. 이것은 Visitor 패턴의 대략적인 구현으로, 알 수 있듯이 문제를 해결합니다. 이 방법으로 구현하지 않으려 고 강력하게 조언합니다. Acyclic Visitor라는 세련미를 통해 해결할 수있는 명백한 의존성 문제가 있습니다. Loki에서는 이미 구현되어 있으므로 구현에 대해 걱정할 필요가 없습니다.

구현과는 별도로 유형 스위치 (예 : 다른 사람이 지적했듯이 가능하면 피해야 함)에 의존하지 않고 클래스에 특정 인터페이스 (예 : 하나의 인수 compute 함수의 경우). 또한 방문자 클래스가 계층 구조 (이 예에서 컴퓨터를 기본 클래스로 만들 때) 인 경우이 기능의 기능을 추가하려는 경우 계층 구조에 새 기능을 추가 할 필요가 없습니다.

방문 A, 방문 B, ... "패턴"이 마음에 들지 않으면 걱정하지 마십시오. 이것은 간단한 구현 일 뿐이며 필요하지 않습니다. 기본적으로 실제 구현에서는 방문 기능의 템플릿 전문화를 사용합니다. 이 도움이

희망, 나는 그것을 :)에 많은 노력을했다

+0

재미있는 포인터 덕분에 – needsaname

+0

당신은 오신 것을 환영합니다. 나는 당신의 문제에 대한 좀 더 많은 물질적 인 코드 솔루션을 개발했다. 희망이 도움이됩니다. –

관련 문제