Чи можна вирішити задачу кола-еліпса шляхом обернення відношення?


13

Маючи CircleсягатиEllipse перерв в Ліски Substition принципі , тому що вона змінює постусловіем , а саме: ви можете встановити X і Y незалежно один від одного , щоб намалювати еліпс, але X завжди має дорівнювати Y для кіл.

Але хіба тут проблема не спричинена тим, що Circle є підтипом Еліпса? Чи не могли б ми змінити відносини?

Отже, Коло - це суперпертип - він має єдиний метод setRadius.

Потім Еліпс розширює коло, додаючи setXі setY. Виклик setRadiusна Ellipse встановив би і X, і Y - значить, посткондицію в setRadius зберігається, але тепер ви можете встановити X і Y незалежно через розширений інтерфейс.


1
Ви спочатку заглянули у Вікіпедію ( en.wikipedia.org/wiki/Circle-ellipse_problem )?
Doc Brown

1
так - я навіть пов'язую це у своєму питанні ...
HorusKol

6
І ця точна точка висвітлена в цій статті, тому мені незрозуміло, що ви запитуєте?
Філіп Кендалл

6
"Деякі автори пропонують змінити зв'язок між колом та еліпсом на тій підставі, що еліпс - це коло з додатковими можливостями. На жаль, еліпси не можуть задовольнити багато інваріантів кіл; якщо коло має радіус методу, тепер Еліпс матиме щоб це також забезпечити ".
Філіп Кендалл

3
Що я вважав найяскравішим поясненням того, чому ця проблема має погані передумови, лежить у самому дні статті у Вікіпедії: en.wikipedia.org/wiki/… . В залежності від ситуації, існує кілька чистих конструкцій, але це залежить від того, що вам потрібно від цих двох класів , щоб зробити , щоб не бути .
Артур Гавлічек

Відповіді:


37

Але хіба тут проблема не спричинена тим, що Circle є підтипом Еліпса? Чи не могли б ми змінити відносини?

Проблема з цим (і проблема квадрат / прямокутник) помилково припускає, що відношення в одному домені (геометрія) має місце в іншому (поведінка)

Коло та еліпс пов'язані, якщо ви переглядаєте їх крізь призму геометричної теорії. Але це не єдиний домен, який ви можете подивитися.

Об'єктно-орієнтований дизайн стосується поведінки .

Визначальною характеристикою об’єкта є поведінка, за яку відповідає об'єкт. А в області поведінки коло та еліпс мають таку різну поведінку, що, мабуть, краще взагалі не вважати їх такими, що стосуються. У цій області еліпс і коло не мають суттєвого зв’язку.

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

Найпоширенішим прикладом цієї помилки в реальному світі є припущення, що об'єкти пов'язані (або навіть одного класу), оскільки вони мають подібні дані, навіть якщо їх поведінка сильно відрізняється. Це поширена проблема, коли ви починаєте конструювати об'єкти «дані першими», визначаючи, куди дані йдуть. Ви можете закінчити клас, пов’язаний через дані, що мають зовсім іншу поведінку. Наприклад, і оплата праці, і об'єкти працівника можуть мати атрибут "валової заробітної плати", але працівник не є типом оплати праці, а розрахунковий лист не є типом працівника.


