2013-06-22 4 views
2

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

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

На данный момент я застрял в том, что должно быть в дизайне класса, где в качестве одного класса «Рисование» будет выполнен чертеж, потребляющий объект класса «Форма». Поскольку вектор будет содержать разные объекты одного и того же базового класса 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

ответ

1

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

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

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

Альтернативы (Примечание: код не был протестирован):

  1. Функция указатель, который, как создать свой собственный делегат ака виртуальные таблицы.

    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. Functor, который является классом, который переопределяет оператор(), а также как делегат

    struct square 
    { 
        function<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); 
    
  3. Наследуйте от другого класса, который реализует функцию, которую вы хотите либо с шаблоном базового класса (IIRC, это было сделано в ATL). Это просто сворачивает вашу собственную жестко закодированную vtable и называется Curiously 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); 
         } 
    }; 
    

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

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

    Примечание: если вы используете г ++ 4.7, быть предупрежденunordered_map было показано, что performance issues.

+0

Я думал об этом, но то, о чем я думал, заключается в том, чтобы оттянуть чертеж отдельно от базового класса. Совершенно другой класс, который делает рисунок. –

+0

Вы можете использовать что-то вроде делегата, чтобы делать то, что ищете. То есть указатель функции или функтор. Однако, каковы причины этого? Этот тип разделения должен иметь веские основания для этого. – Adrian

+0

@Wajih, я добавил несколько примеров того, как вы можете отделить код чертежа от класса реализации. – Adrian

1

Это в значительной степени классическая демонстрация того, когда вы хотите виртуальную функцию. Определите 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); 
    } 
}; 

... и так далее для каждого типа формы, о котором вы заботитесь.

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

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

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

+0

Но что, если я хочу убрать свой чертеж от базового класса? В другом классе полностью? –

+0

@Wajih: ответ OO заключается в том, что доступные объекты * должны * знать, как рисовать себя (и это одно из тех мест, которые дизайн OO, вероятно, будет работать очень хорошо). Если вы действительно настаиваете на чем-то другом, вы могли бы (на одном примере) использовать некоторые 'dynamic_cast' для определения типов, а затем использовать перегрузку функций для обработки чертежа для разных типов. Я бы склонен придерживаться дизайна OO, и если вы хотите сохранить рисунок * отдельно, передайте что-то вроде параметра шаблона, который каждый объект может использовать для рисования. –

+1

@Wajih: нижняя строка, однако, довольно проста: это действительно * тип проблемы OO и полиморфизм были разработаны для решения, и он остается о лучшем (широко доступном) способе решения такого рода проблем. –