C ++ Как заменить это выражение if ... else? - PullRequest
3 голосов
/ 28 января 2010

У меня есть следующий код C ++ (упрощенная версия):

class Shape
{
    bool isCircle = false;
    bool isSquare = false;
}

class Circle : public Shape
{
    // some special members/methods
}

class Square : public Shape
{
    // some special members/methods
}

class CAD
{
    virtual DrawCircle(Circle * circle) = 0;
}

class SWX : public CAD
{
    virtual DrawCircle(Circle * circle){// do some stuff that draws circle on SWX system}
}

class PRO : public CAD
{
    virtual DrawCircle(Circle * circle){// do some stuff that draws circle on PRO system}
}

int main()
{
    Circle * circle = new Circle();
    circle->isCircle = true;

    Square * sq = new Square;
    sq->isSquare = true;

    vector<Shape*> shapes;
    shapes.push_back(circle);
    shapes.push_back(sq);

    SWX * swx = new SWX();

    for( int i = 0 ; i < shapes.size() ; ++i )
    {
        if( shapes[i]->isCircle )
    {
        SWX->DrawCircle((Circle*)(shapes[i]));
    }
    else if( shapes[i]->isSquare )
    {
        SWX->DrawSquare((Square*)(shapes[i]));
    }
}

Я хочу устранить необходимость в if ... else (если это вообще возможно в рамках ограничений, указанных ниже).

Мои ограничения сейчас:

  • САПР и производные классы являются огромными классами с различными внешними зависимостями.
  • Классы CAD не могут быть объединены с Shape и производными классами (это было бы идеально, так как с тех пор я могу использовать полиморфизм для решения своей проблемы), так как другие проекты / классы зависят от классов Shape и не могут зависеть от CAD классы.
  • Существует более дюжины производных от Shape классов с полудюжиной производных от CAD классов, и это, если ... еще происходит во многих местах - поэтому будет полезно, если какое-либо решение будет простым для понимания (легче убедить мое товарищи по команде, чтобы изменить старый код).

Любые предложения / комментарии / решения, которые у вас есть, приветствуются.

Ответы [ 9 ]

8 голосов
/ 28 января 2010

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

Вот как шаблон посетителя будет работать в вашем случае:

  • Вам нужен абстрактный ShapeVisitor класс. У него есть абстрактный Visit метод для каждого конкретного подкласса Shape. например: Visit(Circle*), Visit(Square*) и т. д.
  • Форма имеет абстрактный метод AcceptVisitor(ShapeVisitor*).
  • Каждый подкласс Shape реализует AcceptVisitor как просто вызов visitor->Visit(this)
  • Каждый класс CAD является (или имеет до вас) ShapeVisitor. Методы Visit делают соответствующий рисунок для определенного типа Shape. Никаких условий или кастинга не требуется.

Вот модифицированная версия вашего кода, которая использует шаблон посетителя довольно слабо:

class Circle;
class Square;
class ShapeVisitor
{
    virtual void Visit(Circle *circle) = 0;
    virtual void Visit(Square *square) = 0;
}

class Shape
{
    virtual void AcceptVisitor(ShapeVisitor *visitor) = 0;
}


class Circle : public Shape
{
    // some special members/methods

    virtual void AcceptVisitor(ShapeVisitor *visitor)
    {
        visitor->Visit(this);
    }
}

class Square : public Shape
{
    // some special members/methods

    virtual void AcceptVisitor(ShapeVisitor *visitor)
    {
        visitor->Visit(this);
    }
}

class CAD : public ShapeVisitor
{
    virtual DrawCircle(Circle *circle) = 0;
    virtual DrawSquare(Square *square) = 0;

    virtual void Visit(Circle *circle) {
        DrawCircle(circle);
    }

    virtual void Visit(Square *square) {
        DrawSquare(square);
    }
}

class SWX : public CAD
{
    virtual DrawCircle(Circle *circle){// do some stuff that draws circle on SWX system}

}

class PRO : public CAD
{
    virtual DrawCircle(Circle * circle){// do some stuff that draws circle on PRO system}
}

int main()
{
    Circle * circle = new Circle();
    Square * sq = new Square;

    vector<Shape*> shapes;
    shapes.push_back(circle);
    shapes.push_back(sq);

    SWX * swx = new SWX();

    for( int i = 0 ; i < shapes.size() ; ++i )
    {
        shapes[i]->AcceptVisitor(SWX);
    }
}

В этом коде я решил сделать CAD фактически подклассом ShapeVisitor. Кроме того, поскольку у вас уже есть виртуальные методы в CAD для рисования, я реализовал методы Visit там (один раз), а не один раз в каждом подклассе. Как только вы переключаете клиентов на использование AcceptVisitor вместо непосредственного вызова методов Draw *, вы можете защитить эти методы, а затем в конечном итоге перенести реализацию методов Visit вниз на подклассы (то есть рефакторинг для удаления дополнительный уровень косвенности вызван наличием Visit(Foo*) call DrawFoo(Foo*)).

6 голосов
/ 28 января 2010

Это классический случай для DoubleDispatch , где вам нужен отдельный метод для каждой возможной пары (Shape, CAD):

  • Ядерное поле isSquare / isCircle.
  • добавить virtual void DrawOn(CAD*) к интерфейсу Shape.
  • Реализация Circle::DrawOn(CAD*) (например):

    void Circle::DrawOn(CAD *c) {
      c->DrawCircle(this);
    }
    

    Это «хитрость», которая позволяет при вызове, подобном myCircle->DrawOn(mySWX), вызывать правильный метод независимо от типа Shape или CAD.

1 голос
/ 29 января 2010

Почему бы не определить простой интерфейс ICAD? Поскольку САПР зависит от формы, это не увеличивает сложность:

class Shape
{
    Draw(ICAD* cad) = 0;        
}

class Circle : public Shape
{
    Draw(ICAD* cad)
    {
        ICAD->DrawCircle(self)
     }
}

class Square : public Shape
{
    Draw(ICAD* cad)
    {
        ICAD->DrawSquare(self)
     }
}

DrawSquare (self) выглядит забавно, но я не знаю, что классы CAD делают с объектами фигур.

class ICAD
{
    virtual DrawSquare(Square* square) = 0;
    virtual DrawCircle(Circle * circle) = 0;
}

Я предполагаю, что в классе САПР есть не только абстрактные методы, поэтому вы не связываете его с Shape.

class CAD : public ICAD
{
    // big CAD class...
}

class SWX : public CAD
{
    virtual DrawCircle(Circle * circle){// do some stuff that draws circle on SWX system}
}

class PRO : public CAD
{
    virtual DrawCircle(Circle * circle){// do some stuff that draws circle on PRO system}
}

int main()
{
    Circle * circle = new Circle();
    Square * sq = new Square;

    vector<Shape*> shapes;
    shapes.push_back(circle);
    shapes.push_back(sq);

    SWX * swx = new SWX();

    for( int i = 0 ; i < shapes.size() ; ++i )
    {
        shapes[i]->Draw(swx);
    }
}
1 голос
/ 28 января 2010

Это довольно простой ОО полиморфизм. Круг и Квадрат - специализированные версии формы. Каждый из них должен знать, какое специализированное поведение необходимо для работы с вашими классами САП.

class Shape
{
    virtual void DrawWithCAD(CAD * cad) = 0;
}

class Circle : public Shape
{
    virtual void DrawWithCAD(CAD * cad)
    {
        cad->DrawCircle(this);
    }
}

class Square : public Shape
{
    virtual void DrawWithCAD(CAD * cad)
    {
        cad->DrawSquare(this);
    }
}

Тогда ваш цикл main () изменится на:

for( int i = 0 ; i < shapes.size() ; ++i )
{
    shapes[i]->DrawWithCAD(swx);
}
1 голос
/ 28 января 2010

Почему бы просто не SWX-> Draw (shape [i]) ;? Вам нужно добавить два метода Draw, один из которых принимает круг, а другой - квадрат?

1 голос
/ 28 января 2010

У вас там довольно странный ОО, но по крайней мере DrawXxxx должен просто стать Draw (). Круг, квадрат и другие фигуры будут определять метод Draw (), который обеспечивает реализацию виртуального метода Draw в Shape. Тогда вы можете просто вызвать Draw на любую фигуру, и она сделает правильные вещи.

Булевы значения isXxxx тоже должны идти. Классы знают, что они из себя представляют, и instanceof может сказать вам (хотя нет необходимости проверять, когда вы рисуете, поскольку это будет вызов виртуального метода).

0 голосов
/ 28 января 2010

Напишите несколько глобальных функций Draw, перегруженных для каждой фигуры ... (DrawCircle и его друзья являются виртуальными, поэтому мы можем использовать полиморфизм для вызовов объектов CAD)

void Draw(CAD *cad, Circle *circle){
    cad->DrawCircle(circle);
}

void Draw(CAD *cad, Square *square){
    cad->DrawSquare(square);
}
0 голосов
/ 28 января 2010

Способ, которым я решил бы это, - позволить отдельным классам фигур рисовать себя на некотором «холсте» рисования (класс, обеспечивающий буфер рисования и некоторые основные функции рисования, например, для линий, прямоугольников и эллипсов). 1001 *

Класс SWX затем вызовет метод Shape Draw, предоставив ему собственный холст:

class Shape
{
public:
    virtual void DrawOnto(Canvas& canvas) = 0;
};

class Circle : public Shape
{
public:
    virtual void DrawOnto(Canvas& canvas);
};

void Circle::DrawOnto(Canvas& canvas)
{
    // ..., e.g.:
    canvas.DrawEllipse(center.x, center.y, radius, radius);
}

...

class SWX : public CAD
{
private:
    Canvas canvas;
public:
    void Draw(Shape& shape)
    {
        shape.DrawOnto(this.canvas);
        // ^ this is the place where your if..else used to be
    }
};
0 голосов
/ 28 января 2010

Возможно, это не идеальное решение, но вы можете просто предоставить функцию-член CAD :: Draw и перегрузить ее для обработки каждого типа фигуры.Что-то вроде:

class CAD {
  public:
    virtual void Draw(const Circle& circle) = 0;
    virtual void Draw(const Square& square) = 0;
};

Тогда вы можете просто вызвать Draw для любого поддерживаемого объекта без каких-либо операторов if.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...