Розмежування проблем (домену) домену щодо поведінкових та відповідальних можливостей OOD є дуже важливим моментом. Наприклад, у програмі для малювання ви, можливо, повинні мати змогу перенести коло на квадрат, але це не легко моделюється за допомогою класів / об’єктів на більшості мов (оскільки об'єкти зазвичай не можуть змінити клас). Таким чином, домен програми не завжди добре поєднується з ієрархією успадкування заданої мови OOP, і ми не повинні намагатися його змусити; у багатьох випадках склад краще.
Ерік Ейдт

3
Ця відповідь - це найкраще, що я бачив у всьому питанні, і як потенціал помилок дизайну може виникнути в більш загальних випадках. Дякую
HorusKol

1
@ErikEidt Проблему зміни поведінки об'єкта можна вирішити в OOD шляхом розкладання. Наприклад, якщо форма морфів змінюється на коло, вам не доведеться змінювати клас. Натомість клас приймає поточний об’єкт геометричної поведінки, який ви можете замінити на іншу поведінку, коли ви перетворюєтесь. Цей інший клас містить правила геометричної фігури, яка наразі моделюється, і клас формувальної форми, що формується, визначається для цього класу для геометричної поведінки. Якщо об'єкт перетворюється на інший клас, ви змінюєте клас поведінки на щось інше.
Cormac Mulhall

2
@Cormac, правильно! Я, загалом, назвав би форму композиції, як я вже згадував, хоча ви могли б визначити, конкретніше, стратегію чи інше. По суті, у вас є особистість, яка не перетворюється, та інші речі, які потім можна змінити. Загалом, добре підкреслити різницю між поняттями доменних додатків та деталями OOP даної мови та необхідністю відображення між ними (тобто архітектури, дизайну та програмування).
Ерік Ейдт

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 Так. Два класи, що успадковують інтерфейс, який вони обидва реально правильно реалізують - це зовсім чудово.
Іхрек

7

Cormac справді чудова відповідь, але я просто хочу трохи детальніше пояснити причину плутанини.

Наслідування в OO часто навчають за допомогою метафор реального світу, наприклад "яблука та апельсини - це підкласи фруктів". На жаль, це призводить до помилкової думки, що типи в ОО повинні моделюватися відповідно до деяких таксономічних ієрархій, що існують незалежно від програми.

Але при розробці програмного забезпечення типи повинні моделюватися відповідно до вимог програми. Класифікації в інших областях зазвичай не мають значення. У реальній програмі з об'єктами "Apple" та "Orange" - скажімо, система управління запасами для супермаркету - вони, ймовірно, зовсім не будуть окремими класами, а категорії типу "Fruit" будуть атрибутами, а не супертипами.

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

То чи повинен Circle бути підкласом Еліпса чи навпаки? Це повністю залежить від вимог конкретної програми, яка використовує ці об'єкти. Додаток для малювання може мати різний вибір способів поводження з колами та еліпсами:

  1. Розглядайте кола та еліпси як окремі типи фігур із різним інтерфейсом (наприклад, дві ручки розміру на еліпсісі, одна ручка на колі). Це означає, що ви можете мати еліпс, який геометрично є колом, але не Колом з точки зору програми.

  2. Обробляйте всі еліпси, включаючи кола однакові, але у них є можливість "заблокувати" x і y на одне значення.

  3. Еліпси - це лише кола, де застосовано масштабну трансформацію.

Кожна можлива конструкція призведе до різної моделі об'єкта -

У 1-му випадку «Коло» та «Еліпси» будуть класами побратимів

У другому взагалі не буде окремого класу Circle

У третьому не буде окремого класу Еліпса. Тож так звана проблема кола-еліпса не входить у малюнок ні в одному із них.

Отже, щоб відповісти на поставлене запитання: Чи повинен круг подовжувати еліпс? Відповідь: Це залежить від того, що ви хочете зробити з цим. Але, мабуть, ні.


1
Дуже гарна відповідь!
Уцав T

6

Помилково з самого початку наполягати на тому, щоб мати клас "Еліпс" та "Коло", де один є підкласом іншого. У вас є два реалістичні варіанти: один - мати окремі класи. Вони можуть мати загальний надклас для таких речей, як колір, незалежно від того, заповнений предмет, ширина лінії для малювання тощо.

Інший - мати лише один клас під назвою "Еліпс". Якщо у вас є цей клас, його досить просто використовувати для представлення кіл (можуть бути пастки залежно від деталей реалізації; Еліпс матиме деякий кут, і обчислення цього кута не повинно виникати проблем для еліпса у формі кола). Ви навіть можете мати спеціалізовані методи для кругових еліпсів, але ці "кругові еліпси" все одно будуть повноцінними об'єктами "Еліпса".


Може існувати метод IsCircle, який би перевірив, чи має певний об'єкт класу Ellipse насправді обидві осі однакові. Ви також вказали на кутове питання. Кола не можна "обертати".

3

Після пунктів LSP, одне "правильне" рішення цієї проблеми - це @HorusKol і @Ixrec, - обидва типи виходять із Shape. Але це залежить від моделі, над якою ви працюєте, тому завжди слід повертатися до цього.

Мене вчили:

Якщо підтип не може виконувати таку саму поведінку, як супертип, відношення не міститься в приміщенні IS-A - його слід змінити.

  • Підтип - це СУПЕРСЕТ супер-типу.
  • Супер-тип - це SUBSET підтипу.

Англійською:

  • Похідний тип - це СУПЕРСЕТ базового типу.
  • Базовий тип - це SUBSET похідного типу.

(Приклад:

  • Автомобіль з вихлопом для хлопчика все ще є автомобілем (на думку деяких).
  • Автомобіль без двигуна, колеса, кермової рейки, приводу поїзда та залишилася лише оболонка, це не "машина", це просто оболонка.)

Ось так працює класифікація (тобто в тваринному світі), і в основному, в ОО.

Використовуючи це як визначення спадщини та поліморфізму (які завжди пишуться разом), якщо цей принцип порушений, вам слід намагатися переосмислити типи, які ви намагаєтеся моделювати.

Як згадували @HorusKul та @Ixrec, у математиці у вас чітко визначені типи. Але в математиці коло - це еліпс, тому що це ПІДСУМКА еліпса. Але в ООП це не так, як працює спадщина. Клас повинен успадковуватись лише у випадку, якщо це SUPERSET (розширення) існуючого класу - це означає, що він все ще є базовим класом у всіх контекстах.

Виходячи з цього, я думаю, що рішення слід трохи переробити.

Майте базовий тип форми, потім RoundedShape (фактично коло, але тут я використав іншу назву DELIBERATELY ...)

... тоді Еліпс.

Цей шлях:

  • RoundedShape - це форма.
  • Еліпс - округла форма.

(Це тепер має сенс для людей у ​​мові. У нас уже чітко визначене поняття "кола", і те, що ми намагаємося зробити тут, узагальнюючи (агрегуючи), порушує цю концепцію.)


Наші чітко визначені концепції не завжди спрацьовують на практиці.

-1

З точки зору ОО еліпс розширює коло, він спеціалізується на ньому, додаючи деякі властивості. Існуючі властивості кола все ще зберігаються в еліпсі, воно просто стає складнішим і конкретнішим. Я не бачу жодних проблем з поведінкою в цьому випадку, як це робить Кормак, форми не мають поведінки. Біда лише в тому, що в ліґістичному чи математичному сенсі не вважає правильним говорити «еліпс - це коло». Тому що вся суть вправи, яка не згадується, але все-таки неявна, полягає в класифікації геометричних фігур. Це може бути вагомою причиною вважати коло та еліпс однолітками, а не пов'язувати їх у спадок і приймати, що вони просто мають одні й ті ж властивості, і НЕ дозволяють вашому виткнутому розуму ОО мати свій шлях із цим спостереженням.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.