Как стать автором
Обновить

Комментарии 259

очевидно же!
множество эллипсов включает множество кругов.
А с точки зрения ООП? Вы бы сделали наследование? Меня тогда интересует какие бы методы вы ввели для задания параметров Эллипса? Если введете метод setSize(x,y), то он бы наследовался классом Круга. Это было бы логично с точки зрения ООП или нет?

(Лично мой ответ — последний)
А если такой метод не введём? Например устали от изменяемых состояний и работаем только с неизменяемыми данными?

А если наоборот, будем только заполнять данные? (а потом складывать их будет в xml-ку, читать не будем. Скажем поскольку стоит задача преобразования одного векторного формата в другой)

Не хватает описания действий которые планируется делать с этими классами.
Для круга просто x == y. Разве это проблема? Я что-то не вижу.
Проблема в том, что пользователь эллипса ожидает получить эллипс 3x5 после вызова setSize(3,5). А если вместо эллипса подставить наследник-круг — то получится или 3x3, или 5x5.
По этому у круга будет свой setSize/1, а setSize/2 эллипса будет скрыт :)
В каком языке можно скрыть public/protected методы в потомках? И как это работает? Есть предок с методом, передали вместо предка его потомка и тут, неожиданно, оказывается что метода нет?
Что же выдаст Circle().setSize(1,2)?
Ошибку, в питоне функции не разилчаются по количеству аргументов, чтобы докапаться до нужной функции родителя придется внутри Circle вызывать super().setSize(1, 2)

Пример на php, там setSize пройдет с любым количеством аргументов
Да, я понял к чему вы клоните, написать-то можно так, что Circle себя будет корректно вести, пока его не подадут туда, где ожидается Ellipse :)
Ошибку? Ну так и смысл какой тогда в наследовании? Если метод может наследоваться, а может и не наследоваться?
Если у вас где-то надо вызвать setSize(), вы что, будете проверять, «а не круг ли это?», и вызывать метод с разным числом параметров?
Где-то вы меня обманываете.
Ошибку:
TypeError: setSize() takes exactly 2 arguments (3 given)
Ну можно перегрузить тогда метод, сделать его virtual.
Вроде как для задания эллипса нужны не два, а минимум четыре параметра:
a = Ellipse(Point(3,5), 3, Point(4,8), 4)

И, да, функции setSize нет.

В идеале делаем так:
a = Ellipse(point1, radius1, point2, radius2, filled)
Для круга соответсвенно перепишем конструктор, так, чтобы
point2 = point1
radius2 = radius1
filled = True
Простите, а куда эллипсу столько параметров?… Координаты фокусов и длина большой полуоси, всё.
Да, действительно, три параметра.
В восьмой класс, чтоли, вернуться?..
два параметра достаточно — точка «from» и точка «to».
Только при заранее заданных соглашениях и то не всяких. При двух точках эллипс можно нарисовать, навскидку, бесконечным числом способов.
Можно кинуть Exception в классе круга, если параметры не равны друг другу. А вообще вопрос какой-то чересчур общий. Вроде как «С какой стороны вы обойдете лужу после дождя: справа или слева?».
НЛО прилетело и опубликовало эту надпись здесь
в тегах к записи «ооп, программирование» + ключевая фраза «наследственную связь». А наследование как один из важнейших механизмов ООП.
НЛО прилетело и опубликовало эту надпись здесь
Ну так не надо вводить метод setSize. Можно сделать типы неизменяемыми, как string в .NET. А вместо setSize будем создавать новые инстансы через конструкторы и методы, не изменяющие состояние класса.

Интересно, в каком случае может понадобиться метод setSize, когда создание нового инстанса через конструктор не оправдано. Не приведете пример?
Подумав, понял, что конструкторов не достаточно. Нужна фабрика, которая на CreateEllipse с аргументами, дающими равные полуоси, выдаст инстанс класса Circle.
Возможно когда он подписан на событие?
ShapeOwner.ShapeChanged? Была одна фигура, а стала другая. А не эллипс изогнулся.
«когда создание нового инстанса через конструктор не оправдано»
для изменения объекта пересоздавать его вообще не стоит.
а если был элипс с размерами (3;5), его сжимает под пресом и у него размер становится (2.9;5), (2,8;5)… Каждый раз предлагаете пересоздавать? неразумно.

Про события имелось ввиду что если у вас объект подписан на несколько событий, то при его пересоздавании вы эти подписки потеряете (в смысле что придется отдельно об этом позаботиться), хотя объект всего лишь изменился, что не логично.
> а если был элипс с размерами (3;5), его сжимает под пресом
Ну скажем, не эллипс сжимается прессом, а объект, имеющий форму эллипса.
Я бы использовал объект-значение для обоих классов ) тогда круг можно реализовать как подкласс эллипса, и это будет корректно с точки зрения наследования в ООП.
Я бы сделал абстрактный класс Фигура, и от нее отнаследовал круг и эллипс. Таким образом я выбираю вариант, который отсутствует в списке: наследственная связь есть, но не та, которая приведена.
Большой вам привет от принципа Лисков
ИМХО круг — родитель. вариации на тему круга — эллипс.

