Проблема дублирования кода ортогональных переменных

Недавно я начал рефакторинг некоторого устаревшего кода и наткнулся на две функции для рисования координатной сетки, проблема в том, что эти функции отличаются только ортогональными переменными, которые они обрабатывают, что-то в этом роде

void DrawScaleX(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    for(int x = x0; x < x1; x += step)
    {
         MoveToEx(dc, x, y0, NULL);
         LineTo(dc, x, y1);
    }
}
void DrawScaleY(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    for(int y = y0; y < y1; y += step)
    {
         MoveToEx(dc, x0, y, NULL);
         LineTo(dc, x1, y);
    }
}

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

У меня вопрос: как бы вы переписали эти две функции в одну, чтобы избежать этой проблемы?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
515
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Вот мое собственное решение


class CoordGenerator
{
public:
    CoordGenerator(int _from, int _to, int _step)
        :from(_from), to(_to), step(_step), pos(_from){}
    virtual POINT GetPoint00() const = 0;
    virtual POINT GetPoint01() const = 0;
    bool Next()
        {
            if (pos > step) return false;
            pos += step;
        }
protected:
    int from;
    int to;
    int step;
    int pos;
};

class GenX: public CoordGenerator
{
public:
    GenX(int x0, int x1, int step, int _y0, int _y1)
        :CoordGenerator(x0, x1, step),y0(_y0), y1(_y1){}
    virtual POINT GetPoint00() const
        {
            const POINT p = {pos, y0};
            return p;
        }
    virtual POINT GetPoint01() const
        {
            const POINT p = {pos, y1};
            return p;
        }
private:
    int y0;
    int y1;
};

class GenY: public CoordGenerator
{
public:
    GenY(int y0, int y1, int step, int _x0, int _x1)
        :CoordGenerator(y0, y1, step),x0(_x0), x1(_x1){}
    virtual POINT GetPoint00() const
        {
            const POINT p = {x0, pos};
            return p;
        }
    virtual POINT GetPoint01() const
        {
            const POINT p = {x1, pos};
            return p;
        }
private:
    int x1;
    int x0;
};

void DrawScale(HDC dc, CoordGenerator* g)
{
    do
    {
        POINT p = g->GetPoint00();
        MoveToEx(dc, p.x, p.y, 0);
        p = g->GetPoint01();
        LineTo(dc, p.x, p.y);
    }while(g->Next());
}

Но мне это кажется слишком сложным для такой крошечной проблемы, поэтому я с нетерпением жду ваших решений.

Почему вы просто не выделяете тело цикла for в отдельную функцию? Затем вы можете делать забавные вещи в извлеченной функции.

void DrawScaleX(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    for(int x = x0; x < x1; x += step)
    {
        DrawScale(dc, x, y0, x, y1);
    }
}

void DrawScaleY(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    for(int y = y0; y < y1; y += step)
    {
        DrawScale(dc, x0, y, x1, y);
    }
}

private void DrawScale(HDC dc, int x0, int y0, int x1, int y1)
{
    //Add funny stuff here

    MoveToEx(dc, x0, y0, NULL);
    LineTo(dc, x1, y1);

    //Add funny stuff here
}

Что ж, очевидным «решением» было бы создание единственной функции и добавление одного дополнительного параметра (типа enum). Затем выполните внутри if () или switch () и выполните соответствующие действия. Потому что, эй, функциональность функций отличается, поэтому вам нужно выполнять эти разные действия где-то.

Однако это добавляет сложности во время выполнения (проверяйте вещи во время выполнения) в месте, которое можно было бы лучше проверить во время компиляции.

Я не понимаю, в чем проблема с добавлением дополнительных параметров в будущем в обе (или несколько функций). Это выглядит так:

  1. добавить дополнительные параметры ко всем функциям
  2. скомпилируйте свой код, он не будет компилироваться во многих местах, потому что он не передает новые параметры.
  3. исправить все места, которые вызывают эти функции, путем передачи новых параметров.
  4. выгода! :)

