Рисование объектов — лучший дизайн класса?

У меня проблема с разработкой класса, который позволит мне рисовать объекты различной формы.

  1. Форма является базовым классом
  2. Треугольник, Квадрат, Прямоугольник являются производными от классов Shape учебный класс
  3. у меня есть vector<Shape*> ShapeCollection который хранит производные объекты, т.е. Triangle,Square, Rectangle
  4. Как только я выберу объект из вектора, мне нужно нарисовать объект на экране.

В этот момент я застрял в том, каким должен быть дизайн класса, где один класс «Drawing» будет рисовать, потребляя объект класса «Shape». Так как вектор будет содержать разные объекты одного базового класса Shape, Поскольку у меня есть поток, который выбирает объект из вектора, и как только у меня есть объект, я должен быть в состоянии правильно нарисовать его.

Так что более или менее ниже, это то, что я говорю

class Drawing
{
public:
void Draw(Shape* shape, string objectName)
{
// Now draw the object.
// But I need to know which Object I am drawing or use
// switch statements to identify somehow which object I have
// And then draw. I know this is very BAD!!!
// e.g.
switch(objectName)
{
case "rectangle":
DrawRectangle((Rectangle*) shape)
break;
//Rest of cases follow
}
}
}

Где у меня будет DrawSquare, функция DrawTriangle, которая будет рисовать.

Это должно быть что-то, что было решено. Там должен быть лучший способ сделать это как
все это заявление о переключении должно как-то уйти!

Любое руководство высоко ценится.

Спасибо


@Adrian и @Jerry предложили использовать виртуальную функцию, я думал об этом, но мне нужно, чтобы мой рисунок был удален от базового класса Shape

3

Решение

Вы бы использовали полиморфизм.

  1. Создайте чисто виртуальную функцию в вашем базовом классе (т.е. при объявлении функции присвойте ей 0, как в void DrawShape() = 0;)
  2. Объявите и определите эту функцию в ваших производных классах.

Таким образом, вы можете просто позвонить DrawShape() на каждом из этих объектов, даже если он передается как объект Shape.

