Может ли проблема круг-эллипс быть решена путем изменения отношений?

13

Имея CircleпростиратьсяEllipse перерывам в Лиск Substition принципе , потому что она изменяет постусловие , а именно: вы можете установить X и Y независимо друг от друга , чтобы нарисовать эллипс, но X всегда должен быть равен Y для окружностей.

Но разве проблема не в том, что круг является подтипом эллипса? Разве мы не можем изменить отношения?

Итак, Circle - это супертип - у него один метод setRadius.

Затем Ellipse расширяет круг, добавляя setXи setY. Вызов setRadiusEllipse установит и X, и Y - это означает, что постусловие на setRadius сохраняется, но теперь вы можете устанавливать X и Y независимо через расширенный интерфейс.

HorusKol
источник
1
Вы сначала заглянули в Википедию ( en.wikipedia.org/wiki/Circle-ellipse_problem )?
Док Браун
1
да - я даже связал это в своем вопросе ...
HorusKol
6
И этот точный момент рассматривается в этой статье, поэтому мне неясно, о чем вы спрашиваете?
Филипп Кендалл
6
«Некоторые авторы предлагают изменить соотношение между кругом и эллипсом на том основании, что эллипс - это круг с дополнительными возможностями. К сожалению, эллипсы не могут удовлетворить многие инварианты окружностей; если у Circle есть радиус метода, Ellipse теперь будет иметь чтобы обеспечить это также. "
Филипп Кендалл
3
То, что я нашел самым ясным объяснением того, почему у этой проблемы плохие предпосылки, лежало в самом низу статьи в Википедии: en.wikipedia.org/wiki/… . В зависимости от ситуации, есть несколько чистых проектов, но это зависит от того, что вам нужно от этих двух классов делать , а не быть .
Артур Гавличек

Ответы:

37

Но разве проблема не в том, что круг является подтипом эллипса? Разве мы не можем изменить отношения?

Проблема с этим (и проблема квадрата / прямоугольника) ложно предполагает, что отношения в одной области (геометрия) сохраняются в другой (поведение)

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

Объектно-ориентированный дизайн имеет дело с поведением .

Определяющей характеристикой объекта является поведение, за которое он отвечает. А в области поведения круг и эллипс ведут себя так по-разному, что, вероятно, лучше вообще не думать о них как о связанных. В этой области эллипс и круг не имеют существенных отношений.

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

Наиболее распространенный реальный пример этой ошибки - предположить, что объекты связаны (или даже один и тот же класс), потому что они имеют схожие данные, даже если их поведение сильно отличается. Это распространенная проблема, когда вы начинаете конструировать объекты «сначала данные», определяя, куда идут данные. Вы можете получить класс, связанный через данные с совершенно другим поведением. Например, как ведомость платежа, так и объекты работника могут иметь атрибут «брутто-зарплата», но работник не является типом зарплаты, а ведомость не является типом работника.

Кормак Мулхолл
источник
Разделение интересов домена (приложения) и возможностей OOD по поведению и ответственности является очень важным моментом. Например, в приложении для рисования вы, возможно, должны иметь возможность превратить круг в квадрат, но это не так легко смоделировать с использованием классов / объектов в большинстве языков (поскольку объекты обычно не могут изменить класс). Таким образом, домен приложения не всегда хорошо сопоставляется с иерархией наследования данного языка ООП, и мы не должны пытаться форсировать это; во многих случаях состав лучше.
Эрик Эйдт
3
Этот ответ, безусловно, является лучшим из всего, что я когда-либо видел, и того, как вероятность ошибок проектирования может возникнуть в более общих случаях. Спасибо
HorusKol
1
@ErikEidt Проблема изменения поведения объекта может быть решена в OOD посредством декомпозиции. Например, если изменяемая форма превращается в круг, вам не нужно менять класс. Вместо этого класс принимает текущий объект геометрического поведения, который вы можете поменять на другое поведение при переходе. Этот другой класс содержит правила геометрической фигуры, моделируемой в настоящее время, и класс изменяемой фигуры относится к этому классу для геометрического поведения. Если объект превращается в другой класс, вы меняете класс поведения на другой.
Кормак Мулхолл
2
@ Кормак, верно! В общем, я бы назвал это формой композиции, как я уже упоминал, хотя вы могли бы определить, более конкретно, шаблон стратегии или что-то подобное. По сути, у вас есть личность, которая не меняется, и другие вещи, которые затем могут быть изменены. В целом, это хорошее выделение различий между концепциями предметной области и особенностями ООП данного языка, а также необходимость сопоставления между ними (т. Е. Архитектура, дизайн и программирование).
Эрик Эйдт
1
Но работа может быть зарплатой.
8

Круги являются частным случаем эллипсов, а именно, что обе оси эллипса одинаковы. В области задачи (геометрии) неверно утверждать, что эллипсы могут быть своего рода кругом. Использование этой некорректной модели нарушит многие гарантии круга, например, «все точки на круге имеют одинаковое расстояние до центра». Это тоже будет нарушением принципа подстановки Лискова. Как эллипс будет иметь один радиус? (Но не setRadius()более важно getRadius())

Хотя моделирование кругов как подтипа эллипсов не является в корне неверным, именно введение изменчивости нарушает эту модель. Без setX()и setY()методов, нет никакого нарушения LSP. Если необходимо иметь объект с разными размерами, лучше создать новый экземпляр:

class Ellipse {
  final double x;
  final double y;
  ...
  Ellipse withX(double newX) {
    return new Ellipse(x: newX, y: y);
  }
}
Амон
источник
1
хорошо - так, если бы какой - то общий интерфейс между Ellipseи Circle(например getArea) , которые были бы абстрагируются к типу Shape- может Ellipseи Circleотдельно от подтипа Shapeи удовлетворяют LSP?
HorusKol
1
@HorusKol Да. Два класса, унаследовавших интерфейс, который они действительно правильно реализуют, вполне подойдут.
Ixrec
7