Иной вариант это Окружность -> Эллипс, Круг.
НЛО прилетело и опубликовало эту надпись здесь
Другими словами:
Эллипс, окружность — кривые.
Круг — область.
p.s. Если тут подумать, кривые можно вычленить из области, т.е. своего рода параметрически понизив размерность. Исходя из этого, окружность\эллипс\(и много чего ещё) можно получить из круга, задав какую-нибудь параметрическую функцию, зависящую от полярного угла.… данный момент требует дополнительного осмысления…
а как тогда называется внутренность эллипса? правильно, тоже эллипс. точно так же, как и тор — поверхность и тело. так что с кругом эллипс в определенной степени соотносится
Нет, тело тора — Полноторие. И как правильно написано в Википедии — в народе называется бубликом.
А про эллипс Вы правы.
недавно был на публичной лекции доктора математических наук Владимира Успенского, на которой он среди прочего упомянул, что хотя термин «полноторие» действительно существует и более точно отражает суть предмета, используется он крайне редко
Я думаю у них нет наследственной свази, это скорее братья, нежели родитель/потомок.
Не могу согласиться… представим ситуацию где надо было бы выводить фигуру на экран. Реализовав это в классе элипса, можно сделать класс круга наследником, и когда ему будут задавать радиус, устанавливать разрмеры элипса (как уже писали выше). В этом случае реализация вывода на экран будет той-же… Не знаю насколько правильно так рассуждать, но более простого варианта я представить не могу. Точно так же пожалуй как квадрат и прямоугольник будут частными случаями параллелограмма…
Тогда причём тут наследование, если методы будут другими? (например, установка размера по 1 параметру, а не по двум) Не логичнее ли тогда просто иметь вложенный объект класса «эллипс»?
Выводить фигуру на экран нужно отдельным классом (или функцией), которой будут пользоваться классы эллипс и круг.

Помимо прочих преимуществ такой подход позволит отделить данные от представления.
Тогда уж функция вывода должна пользоваться методами эллипса и круга. Причём одними и теми же. Возвращаемся к проблеме наследования…
А если кругу будут задавать разный размер осей одним методом — он какой должен игнорировать — больший или меньший, первый или второй? Наследник ведь должен реализовывать все методы родителя.

А квадрат это частный случай (наследник?) ромба или прямоугольника?
ну вопервых можно setSize перегрузить и сделать там только один аргумент, тогда эта проблема отпадает. А вот по поводу утверждения «наследник должен реализовывать все методы родителя»… Отчего же? Применительно к какому языку?

А Ромб — частный случай параллелограмма, как и прямоугольник. Квадрат бы логичнее было бы сделать наследником от прямоугольника. Но это лишь моя точка зрения.
По-моему это базовое свойство наследования, что наследник может использоваться в любом месте, где может использоваться родитель.
Видимо с утра не проснулся… Тогда надо игнорировать один из аргументов. По логике — второй, а не тот который больше/меньше…
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Математически, круг это частный случай эллипса, так что по моему очевидно первое.
Поддерживаю.
Круг это усложненный математически эллипс с одинаковыми коэффициентами растяжения. При расчете у вас круг будет считаться быстрее, чем круг, создаваемые с помощью функций эллипса
У потомков и родителей в ООП могут отличаться реализации методов. Удивительно, не правда ли?
ну тем самым вы, по сути, можете переопределить все функции класса string и сделать из него Ellipse.
Ну дык обычно так и делают. Вот, например, у нас есть однобайтовая строка String. А есть потомок — многобайтовая MultibyteString. В потомке почти все методы будут иметь другую реализацию, ибо иначе невозможно. Однако и первое и второе — строка. Поэтому для «пользователя» (то есть кода, который использует эти классы) эти классы будут одинаковыми, а что там внутри нас вообще не должно волновать. В случае UI всё ещё сложнее, там разные потомки ещё и творят невесть что без нашего прямого участия. Однако ничего не ломается.
Ну по сути — переопределить в дочернем классе 90% основных функций родительского практически тоже самое, что просто переписать данный класс. Разве что влияют уже архитектурные особенности языка, вроде приведения типов
Поэтому умные люди придумали интерфейсы (:
Не совсем так. Интерфейсы придумали из-за отсутствия множественного наследования. А для приведенной выше задачи хватит и абстрактного класса.
Я к тому, что если у вас функция draw — основная,
Ellipse:draw(x,y,a,b,c)
{...}
Circle:draw(x,y,R)
{super.draw(x,y,R,R,0);}

то круг рисуется тяжело.

А если вы переопределяете функцию draw, то у вас получается по сути дубликат класса.
Почему круг рисуется тяжело? Алгортмически сложность рисования окружности и эллипса одинаковы.

Кроме того, что вы только в рисование упёрлись? Даже если бы там действительно очень разные алгоритмы использовались, наследование ведь используют не только и, в общем-то, не столько для уменьшения дублирования кода.

На самом деле ответ на вопрос неоднозначен. Не задан контекст. Какие именно объекты реального мира (или мира модели) отображаются на иерархию классов. В каких-то задачах удобно рассматривать окружность как частный случай эллипса (втч упомянутая отрисовка). В других — наоборот, удобнее считать эллипс сплющенной окружностью, тогда и наследовать надо «наоборот». В третьих, моделируемые объекты в терминах задачи могут вообще не иметь ничего общего, кроме не относящегося непосредственно к задаче визуального сходства, тогда и связь между ними не требуется.
Параметрическое уравнения окружности:
x=R*sin(t)
y=R*cos(t)
эллипса:
x=a*sin(t)
y=b*cos(t)
При a=b эллипс вырождается в окружность, при этом не вижу ни какого алгоритмического усложнения.
а теперь посчитай габариты по осям х и у для повёрнутого на непрямой угол эллипса…
Я бы вынес функциональность отвечающую за трансформации в отдельную сущность, как это сделано в WPF, и накладывал бы все трансформации на объект после просчёта им собственных характеристик без учёта трансформаций.
При таком подходе будет абсолютно безразлично кого поворачивать и как считать его габариты.
Если объект представлен в параметрической и/или векторной формах, а в дальнейшем будет преобразован в растровую форму, и к нему необходимо применить некоторый параметрический модификатор, то применять такой модификатор рекомендуется до преобразования в растровую форму. Поворачивать нужно эллипс именно пока он является эллипсом, а не когда он уже стал множеством точек конечного размера, приблизительно соответствующим изображению этого эллипса.
Технически, нет необходимости создавать дочерний класс для частного случая.

Мое мнение — эллипс и окружность должны принадлежать к классу «эллипс». Всё. :)
вместе с гипербоолй и параболой эллипс и окружность должны принадлежать классу «конические сечения» :)
математически — да.
а алгоритмически — эллипс сложнее, и не рационально в базовом классе реализовать алгоритмы нужные в некоторых производных.
Глупости. Не пойму, почему так сложно почитать определения этих объектов?

