2010-02-28 3 views
6

이미 클래스의 계층 구조가 있다고 가정 해 봅시다.원래 클래스를 수정하지 않고 가상 함수 추가

class Shape { virtual void get_area() = 0; }; 
class Square : Shape { ... }; 
class Circle : Shape { ... }; 
etc. 

이제 내가 (효과적으로) 각 하위 클래스의 적절한 정의를 Shapevirtual draw() = 0 방법을 추가 할 것을 가정 해 봅시다. 그러나, 나는 그들이 변경하고 싶지 않은 라이브러리의 일부이기 때문에 이러한 클래스를 수정하지 않고 이것을하고 싶다고 가정 해 봅시다.

이 문제를 해결하는 가장 좋은 방법은 무엇입니까?

실제로 "virtual"메서드를 "추가"하는지 여부는 중요하지 않지만 포인터 배열을 사용하여 다형성 동작을 원합니다.

내 첫번째 생각은이 작업을 수행하는 것입니다 :

class IDrawable { virtual void draw() = 0; }; 
class DrawableSquare : Square, IDrawable { void draw() { ... } }; 
class DrawableCircle : Circle, IDrawable { void draw() { ... } }; 

하고 그냥 각각 DrawableSquare들과 DrawableCircle의 모든 Square의의 창조와 Circle의 교체.

이 작업을 수행하는 가장 좋은 방법입니까, 아니면 더 나은 것이 있습니까 (바람직하게는 SquareCircle의 생성을 그대로 유지하는 것이 좋습니다).

미리 감사드립니다.

답변

5

는 (나는 아래로 더 해결책을 제시 할 ... 곰을 나와 함께 ...) (거의) 문제를 해결하기

한 가지 방법은 방문자 디자인 패턴을 사용하는 것입니다. 이런 식으로 뭔가 : 대신의 다음

class DrawVisitor 
{ 
public: 
    void draw(const Shape &shape); // dispatches to correct private method 
private: 
    void visitSquare(const Square &square); 
    void visitCircle(const Circle &circle); 
}; 

:

Shape &shape = getShape(); // returns some Shape subclass 
shape.draw(); // virtual method 

당신은 할 것이다 :

DrawVisitor dv; 
Shape &shape = getShape(); 
dv.draw(shape); 

는 일반적으로 방문자 패턴이 같은 draw 방법을 구현하는 것이 :

DrawVisitor::draw(const Shape &shape) 
{ 
    shape.accept(*this); 
} 

하지만 Shape 계층 구조를 방문하도록 설계된 경우에만 작동합니다. 각 하위 클래스는 방문자의 적절한 visitXxxx 메서드를 호출하여 accept 가상 메서드를 구현합니다. 대부분의 경우 그렇게 설계되지 않았을 가능성이 큽니다.

(및 모든 하위 클래스)에 가상 accept 메서드를 추가하기 위해 클래스 계층을 수정할 수없는 경우 올바른 draw 메서드로 디스패치하는 다른 방법이 필요합니다. 하나의 순진한 접근 방식은 다음과 같습니다.

DrawVisitor::draw(const Shape &shape) 
{ 
    if (const Square *pSquare = dynamic_cast<const Square *>(&shape)) 
    { 
    visitSquare(*pSquare); 
    } 
    else if (const Circle *pCircle = dynamic_cast<const Circle *>(&shape)) 
    { 
    visitCircle(*pCircle); 
    } 
    // etc. 
} 

동적 인 방식을 사용하면 효과가 있습니다. 당신이 타격을 줄 수있는 경우에, 그것을 유지, 디버그를 이해하기 쉬운 간단한 방법 등

모든 형상 유형의 열거가 있었다 가정 :

enum ShapeId { SQUARE, CIRCLE, ... }; 

는 가상 있었다 방법 ShapeId Shape::getId() const = 0; 각 하위 클래스는 ShapeId을 반환하도록 재정의합니다. 그렇다면 dynamic_cast의 if-elsif-elsif 대신 큰 switch 문을 사용하여 파견을 수행 할 수 있습니다. 아니면 switch 대신 해시 테이블을 사용하십시오. 가장 좋은 경우 시나리오는이 매핑 기능을 한 곳에 두는 것이므로 매번 매핑 논리를 반복하지 않고도 여러 방문자를 정의 할 수 있습니다.

따라서 getid() 메소드가 없을 수도 있습니다. 너무 나빴어. 각 유형의 객체에 대해 고유 한 ID를 얻는 또 다른 방법은 무엇입니까? RTTI. 이것은 반드시 우아하고 완벽하지는 않지만, type_info 포인터의 해시 테이블을 만들 수 있습니다. 일부 초기화 코드에서이 해시 테이블을 빌드하거나 동적으로 (또는 둘 다) 빌드 할 수 있습니다.

DrawVisitor::init() // static method or ctor 
{ 
    typeMap_[&typeid(Square)] = &visitSquare; 
    typeMap_[&typeid(Circle)] = &visitCircle; 
    // etc. 
} 

DrawVisitor::draw(const Shape &shape) 
{ 
    type_info *ti = typeid(shape); 
    typedef void (DrawVisitor::*VisitFun)(const Shape &shape); 
    VisitFun visit = 0; // or default draw method? 
    TypeMap::iterator iter = typeMap_.find(ti); 
    if (iter != typeMap_.end()) 
    { 
    visit = iter->second; 
    } 
    else if (const Square *pSquare = dynamic_cast<const Square *>(&shape)) 
    { 
    visit = typeMap_[ti] = &visitSquare; 
    } 
    else if (const Circle *pCircle = dynamic_cast<const Circle *>(&shape)) 
    { 
    visit = typeMap_[ti] = &visitCircle; 
    } 
    // etc. 

    if (visit) 
    { 
    // will have to do static_cast<> inside the function 
    ((*this).*(visit))(shape); 
    } 
} 

