вторник, 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;