Квадратная картина вместе с рамкой является частным случаем прямоугольной рамки без картины?
тогда зачем наследовать когда можно указать параметрами, кои там есть?
по моему очевидно последнее
Согласен, мне кажется тут даже наследоваться не стоит, есть класс элипса, и именнованный параметр «круг» или дополнительный конструктор у элипса с уменьшенным кол-вом параметров, дальше все методы элипса сработают и на круге. Т.е. не вижу смысла гродить огород.
Как сработает метод эллипса scale(scaleA, scaleB) на круге?
Вы не поверите, но пару дней назад на лекции по архитектуре (Software Design and Architecture) нам привели этот же пример с теми же аргументами, но с противоположным выводом. С точки зрения архитектуры и принципов OOP — круг является базовым классом, а эллипс его расширяет и никак иначе, несмотря на «очевидное».

Кстати, такой же вопрос только с квадратом и прямоугольником задали в одной софтверной корпорации…
А ещё есть параллелограммы и ромбы…
Liskov substitution principle, вид сбоку. Эллипс никак не связан с кругом ровно по тем же причинам, по которым прямоугольник не связан с квадратом.

en.wikipedia.org/wiki/Circle-ellipse_problem
точно-точно. также сразу вспомнил про Принцип подстановки Лисков
Думаю этот вопрос нужно освещать в каждой книге по ООП
Очень огорчает, что Ваш комментарий заминусован. Все верно, у круга и эллипса различное поведение. Для круга нельзя применить функцию эллипса setSize(r1, r2).
Точно! Создал голосование, чтобы проверить подкованность хабровчан =)

«Подкласс не должен требовать от вызывающего кода больше, чем базовый класс, и не должен предоставлять вызывающему коду меньше, чем базовый класс»

Забавно, что примерно 20% считают иначе. :-)
А проверили подкованность создающего голосование. Тут нет правильного ответа из-за отсутствия требований к классам. В некоторых случаях может быть 1-й ответ, в некоторых 3-й.
Как написали ниже, эллипс можно задать одной полуосью и эксцентриситетом (соответствующие ф-ии в интерфейсе эллипса). Тогда круг — это эллипс с нулевым эксцентриситетом — переопределяем соответствующую ф-ию в классе окружности, устанавливая его всегда ноль.
А нужно ли тогда делать для круга отдельный класс? Он получается лишним. А для создания круга достаточно определить метод в классе эллипс, который бы создавал эллипс с нулевым эксцентриситетом.
Все верно, в Википедии подробно все написано.

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

LSP при использовании этого способа наследования нарушается только в том случае: если объекты наших классов мутабельны. Однако, в формулировке задачи этого нет, а в моей практике нужны были именно подобные иммутабельные классы => для неизменяемых окружностей и эллипсов подходит первый вариант. Конечно, в реальных задачах это может быть часто не так и в каждом случае нужно выбирать отдельно.
Полностью согласен. Зависит от того, как вы будете пользоваться классами, но с точки зрения математики — круг будет подклассом. Например, если у вас задача создать обьекты, сохранить, а потом отрисовать, то отличаться у классов будут только конструкторы, а принципы отрисовки круг будет наследовать у эллипса.
Преждевременная оптимизация, конечно, зло, но я бы обыграл тот факт, что окружность имеет бесконечное множество осей симметрии, а значит и горизонтальную с вертикальной и сложные алгоритмы нужны только для четверти окружности.
окружность имеет бесконечное множество осей симметрии, а значит и горизонтальную с вертикальной и сложные алгоритмы нужны только для четверти окружности.
А у эллипса с этим всего вдвое хуже.
Симметрия окружности легко обыгрывается на растровых устройствах через +- по x и y к координатам центра, целочисленная операция. С эллипсом такой оптимизации не сделать в общем случае, только если он горизонтальный или вертикальный, то есть его две оси симметрии совпадают с осями растра. А если такой, то также по четвертям рисуется, то есть не хуже у него, а так же.
У эллипса одна половина всегда получается двойным отображением (И по оси Х, И по оси Y) другой. То есть, достаточно нарисовать только половину эллипса, а потом отобразить её, заменив знаки в обеих координатах.
А вы не представляете эллипс у которого ось не параллельна оси х или у?
Представляю. А вы? Возьмите половину его и отразите относительно обеих осей.
Именно обеих, а не какой-то одной!
Если вы ответили (1) или (2), то напишите какие бы вы сделали методы для задания параметров? Например, можно ли в классе Эллипс делать метод setSize(x,y)? setWidth(x)? setHight(y)?

C математической точки зрения Круг это подкласс Эллипса. А вот с точки зрения принципов ООП?
Наследование лучше применять, когда класс потомок именно расширяет базовую сущность.
Например:
class Circle
{
int x, y, radius;
}

