суббота, 22 января 2011 г.

Почему дизайн должен быть функциональным?

Нередко разработчик проектирует систему, ориентируясь на что угодно, но только не на удобство её дальнейшего использования. Часто он даже не задумывается, что проектируемая система должна выполнять определённые обязанности.

Чтобы подчеркнуть мысль о том, что дизайн системы должен быть прежде всего функциональным, приведу три примера. Они взяты из разных областей человеческой деятельности, в том числе, и из software design'а.

Пример 1. Панель управления лифтом

В моём доме отремонтировали лифт. Внутри кабины сделали металлическую обшивку. После ремонта панель управления выглядит красиво, но номера этажей на кнопках не различить: цифры – слишком тусклые, а кнопки – через чур блестят.



Чтобы устранить проблему, сообразительные жильцы наклеили рядом с кнопками бумажки с номерами этажей.


Пример 2. "Квейк" на машинках

Недавно играл в шутер, в котором игроки и боты ездят на машинках. Со стороны кажется, что идея "скрестить" жанры – шутер и гонки – разумна. Однако играть в эту игру оказалось невозможно. И вот, почему.

Скорость машинки гораздо выше, чем скорость человека или монстра. Чтобы машинка могла ездить свободно, для неё нужна трасса. Классический же уровень шутера представляет собой закрытое помещение с небольшими коридорами, автоматически открывающимися дверями, лифтами, джампами и телепортами. Бегать по такому уровню удобно, а вот погонять на машинке – невозможно. Потому что, не успев набрать скорость, сразу же врезаешься в стену.

Пример 3. Странный код

В одном из обсуждений на RSDN.RU одним из коллег был приведён код на C#, который я для данной статьи "перевёл" на C++.

class IWidth
{
public:

    virtual ~IWidth() {}
    virtual int Width() const = 0;
};

class IHeight
{
public:

    virtual ~IHeight() {}
    virtual int Height() const = 0;
};

class IRectangle : public IWidth, public IHeight
{
};

class Rectangle : public IRectangle
{
public:

    Rectangle(int iWidth, int iHeight)
        : m_iWidth(iWidth),
          m_iHeight(iHeight)
    {
    }
    ~Rectangle() {}

    virtual int Width() const { return m_iWidth; }
    virtual int Height() const { return m_iHeight; };

private:

    int m_iWidth;
    int m_iHeight;
};

class ISide
{
public:

    virtual ~ISide() {}
    virtual int Side() const = 0;
    virtual int Angle() const = 0;
};

class IRombus : public ISide
{
};

class Rombus : public IRombus
{
public:

    Rombus(int iSide, int iAngle)
        : m_iSide(iSide),
          m_iAngle(iAngle)
    {
    }
    ~Rombus() {}

    virtual int Side() const { return m_iSide; }
    virtual int Angle() const { return m_iAngle; }

private:

    int m_iSide;
    int m_iAngle;
};

class ISquare : public IRectangle, public IRombus
{
};

class Square : public ISquare
{
    // Реализацию пропущу.
    // ...
};

Этот код написан в соответствии с привычными парадигмами:

  1. Наследование.
Все фигуры объединены в общую иерархию классов.
  1. Выделение интерфейсов.
Для каждого класса написан соответствующий интерфейс, который этот класс реализует.

Тем не менее, у автора хочется спросить: что делают эти классы и для чего они нужны? Какие полезные функции они выполняют?

Иерархия содержит 9 классов (по три класса на фигуру!), а её функциональность – нулевая.