Альтернативы (ПРИМЕЧАНИЕ: код не был проверен):

  1. Указатель на функцию, похожий на создание собственного делегата vtable.

    struct square
    {
    void (*draw)(square&);
    };
    
    void drawSquare(square& obj)
    {
    // draw square code
    // there is no 'this'. must access members via `obj`.
    }
    
    square s;
    s.draw = drawSquare;
    s.draw(s);
    
  2. Функтор, который является классом, который переопределяет operator (), а также как делегат

    struct square
    {
    // Note that std::function can hold a function pointer as well as a functor.
    function<void(square&)> draw;
    };
    
    struct drawSquare
    {
    void oparator()(square& obj)
    {
    // draw square code
    // there is no 'this'. must access members via `obj`.
    }
    };
    
    square s;
    square s.draw = drawSquare();
    s.draw(s);
    

    ПРИМЕЧАНИЕ: 1 и 2 также могут быть инициализированы лямбда-функциями:

    square s;
    s.draw = [](square& obj) {
    // draw square code
    // there is no 'this'. must access members via `obj`.
    };
    s.draw(s);
    

    ПРИМЕЧАНИЕ: 1 может быть сделано с шаблоном:

    struct square;
    
    template <void (*DRAW)(square&)>
    struct square
    {
    void draw()
    {
    DRAW(*this);
    }
    };
    
    void drawSquare(square& obj)
    {
    // draw square code
    // there is no 'this'. must access members via `obj`.
    }
    
    square s<&drawSquare>;
    s.draw();
    

    ПРИМЕЧАНИЕ: 2 можно сделать и с шаблоном:

    template <typename DRAW>
    struct square
    {
    void draw()
    {
    // First set of parentheses instantiate the DRAW object.
    // The second calls the functor.
    DRAW()(*this);
    }
    };
    
    struct drawSquare
    {
    void oparator()(square& obj)
    {
    // draw square code
    // there is no 'this'. must access members via `obj`.
    }
    };
    
    square s<drawSquare>;
    s.draw();
    

    Или в качестве альтернативы, что позволило бы передать функтор с состоянием:

    template <typename DRAW>
    struct square
    {
    DRAW draw;
    };
    
    struct drawSquare
    {
    void operator()(square& obj)
    {
    // draw square code
    // there is no 'this'. must access members via `obj`.
    }
    };
    
    square s<drawSquare>;
    s.draw = drawSquare();
    s.draw(s);
    
  3. Унаследуйте от другого класса, который реализует функцию, которую вы хотите либо с шаблонным базовым классом (IIRC, это было сделано в ATL). Это просто свертывание вашего собственного жестко запрограммированного vtable и называется Curly Recurring Type Pattern (CRTP).

    template <class D>
    struct shape
    {
    inline void draw() { return static_cast<D&>(*this).draw(); }
    };
    
    void draw(square& obj)
    {
    // draw square code
    // No 'this' available. must access shape members via `obj`.
    }
    
    struct square : public D<square>
    {
    void draw()
    {
    drawSquare(*this);
    }
    };
    

    Другие примеры можно найти Вот а также Вот.

  4. Иметь свой draw класс наследуется от type of shape класс, который наследуется от базы shape учебный класс.

    struct shape
    {
    virtual void draw() = 0;
    };
    
    struct square : public shape
    {
    };
    
    struct drawSquare : public square
    {
    virtual void draw()
    {
    // draw square code
    // you access the square's public or protected members from here
    }
    };
    
  5. Использовать std::unordered_map

    #include <unordered_map>
    #include <typeinfo>
    #include <functional>
    
    struct shape { };
    
    struct square : public shape { };
    
    void drawSquare(shape& o)
    {
    // this will throw an exception if dynamic cast fails, but should
    // never fail if called from function void draw(shape& obj).
    square& obj = dynamic_cast<square&>(o);
    
    // draw square code
    // must access shape members via `obj`.
    }
    
    std::unordered_map<size_t, std::function<void(shape&)>> draw_map
    {
    { type_id(square).hash(), drawSquare }
    };
    
    void draw(shape& obj)
    {
    // This requires the RTTI (Run-time type information) to be available.
    auto it = draw_map.find(type_id(obj).hash());
    
    if (it == draw_map.end())
    throw std::exception(); // throw some exception
    (*it)(obj);
    }
    

    ПРИМЕЧАНИЕ: если вы используете g ++ 4.7, имейте в виду unordered_map было показано, чтобы иметь проблемы с производительностью.

2

Другие решения

Это в значительной степени Классическая демонстрация того, когда вы хотите виртуальную функцию. Определить draw в вашем базовом классе, затем переопределите его в каждом производном классе. Затем, чтобы нарисовать все объекты, вы проходите через коллекцию и вызываете draw() член для каждого.

class shape {
// ...
virtual void draw(canvas &c) = 0;
};

class square : public shape {
int x, y, size;
// ...
virtual void draw(canvas &c) {
c.move_to(x, y);
c.draw_to(x+size, y);
c.draw_to(x+size, y+size);
c.draw_to(x, y+size);
c.draw_to(x, y);
}
};

…и так далее для каждого типа фигуры, который вам небезразличен.

Редактировать: используя класс стратегии, вы получите код, смутно подобный следующему:

template <class draw>
class shape {
// ...
virtual void draw(canvas &c) = 0;
};

template <class d>
class square : public shape<d> {
// ...
virtual void draw(canvas &c) {
d.square(x, y, size, c);
}
};

Другой возможностью будет использование шаблона посетителя. Это типично используется, когда вам нужно / нужно пересечь более сложную структуру вместо простой линейной последовательности, но также может использоваться и здесь. Это достаточно сложно, так что, возможно, здесь будет немного сложнее, но если вы ищете «Шаблон посетителя», вы должны найти изрядное количество материала.

1

По вопросам рекламы [email protected]