class ColoredCircle: extends Circle
{
Color color;
}

В случае Ellipse/Circle думаю лучшим будет такой подход:
class Ellipse
{
int x, int y, radiusX, radiusY.
}

class Cirlce: extends Ellipse
{
Circle(int _x, int _y, int _radius) //конструктор
{
super(_x, _y, _radius, _radius); //вызов конструктора класса ellipse
}
}
>Circle и ColoredCircle

Спорно. При таком подходе обычно нарушается контракт на симметричность метода equals. Именно поэтому сейчас факт наследования java.sql.Timestamp от java.util.Date считается неудачным решением в Java (см. Bloch — Effective Java)
ооп ведь создавался как способ описывать принципы материального мира а не наоборот. По этому и надо ихсодить из принципов мкатматики а не каких-то там правил ООП. Для описания елипса достаточно значений большой и малой оси (или как их там) по этому надо сделать setMajorAxis() setMinirAxis() а в отнаследованном круге переопределить их так чтобы они присваивали осям одно значение, и добавить setRadius() для полноты еффекта. я вот так считаю.
В вопросе голосования не сказано, что объекты класса должны быть изменяемыми.
>> Например, можно ли в классе Эллипс делать метод setSize(x,y)? setWidth(x)? setHight(y)?
Нет, так как эллипс задается координатами фокусов и двумя радиусами, а не длиной-шириной. =)

Можно, конечно, в функции setWidth(x) пропорционально менять радиусы и устанавливать расстояние между фокусами в x-r1-r2, но такое поведение дает нормальные результаты только когда фокусы находятся один под другим. Вывод: работа функции неоднозначна, поэтому мы ее и не делаем.

Круг — не подкласс эллипса, также как и не подкласс окружности с точки зрения геометрии.
Эллипс (на плоскости) можно задать по разному, например, координами центра, длиной осей («длиной-шириной») и углом наклона или координами центра, «радиусом», «сжатием/растягиванием» и опять углом наклона. В любом случае минимум 5 числовых параметров. А в вашем случае какой-то шестой, кажется, излишний (видимо из координат фокусов и одного радиуса можно извлечь второй, формулы лень выводить).
Зависит от задачи которую я буду решать этими классами
Ребят. Извините пожалуйста что я так тупо и наивно, но…

Отношение наследования подрузамевает связь IS-A. Если один класс является потомком другого класса, то их связывает отношение IS-A — то есть A extends B <=> A is-a B.

По-моему дальше тут обсуждать просто нечего.
Почему, можно считать эллипс вытянутым кругом.
ИМХО всё от задачи зависит.
Тут есть чего обсуждать, или Вы думаете, эта статья в wiki — просто пустой трёп?
IS-A это старая школа, сейчас звучит так: IS-A-AND-CAN-BE-REPLACED-BY.
А за IS-A преподов надо наказывать.
А насколько старая? Лет 20 назад это считалось очевидным.
Окружность — это объект класса Эллипс, у которого координаты фокусов равны между собой. =)
Окружность — это эллипс с нулевым эксцентриситетом
Вы добавите поле Эксцентриситет в описание класса Эллипс? )
Сомневаюсь, что Вы, в свою очередь, в описание класса Эллипс добавите поля «координаты фокусов» )
Я же не опровергаю Ваше утверждение, лишь привожу синонимичное
Неверно для мутабельного случая — у окружности радиус один.
С одной стороны очевидно, что круг — это частный случай эллипса, поэтому я выбрал первое.
Таким образом образом получается:

Ellipse->setHeight(height);
Ellipse->setWidth(width);

public class CCircle extends CEllipse 
{
   public function setRadius(Size);
  {
     parent::setHeight(Size)
     parent::setWidth(Size)
   }
}

То есть именно то излишество, за что ругают ООП.

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

Так что скорее склоняюсь к третьему варианту, CEllipse и CCircle разные предки какого-нибудь CCircuit.
На самом деле это та же диллема, что от чего наследовать — прямоугольник от квадрата или квадрат от многоугольника. Она не разрешима, потому что:
1. Если следовать одной точки зрения, то непонятно, наследовать квадрат от прямоугольника или ромба
2. Если следовать другой точки зрения, то непонятно, наследовать параллелограмм от прямоугольника и от ромба.



На самом деле такие вещи появляются от непонимания сути OOP. Это массовое заблуждение, что ООП как-то связанно с объектами во внешнем мире и чем дальше — тем больше оно насаживается.

Я унаследовал эллипс от прямоугольника! Потому что Circle имеет centerPoint и radius, а эллипс и прямоугольник — точки «from» и «to». У них между собой более схожее поведение.
Хм, недавно читал у Эккеля об этой дилемме :-)
И что там написано было?
Ой соврал, в последнее время столько книг перелопатил по С++, что иногда чувствую в голове кашу :-)

Короче, это было у Майерса в его 55-советах улучшить структуру и код программ. На примере одного метода показывается, что отношение «является», которое и реализуется наследованием, не подходит в данном случае. То есть в случае, когда квадрат наследуется от прямоугольника. Решения дилеммы там по-моему не предлагалось. Более точно, смотрите первоисточник :-)
Воооот! Очень правильное замечание — такие вопросы появляются из-за непонимания устройства и возможностей используемых в разработке инструментов!