일부 버그/구문 오류가있을 수 있지만이 예제를 컴파일하지 않았습니다. 나는 전에 이런 식으로 일을했다. 그 기술은 효과가있다. 공유 라이브러리에 문제가 생길지 모르겠다.

내가 추가 할 것입니다 마지막으로 한가지 :

class ShapeVisitor 
{ 
public: 
    void visit(const Shape &shape); // not virtual 
private: 
    virtual void visitSquare(const Square &square) = 0; 
    virtual void visitCircle(const Circle &circle) = 0; 
}; 
+0

거기에 방문하기보다는'visitCircle (const Circle & circle) '을 의미합니까? –

+0

@Philip : 죄송합니다 ... 해결되었습니다. – Dan

+0

흥미로운 솔루션, 나는'Visitor' 패턴 >> 패턴의 일부만을 사용하는 것이 상황에 적응되도록 의도 된 것이고 다른 방법은 아닙니다. :) –

3

설명하는 내용은 다소 decorator pattern입니다. 기존 클래스의 런타임 동작을 변경하는 데 매우 적합합니다. 모양이 그려 질 수있는 방법이없는 경우

하지만 난 정말 당신의 실제적인 예를 구현하는 방법을 볼 수 없습니다은 다음 런타임 중 ...

에서 그리기 동작을 변경할 수있는 방법이 없다 그러나 나는 이것이 가정 stackoverflow에 대한 아주 단순한 예제? 원하는 기능을위한 모든 기본 구성 요소를 사용할 수있는 경우 이러한 패턴을 사용하여 정확한 런타임 동작을 구현하는 것이 확실한 선택입니다.

+0

이것은 SO에 대한 간단한 예가 아닙니다. 말 그대로 드로잉 메서드를 추가 할 모양이 있습니다. 구체적으로, Bullet Physics SDK 충돌 셰이프의 모양이며 디버깅 목적으로 드로잉 기능을 추가 할 수 있기를 원합니다. –

+0

내가 틀렸다면 정정 해주지 만, 데코레이터가이 문제를 해결한다고 생각하지 않습니다. 데코레이터는 기존 트리에 비헤이비어를 추가하는 대신 기존 클래스에 비헤이비어 트리를 추가합니다. –

+0

Bullet SDK에는 'btIDebugDraw' 인터페이스가있는 것 같습니다 : http://bulletphysics.com/Bullet/BulletFull/classbtIDebugDraw.html. 'drawBox()'와'drawSphere()'와 같은 함수를 구현 한 클래스를 파생시킨 다음, 그것을'btCollisionWorld' 또는'btDynamicsWorld'에 할당 한 다음 그 클래스를 그립니다. 그게 당신이 필요로하는 것을 해줄까요? –

0

하나의 솔루션 '벽 오프'당신이 좋아 수 있습니다 관계없이 파견하기로 결정 방법, 아마 방문자의 기본 클래스를 만들 의미가 있습니다 상황에 따라 템플릿을 사용하여 컴파일 시간에 다형성을 적용하는 방법을 고려해야합니다. 당신이 아무 말도하기 전에, 나는 그것이 잘 유용하지 않을 수 있지만 당신이 작업하고있는 환경의 한계에 따라, 그것은 유용 할 수 있도록이 당신에게 기존의 런타임 다형성을 제공하지 않습니다을 알고 :

#include <iostream> 

using namespace std; 

// This bit's a bit like your library. 
struct Square{}; 
struct Circle{}; 
struct AShape{}; 

// and this is your extra stuff. 
template < class T > 
class Drawable { public: void draw() const { cout << "General Shape" << endl; } }; 

template <> void Drawable<Square>::draw() const { cout << "Square!" << endl; }; 
template <> void Drawable<Circle>::draw() const { cout << "Circle!" << endl; }; 

template < class T > 
void drawIt(const T& obj) 
{ 
    obj.draw(); 
} 

int main(int argc, char* argv[]) 
{ 
    Drawable<Square> a; 
    Drawable<Circle> b; 
    Drawable<AShape> c; 

    a.draw(); // prints "Square!" 
    b.draw(); // prints "Circle!" 
    c.draw(); // prints "General Shape" as there's no specific specialisation for an Drawable<AShape> 

    drawIt(a); // prints "Square!" 
    drawIt(b); // prints "Circle!" 
    drawIt(c); // prints "General Shape" as there's no specific specialisation for an Drawable<AShape> 
} 

drawIt() 메서드는 아마도 draw() 메서드가있는 요구 사항을 충족하는 모든 클래스에 대한 일반적인 동작을 나타 내기 때문에 여기에서 핵심적인 것입니다. 컴파일러가 전달 된 각 유형에 대해 별도의 메소드를 인스턴스화 할 때 코드가 부 풀릴 때 조심하십시오.

공통 기본 클래스가없는 많은 유형에서 작동하도록 하나의 함수를 작성해야하는 상황에서 유용 할 수 있습니다. 나는 이것이 당신이 물어 본 질문이 아니라는 것을 알고 있습니다. 그러나 나는 그것을 대안으로 버릴 것이라고 생각했습니다.

관련 문제