Если это C++, вы, конечно, можете сделать функцию шаблоном, и вместо добавления дополнительного параметра вы добавляете параметр шаблона, а затем специализируете реализации шаблона для разных вещей. Но, на мой взгляд, это просто запутывает суть. Код становится сложнее для понимания, и процесс расширения его с помощью большего количества параметров по-прежнему остается точно таким же:

  1. добавить дополнительные параметры
  2. скомпилировать код, он не будет компилироваться во многих местах
  3. исправить все места, которые вызывают эту функцию

Итак, вы ничего не выиграли, но усложнили понимание кода. Не достойная цель, ИМО.

Я думаю, что перееду:

     MoveToEx(dc, x0, y, NULL);
     LineTo(dc, x1, y);

в свою собственную функцию DrawLine (x0, y0, x0, y0), которую вы можете вызывать из каждой из существующих функций.

Тогда есть одно место, где можно добавить дополнительные эффекты рисования?

Немного шаблонов ... :)

void DrawLine(HDC dc, int x0, int y0, int x0, int x1)
{
    // anti-aliasing stuff
    MoveToEx(dc, x0, y0, NULL);
    LineTo(dc, x1, y1);
}

struct DrawBinderX
{
    DrawBinderX(int y0, int y1) : y0_(y0), y1_(y1) {}

    void operator()(HDC dc, int i)
    {
        DrawLine(dc, i, y0_, i, y1_);
    }

private:
    int y0_;
    int y1_;

};

struct DrawBinderY
{
    DrawBinderX(int x0, int x1) : x0_(x0), x1_(x1) {}

    void operator()(HDC dc, int i)
    {
        DrawLine(dc, x0_, i, x1_, i);
    }

private:
    int x0_;
    int x1_;

};

template< class Drawer >
void DrawScale(Drawer drawer, HDC dc, int from, int to, int step)
{
    for (int i = from; i < to; i += step)
    {
         drawer(dc, i);
    }
}

void DrawScaleX(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    DrawBindexX drawer(y0, y1);
    DrawScale(drawer, dc, x0, x1, step);
}

void DrawScaleY(HDC dc, int step, int x0, int x1, int y0, int y1)
{
    DrawBindexY drawer( x0, x1 );
    DrawScale(drawer, dc, y0, y1, step);
}
Ответ принят как подходящий

Рисование линии - это просто соединение двух точек и рисование увеличения масштаба (x0, y0) и (x1, y1) в определенном направлении, через X и / или через Y. В случае масштабирования это сводится к тому, в каком направлении (ах) происходит шаг (возможно, в обоих направлениях для развлечения).

template< int XIncrement, YIncrement >
struct DrawScale
{
  void operator()(HDC dc, int step, int x0, int x1, int y0, int y1)
  {
    const int deltaX = XIncrement*step;
    const int deltaY = YIncrement*step;
    const int ymax = y1;
    const int xmax = x1;
    while( x0 < xmax && y0 < ymax )
    {
        MoveToEx(dc, x0, y0, NULL);
        LineTo(dc, x1, y1);
        x0 += deltaX;
        x1 += deltaX;
        y0 += deltaY;
        y1 += deltaY;
    }
  }
};
typedef DrawScale< 1, 0 > DrawScaleX;
typedef DrawScale< 0, 1 > DrawScaleY;

Шаблон выполнит свою работу: во время компиляции компилятор удалит все пустые операторы, то есть deltaX или deltaY равно 0, в зависимости от того, какая функция вызывается, и половина кода уходит в каждом функторе.

Вы можете добавить антиалиас, карандашный материал внутри этой функции uniq и получить код, правильно сгенерированный компилятором.

Это вырезано и вставлено на стероидах ;-)

- ppi

В исходном коде DrawScaleX не проверяется, что y0 <= y1. Однако в версии шаблона он действительно проверяет, что y0 <= y1. Таким образом, вы можете столкнуться с проблемой, что приложение больше не работает должным образом. Ознакомьтесь с решением Матей

fdk1342 19.12.2011 23:25

Другие вопросы по теме