Данный опрос похож на задачи типа на сосне три яблока, сколько пролетело ворон?
Интересная тенденция. ООП из объектно-ориентированного программирования плавно вырождается в ПОПу — поведенческо-ориентированное программирование. Т.е. в первую очередь поведение обуславливает схожесть объектов, а не другие факторы. В математике круг можно считать частным случаем эллипса, а можно и то и другое считать сечением n-мерных объектов.
А можно считать эллипс сплюснутым или растянутым кругом. В математике очень многие отношения образуют замкнутую связь, который каждый волен разомкнуть в удобном лично для себя месте.
там было ключевое слово «В математике»
У меня оно тоже есть
Окружность — это геометрическое место точек, равноудаленных от центра.
Эллипс — это геометрическое место точек, для которых сумма расстояний до фокусов одинакова.
Если у эллипса взять фокусы совпадающими (получим окружность), то сумма расстояний до фокусов также будет постоянной, равной удвоенному радиусу. Отсюда следует, что окружность — частный случай эллипса.
А вот провести аналогичные рассуждения в обратную сторону, увы, не получится.
Эллипс — проекция окружности на (другую) плоскость.
Вы наверное имели ввиду, что при некотором отображении окружность может перейти в эллипс? В любом случае, отсюда не вытекает, что эллипс является окружностью.
Ну, если брать именно определение «равноудалённых от центра», то поменяв формулу вычисление «удалённости» (взяв другую норму), мы легко получим эллипс.
Если бы классы были неизменяемыми, то очевидно проще сделать круг подклассом эллипса.
Поскольку на чтение они сохраняют поведение.

А вот если изменяемые, то нет, поскольку поведение круга отличается от поведение эллипса и к ним применимы разные сеттеры.
Зависит от задачи.
Круг обсчитывается быстрее эллипса, так что делать Круг потомком Эллипса невыгодно.
В то же время, думаю, Вытягивать круг будет накладнее отрисовки свежего эллипса, так что от наследования Круг -> Эллипс тоже отказался бы.
Так что я бы под задачи обсчета реализовал бы их отдельно, но, например, потомком одного класса\шаблона\зависит от языка, для упрощения однотипных задач и приведения типов.
А почему бы не заложить в алгоритм обсчета Эллипса частный случай быстрого решения в случае нулевого эксцентрисита? )
Второй вариант исключен, потому что с точки зрения ООП было бы isinstance(ellipse, Circle) == True, что полная чушь.
Я не помню где я это читал, может кто подскажет, хочу перечитать, но мысль была такова:
Принцип наследования мало подходит для моделирования обьектов из реальноый жизни. Для этого лучше подходит идея реализовывать «интерфейсы».

А вот если мы моделируем что-то из «компьютерной среды», где логика более четкая, то наследование проявляет себя во всей красе.

Основываясь на этом лучше сделать два различных класса Circle и Ellipse но сделать например интерейс Drawable, Colorable или что там надо с ними потом в будущем делать;
Если заранее известно, что Circle и Ellipse будут отрисовываться, то часто бывает лучше объявить абстрактный метод Draw предка Circuit и реализовывать его в наследниках.
Ну это ж вроде как и есть интерфейс.
Велика вероятность что у вас еще будет треугольник который тоже надо будет отрисовывать
И тогда прийдется вводит новую абстракцию «Фигура» и вот тут мы получаем сложную иерархию наследования которая будет разрастатся со временем (пока мы будем добавлять новые требования).
Я не исключаю варианта, что так будет лучно, но всё зависит от того, что должно получится в итоге.

На примере, допустим вы можете нарисовать контур и закрашеный контур. В случае использования интерфейсов, вам придется либо делать общий метод вашего Drawer-объекта для закраски, либо использовать интерфейс IPaintable.

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

Тут мы приходим к тому, что надо бы сделаеть класс с абстрактным методом реализирующий интерфейс IPaintable, от которого наследуем и Ellipse и Circle, реализующие IDrawable, но выглядит все это уже досточно громоздко.
Ох щи :) Ну вот мы подошли к тем местам ООП в которых я не силен. Вообщем сложный вопрос.
Гыгы, а топик минусуют те, кто неверно ответил? :))
Те, кто не понимает ООП-проблем.
Те, кто не любит опросов на Хабре.
Круг — это эллипс, у которого два центра совпадают
Коменты не читай @ сразу отвечай!
НЛО прилетело и опубликовало эту надпись здесь
Центра -> фокуса.
НЛО прилетело и опубликовало эту надпись здесь
Эллипс подкласс круга, у него два радиуса а у круга один :)
периметр, площадь, цвет, и др остаются те же )
функция построения перегружена
Вообще зависит от предполагаемого использования. Допустим, я пишу что-то, связанное с орбитами, и хочу избирательно для круговых орбит добавить каких-то функций. Тогда мне было бы логично наследовать круг от эллипса. С другой стороны, если я занимаюсь в большей степени качением, то наследование будет не самым лучшим решением — поведение круга и эллипса принципиально разное.
Результаты голосования неутешительные :)
Задача некорректна.
Почему же некорректна? Третий вариант ответа подходит. Ясно, что эллипс это не наследник круга, потому что эллипс с неравными полуосями — не круг. В то же время ясно, что круг это не эллипс с точки зрения ООП, потому что у эллипса есть две разные независимые полуоси, в то время как у круга только одна. Это нудно и подробно объясняет Александреску в своей книге «C++ Coding Standards».
потому, что нет варианта ответа «зависит от требований»
а какие вы бы хотели видеть результаты голосования?
Не стал бы их делать классами :)
Как неузнаваемо переформулирован вопрос: «Что было раньше — курица или яйцо?»
Как бы вы реализовали наследственную связь между классами Курица и Яйцо?
а) Курица — это подкласс Яйца
б) Яйцо — это подкласс Курицы
в) Не стал бы реализовывать наследственную связь между этими классами
Что было раньше — код или компилятор?
Не в ту сторону пошли, код был раньше.
Его скомпилировал из идей у себя в голове человек ;)
Я про двоичный код и говорю
Поговаривают, не всякий код требует компиляции.
но всяких код требуеот компилятора. Хотя бы затем, чтобы скомпилировать интерпретатор для нашего кода.
Причём интерпретатором кода может служить ткацкий станок, построенный 210 (по вики) лет назад :) А компилятором — «человек с напильником», этот станок инстанцировавший, причём очевидно, что этот процесс был интерпретацией… и т. д. вплоть до Большого взрыва или акта творения…
На хабре как-то обсуждали, что наследование не должно ограничивать функциональность а только расширять (т.е. при замене в коде родителя на любого потомка код остается работоспособен), поэтому наследовать круг от эллипса, имхо, не стоит. Наоборот тоже, но это вообще странно было бы. Думаю, у них просто должен быть общий родитель.
Ещё логично было бы иметь в круге внутренний объект эллипс, переводя все методы работы с кругом в воздействия на эллипс, благо перевод этот будет примитивным.
Т.е. чтобы не наследовать эллипс просто аггрегировать его в круг?
Или круг в эллипс…
Почему внутренний, а не внешний?
Внешний — т.е. допускающий работу с ним напрямую? Нельзя, т.к. тогда он может перестать быть кругом.
Идем от общего к частному.
«Эллипс» является кругом, когда оба его радиуса равны.
Значит «круг» наследуется от «эллипса», у него вместо друх радиусов надо просто указать один.
Решение одно и очевидное же (либо в задаче не указаны ограничения и условия, которые не допускают такого решения)
Для меня не совсем очевидно. В случае когда круг наследуется от эллипса нарушается принцип замещения, поскольку круг не может реализовать все свойства и методы эллипса, обратное возможно.
По LSP, вроде как, не выводятся они друг из друга.
В жизни проще круг не делать вовсе, дождаться фидбека и поднять продажи, как угодно реализовав его в следующей версии.
«Прописать все раз и навсегда» или максимально задействовать полиморфизм? Это больше чем разные стили программирования, это по сути фундаментальная разница идеологий, грубо говоря, Microsoft и Google с его вечной бетой.

