вторник, 19 июля 2011 г.

Проблема эллипса и окружности

Нередко среди приверженцев объектного подхода возникают споры о том, как правильно унаследовать тот или иной класс. Как правило, споры эти выливаются в длинные обсуждения и не приносят ощутимого результата. Каждый из участников дискусии остаётся при своём мнении.

Одной из проблем, которая порождает подобные дискуссии, является проблема эллипса и окружности. Её можно сформулировать так:

Как с точки зрения объектно-ориентированного подхода правильно объединить в единую иерархию два класса - класс Эллипс и класс Окружность:

Унаследовав Эллипс от Окружности?
Или унаследовав Окружность от Эллипса?

Одни программисты выступают за вариант (1), а другие - за вариант (2).

У этих двух групп людей разные подходы к использованию наследования.

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

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

Какой же из этих подходов является более правильным?

На взгляд автора данного блога - никакой. Приверженцы обоих подходов рассматривают различные атрибуты объектов, которых пытаются классифицировать. Но они не рассматривают самого главного - назначения объектов и цели классификации.

Основная ошибка сторонников обоих подходов - классификация ради классификации. Если посмотреть на различные научные дисциплины, то можно понять, что классификация используется в них для упорядочения знаний об окружающем мире. Так уж сложилось, что возможности человеческой памяти не безграничны. И хорошая классификация позволяет представить больший объём знаний наименьшим количеством сущностей.

Таким образом, одна из задач классификации в науке - это сокращение объёма требуемой памяти без сокращения количества информации.

Должны быть задачи и у классификации при проектировании программы. Такими задачами могут быть:

Сокращение объёма кода, который нужно написать.
Упрощение кода (алгоритма).

Иными словами, автор данного блога предлагает смотреть на классификацию как на инструмент, который позволяет уменьшить код.

В этой связи, бесполезно рассматривать различные атрибуты классов Эллипс и Окружность в отдельности от кода, который нужно написать для работы с ними. Основания для классификации нужно искать не внутри самих объектов, а снаружи.

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

Один параметр - окружность, квадрат, правильный N-угольник.
Два параметра - эллипс, прямоугольник, ромб.
Три параметра - прямоугольник с закруглёнными углами, правильная трапеция.
И т.д.

В результате, имеем не одну, а несколько иерархий, которые различаются количеством параметров функции SetSize:

class A: SetSize(float x);
class B: SetSize(float x, float y);
class C: SetSize(float x, float y, float z);

class Circle: public A;
class Ellipse: public B;
class RoundRect: public C;

8 комментариев:

  1. о какие интересные подходы - расширение, ограничение. А как насчет классики - что от чего можно наследовать определяется простым отношением - "ЯВЛЯЕТСЯ"?
    Эллипс НЕ ЯВЛЯЕТСЯ окружностью, а окружность ЯВЛЯЕТСЯ частным случаем эллипса?

    ОтветитьУдалить
  2. > Унаследовав Эллипс от Окружности?
    > Или унаследовав Окружность от Эллипса?

    > Одни программисты выступают за вариант (1), а другие - за вариант (2).

    А как же третья группа - группа которая и то и другое унаследует, к примеру, от абстрактного класса "фигура", в котором собраны общие свойства, такие как "толщина линии", "цвет линии" и т.п. ?

    ОтветитьУдалить
  3. программистам обоих групп читать про один из базовых принципов ооп - http://ru.wikipedia.org/wiki/Принцип_подстановки_Лисков :)

    ОтветитьУдалить
  4. что-то мне Лиски ясность не внесли...

    ОтветитьУдалить
  5. А каким образом в обоих группах нарушается принцип?

    ОтветитьУдалить
  6. 2 Alexey: Один объект может являться частным случаем другого объекта только уже в рамках построенной когда-то кем-то классификации. Если классификации нет, и Вы её только строите, то этот критерий неприменим. С другой стороны, если классификация есть, то не факт, что она Вам подойдёт, т.к. в программировании она должна Вам помогать (== сокращать код), а не усложнять Вам жизнь.

    2 Ахмед: Классический случай с иерархией классов фигур я рассмотрел в серии статей "Проектирование графического редактора". См. здесь: http://askofen.blogspot.com/2011/07/blog-post.html

    ОтветитьУдалить
  7. Согласно принципу LSP объект производного класса может быть использован везде, где ожидается объект базового класса. Здесь явное нарушение, т.к. для эллипса бессмысленно понятие радиуса - он задается двумя полуосями, а для окружности бессмысленны понятия двух полуосей - это по сути одна и та же величина - радиус. То, что в математике эти понятия обобщаются, не означает, что обобщается функциональная работа с ними.
    Вообще пример довольно синтетический и "книжный", автор, давай примеры из практики :)

    ОтветитьУдалить