У Кормака действительно отличный ответ, но я просто хочу немного рассказать о причине путаницы.

Наследование в ОО часто преподается с использованием реальных метафор, типа «яблоки и апельсины являются подклассами фруктов». К сожалению, это приводит к ошибочному убеждению, что типы в ОО должны моделироваться в соответствии с некоторыми таксономическими иерархиями, существующими независимо от программы.

Но при разработке программного обеспечения типы должны моделироваться в соответствии с требованиями приложения. Классификации в других областях обычно не имеют значения. В реальном приложении с объектами «Apple» и «Orange» - скажем, системой управления запасами для супермаркета - они, вероятно, вообще не будут отдельными классами, а такие категории, как «Фрукты», будут скорее атрибутами, чем супертипами.

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

Так должен ли Круг быть подклассом Эллипса или наоборот? Это полностью зависит от требований конкретного приложения, которое использует эти объекты. Приложение для рисования может иметь различные варианты обработки кругов и эллипсов:

  1. Обрабатывайте круги и эллипсы как отдельные типы фигур с различным пользовательским интерфейсом (например, две ручки изменения размера на многоточии, одна ручка на круге). Это означает, что вы можете иметь эллипс, который геометрически является кругом, но не кругом с точки зрения приложения.

  2. Обрабатывайте все эллипсы, включая кружки, одинаково, но есть возможность «зафиксировать» x и y на одном значении.

  3. Эллипсы - это просто круги, где применяется масштабное преобразование.

Каждый возможный дизайн приведет к различной модели объекта -

В 1 - ом случае, Круг и эллипсы будут двойников классы

Во 2-м классе не будет отдельного класса Circle.

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

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

JacquesB
источник
1
Очень хороший ответ!
Утсав Т
6

С самого начала было ошибкой настаивать на наличии классов «Ellipse» и «Circle», где один является подклассом другого. У вас есть два реалистичных варианта: один - иметь отдельные классы. Они могут иметь общий суперкласс для таких вещей, как цвет, заполнение объекта, ширина линии для рисования и т. Д.

Другой - иметь только один класс с именем "Ellipse". Если у вас есть этот класс, его достаточно просто использовать для представления окружностей (могут быть ловушки в зависимости от деталей реализации; у эллипса будет некоторый угол, и вычисление этого угла не должно создавать проблем для эллипса в форме круга). Вы могли бы даже иметь специализированные методы для круглых эллипсов, но эти "круглые эллипсы" все равно были бы полными объектами "эллипса".

gnasher729
источник
Может существовать метод IsCircle, который проверял бы, имеет ли конкретный объект класса Ellipse обе оси одинаковыми. Вы также указали на проблему углов. Круги нельзя вращать.
3

Следуя пунктам LSP, одно «правильное» решение этой проблемы заключается в том, что натолкнулись @HorusKol и @Ixrec - вывод обоих типов из Shape. Но это зависит от модели, с которой вы работаете, поэтому вы всегда должны вернуться к этому.

То, чему меня учили, это:

Если подтип не может выполнять то же поведение, что и супертип, отношение не сохраняется в предпосылке IS-A - его следует изменить.

  • Подтип - это СУПЕРСЕТ супертипа.
  • Супертип - это ПОДПИСЬ подтипа.

По-английски:

  • Производный тип - это СУПЕРСЕТ базового типа.
  • Базовый тип - это ПОДПИСЬ производного типа.

(Пример:

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

Вот как работает классификация (т.е. в мире животных) и, в принципе, в ОО.

Используя это как определение наследования и полиморфизма (которые всегда пишутся вместе), если этот принцип нарушается, вы должны попытаться переосмыслить типы, которые вы пытаетесь смоделировать.

Как уже упоминалось @HorusKul и @Ixrec, в математике у вас есть четко определенные типы. Но в математике круг - это эллипс, потому что это ПОДПИСЬ эллипса. Но в ООП наследование не работает. Класс должен наследоваться только в том случае, если он является SUPERSET (расширением) существующего класса, то есть он все еще является базовым классом во всех контекстах.

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

Имейте базовый тип Shape, затем RoundedShape (фактически круг, но я использовал другое имя здесь СВОБОДНО ...)

... затем Эллипс.

Сюда:

  • RoundedShape это форма.
  • Эллипс - это RoundedShape.

(Это теперь имеет смысл для людей на языке. У нас уже есть четко определенная концепция «круга», и то, что мы пытаемся сделать здесь, обобщая (агрегирование), нарушает эту концепцию.)

Энди Гуд
источник
Наши четко определенные концепции не всегда работают на практике.
-1

С точки зрения ОО эллипс расширяет круг, он специализируется на нем, добавляя некоторые свойства. Существующие свойства круга все еще сохраняются в эллипсе, он просто становится более сложным и более конкретным. Я не вижу никаких проблем с поведением в этом случае, как Cormac, формы не имеют поведения. Единственная проблема заключается в том, что в лингвистическом или математическом смысле неправильно говорить, что «эллипс - это круг». Потому что весь смысл упражнения, который не упоминается, но тем не менее подразумевается, заключался в классификации геометрических фигур. Это может быть хорошей причиной для того, чтобы рассматривать круг и эллипс как равноправных, не связывать их наследованием и признавать, что они просто имеют одни и те же свойства и НЕ позволять вашему искривлённому ОО-уму иметь дело с этим наблюдением.

Мартин Маат
источник