Я предпочту второй стиль (наследовать эллипс от круга), поскольку скорее всего в ходе дальнейшей эволюции кода окажется, что наряду с кругами и эллипсами нужно будет строить over 9000 самых разных фигур и менять в следующей версии эллипс на подкласс какой-нибудь яйцевидной хрени с дополнительными параметрами x, y, j никак не улыбается. Еще раз повторю, это во многом мировоззренческий вопрос в отношении того, чем сильно ООП. ООП позволяет быстро написать код, но критичные по времени и/или размеру фрагменты лучше писать, условно говоря, на чистом асме. А если мы уже создаем разветвленную классно-объектную структуру, то должны скорее продумать, как мы этот код затем будем дописывать, а не как сделать «идеальную золотую версию» и втиснуть в нее все-все-все.
Мне кажется круг должен быть родителем, он проще
здесь скорее связь через интерфейсы и общий абстрактынй клас. например, IDrow — для отприсовки, IArea — для расчёта площади т.д., а вот прямое наследование с переопределение — идиотизм.
Если вопрос стоит «или — или», то есть наследование должно быть обязательно, то реализовал бы эллипс наследником круга окружности, добавив коэффициент «вытянутости» и ориентацию «вытянутости», то есть расширив функциональность базового класса.

Если наследование не обязательно, то сделал бы их оба «братьями», наследуемыми от класса «фигура».
добавьте тэг троллинг :)
«Круг — это подкласс Эллипса» ответили математики.
Аналогично тому что квадрат это частный случай прямоугольника :-)
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Вот вот, я тож начал было в уме цепочку строить, но вовремя остановился :-)
Для эллипса забыли угол наклона к осям(и) или вы мыслите как Коган: «Я с детства не любил овал \n Я с детства угол (прямой) рисовал»? :)

Если речь идёт о немутабельных объектах, то можно (в некоторых языках с костылями типа переменного числа параметров, а в каких, может с передачей массива неопределенной длины, а в третьих ещё как-нибудь)…
Тестирование Хабра на знание LSP, хииитро :)

Подленько отвечу: наследственную связь между классами Эллипс и Круг я бы реализовал на языке с отсутствием переменных. Haskell, наверное; впрочем, если бы нашёл время изучить Agda, то, насколько я понимаю его предназначение, там бы это получилось ещё забавнее.
Если эллипс выровненный по осям, то его можно описать как центр + 2 радиуса, а отличии от окружности которую представляют как центра и радиус. Соответственно, эллипс наследует один радиус и центр от окружности. :)
На самом деле, в вопросе недостаточно данных и по этому на него нельзя ответить точно. Сильно зависит от задачи, от требований к классам, от представления данных. Например иерархия фигур для отображения вполне возможно будет сильно отличаться от иерархии фигур для collision detection или физической симуляции.
фигура (абстрактный) — описывает метов рисовать
замкнутая фигура (абстрактный, наследуется от фигура) — описывает методы получить площадь, получить длину, получить радиус
эллипс (наследуется от замкнутая фигура) — реализует методы получить площадь, получить длину, получить радиус
круг (наследуется от замкнутая фигура) — реализует методы получить площадь, получить длину, получить радиус

