Як реалізувати успадкування RealNumber та ComplexNumber?


11

Сподіваємось, не надто академічний ...

Скажімо, мені потрібні справжні та складні номери у моїй бібліотеці SW.

На основі співвідношення is-a (або тут ), дійсне число - це комплексне число, де b у уявній частині комплексного числа просто 0.

З іншого боку, моя реалізація полягала б у тому, щоб дитина продовжувала батьківську, тому в батьківському RealNumber я мав би реальну частину, і дочірнє ComplexNumber додало б уявне мистецтво.

Також існує думка, що успадкування - це зло .

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

Мій досвід полягає в тому, що ми часто використовуємо спадщину для вирішення проблеми DRY, в результаті у нас часто є ієрархічні штучні абстрактні класи (у нас часто виникають проблеми знайти імена, оскільки вони не представляють об'єкти з реального світу).


7
це схоже на попереднє запитання: чи повинен прямокутник успадковувати квадрат?
комар

1
@gnat О, людина, це був ще один приклад, який я хотів використати ... Дякую!
Betlista

7
... Зауважте, що речення "дійсне число - це складне число" в математичному сенсі справедливе лише для незмінних чисел, тому якщо ви використовуєте незмінні об'єкти, ви можете уникнути порушення LSP (те ж саме має місце і для квадратів та прямокутників, див. Це ТАК відповідь ).
Док Браун

5
... Зауважте далі, обчислення абсолютної величини для складних чисел працює і для реальних чисел, тому я не впевнений, що мав на увазі ваш професор. Якщо ви правильно реалізуєте метод "Abs ()" в незмінному комплексному числі і отримаєте з нього "реальне", метод Abs () все одно дасть правильні результати.
Док Браун

Відповіді:


17

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

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

Це в основному те саме питання, що і приклад прямокутника / квадрата, згаданого в коментарі.


2
Сьогодні я кілька разів бачив цей "Принцип заміни Ліскова", мені доведеться прочитати більше про нього, бо я цього не знаю.
Бетліста

7
Цілком чудово повідомляти уявну частину дійсного числа як нуль, наприклад, за допомогою методу лише для читання. Але немає сенсу реалізовувати реальне як складне число, де уявна частина встановлена ​​на нуль. Це саме той випадок, коли успадкування вводить в оману: хоча наслідування інтерфейсу, мабуть, було б добре, наслідування при впровадженні спричинило б за собою проблематичну конструкцію.
Амон

4
Має досконалий сенс, щоб реальні числа успадковувались від складних чисел, якщо обидва є незмінними. І ви не заперечуєте проти накладних витрат.
Deduplicator

@Deduplicator: Цікавий момент. Незмінюваність вирішує багато питань, але я ще не повністю переконаний у цій справі. Доведеться подумати про це.
Френк

3

не гарний приклад успадкування, оскільки абсолютна величина цих двох обчислюється по-різному

Це на самому ділі не переконливий аргумент проти всього спадщини тут, тільки пропонований class RealNumber<-> class ComplexNumberмодель.

Ви могли б розумно визначити інтерфейс Number, який так RealNumber і ComplexNumberбуде здійснювати.

Це може виглядати приблизно так

interface Number
{
    Number Add(Number rhs);
    Number Subtract(Number rhs);
    // ... etc
}

Але тоді ви хочете обмежити інші Numberпараметри в цих операціях таким же похідним типом, як thisі до якого ви можете наблизитися

interface Number<T>
{
    Number<T> Add(Number<T> rhs);
    Number<T> Subtract(Number<T> rhs);
    // ... etc
}

Або замість цього ви б використовували мову, яка дозволяла структурний поліморфізм, а не політип поліморфізму. Для конкретного випадку чисел вам може знадобитися лише можливість перевантажувати арифметичні оператори.

complex operator + (complex lhs, complex rhs);
complex operator - (complex lhs, complex rhs);
// ... etc

Number frobnicate<Number>(List<Number> foos, Number bar); // uses arithmetic operations

0

Рішення: Не мати публічного RealNumberкласу

Я вважав би це цілком нормальним, якби ComplexNumberмав статичний заводський метод, fromDouble(double)який повертав би комплексне число з уявним рівним нулю. Потім ви можете використовувати всі операції, які ви б використовували в RealNumberекземплярі в цьому ComplexNumberекземплярі.

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

  • розширення поведінки. RealNumbersніяких додаткових операцій комплексне число не може зробити, тому немає сенсу робити це.

  • реалізація абстрактної поведінки з конкретною реалізацією. Оскільки ComplexNumberне повинно бути абстрактним, це також не стосується.

  • повторне використання коду. Якщо ви просто використовуєте ComplexNumberклас, ви повторно використовуєте 100% коду.

  • більш конкретна / ефективна / точна реалізація для конкретного завдання. Це можна застосувати тут, RealNumbersмогло б швидше реалізувати деякі функції. Але тоді цей підклас повинен бути прихованим за статичним fromDouble(double)і не повинен бути відомий зовні. Таким чином не потрібно було б приховувати уявну частину. Зовні повинні бути лише складні числа (які реальні числа). Ви також можете повернути цей приватний клас RealNumber з будь-яких операцій у складному класі чисел, що призводить до реального числа. (Це передбачає, що класи незмінні, як і більшість класів з числами.)

Це як реалізація підкласу Integer, який називається Zero та жорстким кодом деяких операцій, оскільки вони є тривіальними для нуля. Ви можете зробити це, оскільки кожен нуль є цілим числом, але не оприлюднюйте його, ховайте його за заводським методом.


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

0

Скажіть, що дійсне число - це складне число, має більше значення в математиці, особливо теорії множин, ніж у інформатиці.
У математиці ми говоримо:

  • Реальне число - це складне число, оскільки набір складних чисел включає набір дійсних чисел.
  • Раціональне число - це дійсне число, оскільки множина дійсних чисел включає набір раціональних чисел (і безліч ірраціональних чисел).
  • Ціле число - це раціональне число, оскільки множина раціональних чисел включає безліч цілих чисел.

Однак це не означає, що ви повинні або навіть повинні використовувати спадкування при проектуванні вашої бібліотеки для включення класів RealNumber та ComplexNumber. В Ефективній Java, друге видання Джошуа Блоха; Пункт 16 "Композиція користі над спадщиною". Щоб уникнути проблем, згаданих у цьому пункті, після визначення класу RealNumber він може бути використаний у вашому класі ComplexNumber:

public class ComplexNumber {
    private RealNumber realPart;
    private RealNumber imaginaryPart;

    // Implementation details are for you to write
}

Це дозволяє вам використати всю силу повторного використання вашого класу RealNumber, щоб зберегти свій код DRY, уникаючи проблем, визначених Джошуа Блохом.


0

Тут є два питання. Перший полягає в тому, що звичайно використовувати однакові терміни для типів контейнерів та типів їх вмісту, особливо для примітивних типів, таких як числа. Термінdouble , наприклад, використовується для опису як величини подвійної точності з плаваючою комою, так і контейнера, в якому він може зберігатися.

Друге питання полягає в тому, що в той час як відносини між контейнерами, з яких можна прочитати різні типи об'єктів, поводяться так само, як відносини між самими об'єктами, ті, що відносяться до контейнерів, в які можна розмістити різні типи об'єктів, поводяться навпаки серед їх вмісту . Кожна клітка, для якої відомо, що містить примірник, Catбуде кліткою, яка містить екземпляр Animal, але не повинна бути кліткою, яка містить екземпляр SiameseCat. З іншого боку, кожна клітка, яка може вмістити всі екземпляри, Catбуде кліткою, яка може вміщувати всі екземпляри SiameseCat, але не повинна бути кліткою, яка може вміщувати всі екземпляри Animal. Єдиний тип клітки, який може вмістити всі екземпляри Catі які можна гарантувати, ніколи не містить нічого, крім екземпляраCat, є клітка Cat. Будь-який інший тип клітки був би або нездатний прийняти деякі екземпляри, Catякі він повинен прийняти, або був би здатний приймати речі, які не є примірниками Cat.

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