т.е. наследуя их от одного родителя (а не друг от друга) мы избегаем необходимости переопределять методы расчета площади и длины
НЛО прилетело и опубликовало эту надпись здесь
Разницу между «описывает» (ака «интерфейс» или «абстрактный») и «реализует» (ака «имплементирует») знаете?
либо круг/эллипсоид,
либо окружность/круг
тьфу, блин, совсем запутался :)
прошу считать коммент несуществующим :)
а, нет, вру
эллипсоид — объёмный
Не хватает кулфейса и надписи «Habr pwned once more»
Зачем вообще нужен класс окружности? Ellipse::isCircle. Вряд ли поведение окружности и эллипса будет отличаться, и где-то вообще эта функция понадобится.
Я в paint'е хочу ровные окружности рисовать отдельной кнопкой «круг». Хочу в 2 клика (2 точки) получать результат.
А когда мне потребуется эллипс — я согласен отнестись к вопросу рисования серьезнее и кликнуть несколько раз больше.
Поведение отличается?
Пользовались хоть раз Paint? Видели как там реализовано? Есть только эллипс, круг рисуется зажатием шифта. При этом, что характерно, система не знает, что по шифту рисуется окружность, поскольку она продолжает рисовать эллипс, меняются лишь входные данные.
не, ну ок, Paint неудачный пример. Но теоретически в какой-то программе X такое возможно.
З.Ы. за шифт спасибо, не знал )
В паинте ни разу не видел круглого круга… Они с виндой сговорились и считают, что пиксель квадратный…
Отличный вопрос для Хабра, только варианты надо было немного по-другому сделать. Например, «зависит от требований» добавить.
Не, это был бы вариант правильный и потому унылый )
НЛО прилетело и опубликовало эту надпись здесь
Круг не является частным случаем эллипса, окружность является, но не круг!
НЛО прилетело и опубликовало эту надпись здесь
Встречный вопрос: Как бы вы реализовали наследственную связь между курицей и яйцом?
ООП — это инструмент для решения конкретных задач, а не средство моделирования реальности. Нет никакого смысла реализовывать реальные объекты в ООП без описания того, как они должны взаимодействовать. Нужно ли вам рисовать круги и эллипсы? Или может считать площадь? Или что-то еще? В зависимости от ситуации могут быть приемлемыми все три варианта.
Вместо «ООП» можно читать «наследование», конечно, просто с моей колокольни они практически неотрывны.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
А если вам в задаче нужно, чтобы объект возвращал набор генов? И только это.
НЛО прилетело и опубликовало эту надпись здесь
А доставать как? И нужны ли тогда вообще ваши фабрики-конвертеры? Если можно и курицу и яйцо унаследовать от одного предка(или интерфейса, или behavior'а), в котором есть методы работы с генами? И тогда можно их безболезненно передавать в функции, которым нужно работать с генами. А как вы передадите ваши egg и chicken?
Я это всё веду к остальной части своего комментария, что реализация зависит от конкретной задачи, а моделирование «в воздухе» не имеет практического смысла.
Согласно современным научным представлениям яйцо появилось раньше, чем теплокровные, включая курицу.
Архитектура — это способ реализации задачи. Что эти круг и эллипс вообще должны делать? В чем их поведение одинаково, в чем различается?

Как только это выяснится, сразу и выяснится, кто от кого наследует.

Скорее всего (круг и эллипс — мы что, графический редактор проектируем?) это вообще одна и та-же сущность в рамках задачи. Если вы в графическом редакторе растянете круг в эллипс, что, произойдет уничтожение круга и создание эллипса? Бред ведь.

Бессмысленно рассуждать об архитектуре приложения, не сформулировав задачу и не описав предметную область.
НЛО прилетело и опубликовало эту надпись здесь
Если в рамках задачи нет различий между кругом и эллипсом (и, кстати, пока задача не сформулирована — все эти архитектурные терки впустую вообще) — то вообще не доказано, что это разные объекты. Ergo, никакой фабрики пока что не надо
Если бы не было разницы вопрос бы не появился, имхо.
Эллипс — дифеоморфизм круга. Я бы сделал наследование и эллипса, и круга, от общего базового класса.
Хотя, наверное, от задачи зависит, что будет удобнее.
Спасибо за термин, а я тут «сжатие» и «растягивание» использую…
Го го разбирать, чьим подклассом и родителем является точка :)
Точка начало всего, родитель всех и вся :D
Если из предложенных вариантов, то мой — и круг и эллипс наследовать от точки. Т.К. они оба обладают только одним схожим свойством, а именно положение центра в пространстве и это можно унаследовать от точки.

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

Естественно вместо радиусов можно использовать диаметр, т.е. ширину и высоту.
В таком случае и саму точку нужно наследовать от класса «геометрическая фигура с определенным положением на плоскости»
Не знаю систем координат (то есть систем определяющих положение объекта), обходящихся без точки. Определенное положение — это связи между точками
Правильный вопрос: а зачем их наследовать?

(и не в том смысле, что «не надо наследовать», а в том, какую задачу мы решаем)
Оставим вопрос о том, надо ли вообще наследовать, и добавим в иерархию ещё один объект — дуга.
Если (как говорит большинство) круг надо наследовать от эллипса, то, получается, эллипс надо наследовать от дуги. Не крутовато-ли тогда объекту класса круг иметь в себе всю сущность эллиптической дуги и уметь делать всё то же, что и она? И, главное, зачем ему это?
У вас будет неприятный логический переход от незамкнутой дуги к замкнутой дуге. Т.к. в момент замыкания дуги у нее во-первых появляется свойство «площадь», а во-вторых может появиться в том или ином виде метод «заливки».
Чтобы избежать неприятности с площадью можно заменить «круг» на «окружность». Суть моего вопроса от этого не меняется.
У окружности, как у любой замкнутой кривой, есть свойство «площадь, ограниченная контуром». Возможно, этот параметр и окажется несущественнен для системы, а может быть реализация этого свойства и потребуется.
Никто не мешает и у дуги сделать свойство площадь, если вам так хочется. Вы в какие-то частности переходите.
Как считать будем? Выдавать 0, или будем принудительно замыкать кривую вспомогательным отрезком?
ключевое слово: сектор
А нужна ли людям площадь сектора…
да, вы правы, возможность получить площадь сектора может оказаться несущественной для системы, а может быть реализация этого свойства и потребуется =)
То есть замыкать двумя отрезками :)
Вся эта «эмуляция свойств материального мира», доведённая до крайности — чушь.

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

Как вам сделать наследование в программе, можно ответить только после того, как вы расскажете, что за программа и как вы эти объекты собираетесь использовать.
А конкретный пример можете привести когда студенты не являются наследниками людей? Только без инопланетян, студентов-собак и т. п.
В материальном мире являются.
Только программирование к «эмуляции материального мира» имеет отношение только в подобных книженциях.
НЛО прилетело и опубликовало эту надпись здесь
Метод «увеличить линейный размер в N раз» что должен делать для эллипса?
А где вариант с общим предком?
Этот вариант слишком сложен, т.к. тут уже нужен контекст задачи.
То ли наследовать от «параметрической кривой», то ли наследовать от «замкнутой кривой», то ли просто «плоский масштабируемый объект», «ограниченный объект», либо от множества других вариантов.
Суть остаётся одна — либо круг и эллипс родственники, хотя и не прямые (родитель-наследник), либо прямые, либо вообще знакомые (коль скоро встретились в одном приложении).
А вот вам еще задачка:
нужно реализовать классы Мужчина и Женщина.
Как будем наследовать:
так М<-Ж или так: Ж<-М
допускается активное использование оператора new вместо override для сокрытия базовой функциональности.

зы.
в случае с эллипсом проголосовал за 3-й вариант
Мужчина и женщина не пересекающиеся множества по определению…
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
ru.wikipedia.org/wiki/%D0%9C%D1%83%D0%B6%D1%87%D0%B8%D0%BD%D0%B0

ru.wikipedia.org/wiki/%D0%96%D0%B5%D0%BD%D1%89%D0%B8%D0%BD%D0%B0

А вообще 150 зафиксированных в истории случаев тому, что мужчина и женщина не пересекающиеся множества не противоречит — просто человечество не является объединением этих множеств. есть ещё третье подмножество — гермафродиты.
НЛО прилетело и опубликовало эту надпись здесь
Не противоречит тому, что множества мужчин и женщин не пересекаются, а расширяет множество людей, не пересекаясь с первыми двумя. Алсо их, расширяющих подмножеств, может быть ещё 100500.

«Человек» — класс, у него есть свойство «пол» — перечисление «мужчина», «женщина», «гермафродит» и может быть ещё 100500 значений. Можно оформить это и как наследников в процессе рефакторинга. А ещё нужно определиться с предметной областью — мы о биологии или, скажем, о физических лицах, как субъектах права?
НЛО прилетело и опубликовало эту надпись здесь
Вы видели или хотя бы слышали о паспортах или свидетельствах о рождении выданных с полом отличным от мужского или женского? Я — нет.
НЛО прилетело и опубликовало эту надпись здесь
Наследовать нецелесообразно, но если классы «окружность» и «эллипс» разрабатываются согласованно и небезынтересна функция преобразования типов объектов, то для класса «окружность» можно сделать метод, создающий идентичный по начертанию ей эллипс.

Хочу добавить, что многие комментаторы увлеклись «паинтовскими» эллипсами, которые весьма неполноценны. Вы забываете, что эллипс, в отличие от окружности, может быть еще и повернут на некоторый угол. Окружность, конечно тоже можно крутить вокруг ее центра, но от этого ее вид не изменится (но только до тех пор, пока мы говорим о ее изображении, если для нас нет «особых точек»), а изображение эллипса от поворота зависит в гораздо бОльшей степени.
НЛО прилетело и опубликовало эту надпись здесь
У меня в университете буквально месяца 2-3 назад был материал по ООП, и преподаватель именно с кругами и эллипсами приводил пример наследования, причём круг — поток эллипса.
Как бы точка -> эллипс (точка(центр) + радиусы) -> круг (тот же эллипс, только радиусы равны) нет?
Зря комментариев не читали.
Ну вот, спалили мой любимый вопрос на собеседовании :(
Только я прямоугольник и квадрат предпочитаю, в комментах они уже появлялись.
До ромобов и параллелограммов доходите? :)
кривая второго порядка < — эллипс < — круг
По-моему все очень хорошо решается, если сделать так:

public interface Ellipse {
    double getA();
    double getB();
}

public interface Circle extends Ellipse {
    double getR();
}

public class EllipseImpl implements Ellipse {
    public double getA() {...}
    public void setA(double a) {...}
    public double getB() {...}
    public void setB(double b) {...}
}

public class CircleImpl implements Circle {
    public double getA() {...}
    public double getB() {...}
    public double getR() {...}
    public void setR(double r) {...}
}

В итоге с точки зрения чтения (через интерфейс) имеем все плюсы наследования.
А с точки зрения записи (через конкретную реализацию) можем менять только то, что является параметром данного вида фигуры.
Код, который будет управлять параметрами фигуры, будет хранить ссылку типа конкретного класса, остальные же работают с интерфейсом.
Кстати, эту концепцию легко расширить на любые типы атрибутов фигур.
Иерархия интерфейсов при этом может сколь угодно отличаться от иерархии классов реализаций.
Вы еще скажите, что Point3D — подкласс Point2D
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории