Как реализовать наследование RealNumber и ComplexNumber?

11

Надеюсь, не слишком академично ...

Допустим, мне нужны реальные и комплексные числа в моей библиотеке SW.

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

С другой стороны, моя реализация состояла бы в том, что этот дочерний объект расширяет родительский, поэтому в родительском RealNumber у меня будет реальная часть, а дочерний ComplexNumber добавит воображаемую графику.

Также есть мнение, что наследство это зло .

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

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

Betlista
источник
7
это похоже на предыдущий вопрос: должен ли прямоугольник наследовать от квадрата?
комар
1
@gnat О, чувак, это был еще один пример, который я хотел использовать ... Спасибо!
Бетлиста
7
... Обратите внимание, что предложение "действительное число является комплексным числом" в математическом смысле действительно только для неизменяемых чисел, поэтому, если вы используете неизменяемые объекты, вы можете избежать нарушения LSP (то же самое относится и к квадратам и прямоугольникам, см. Это ТАК ответь ).
Док Браун
5
... Обратите внимание, что вычисление абсолютного значения для комплексных чисел работает и для действительных чисел, поэтому я не уверен, что имел в виду ваш профессор. Если вы правильно реализуете метод Abs () в неизменяемом комплексном числе и извлекаете из него «реальное», метод Abs () все равно будет давать правильные результаты.
Док Браун

Ответы:

17

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

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

Это в основном та же проблема, что и в примере прямоугольника / квадрата, упомянутом в комментарии.

Фрэнк Пуффер
источник
2
Сегодня я видел этот «принцип замещения Лискова» несколько раз, мне придется больше об этом читать, потому что я этого не знаю.
Бетлиста
7
Прекрасно сообщить мнимую часть действительного числа как ноль, например, с помощью метода только для чтения. Но нет смысла реализовывать вещественное как комплексное число, где мнимая часть установлена ​​на ноль. Это как раз тот случай, когда наследование вводит в заблуждение: хотя наследование интерфейса здесь вполне возможно, наследование реализации может привести к проблематичному дизайну.
Амон
4
Имеет смысл иметь действительные числа, наследуемые от комплексных чисел, если оба они неизменны. И ты не против накладных расходов.
Дедупликатор
@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
Caleth
источник
0

Решение: не иметь публичный RealNumberкласс

Я бы нашел, что все в порядке, если бы ComplexNumberу меня был статический фабричный метод fromDouble(double), который возвращал бы комплексное число с мнимым нулем. Затем вы можете использовать все операции, которые вы бы использовали на RealNumberэкземпляре в этом ComplexNumberэкземпляре.

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

  • расширяя поведение. RealNumbersне может делать никаких дополнительных операций, комплексное число не может делать, поэтому нет смысла делать это.

  • реализация абстрактного поведения с конкретной реализацией. Так как ComplexNumberне должно быть абстрактным, это также не относится.

  • повторное использование кода. Если вы просто используете ComplexNumberкласс, вы повторно используете 100% кода.

  • более конкретная / эффективная / точная реализация для конкретной задачи. Это может быть применено здесь, RealNumbersможет быстрее реализовать некоторые функции. Но тогда этот подкласс должен быть скрыт за статикой fromDouble(double)и не должен быть известен снаружи. Таким образом, не нужно было бы скрывать мнимую часть. Снаружи должны быть только комплексные числа (которые являются действительными числами). Вы также можете вернуть этот закрытый класс RealNumber из любых операций в классе комплексных чисел, которые приводят к действительному числу. (Это предполагает, что классы неизменны, как большинство числовых классов.)

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

findusl
источник
Я не удивлен, чтобы получить какое-то отрицательное голосование, так как у меня нет источника, чтобы доказать. Также, если у кого-то еще не было идеи, я всегда подозреваю, что для этого может быть какая-то причина. Но, пожалуйста, скажите мне, почему вы думаете, что это неправильно, и как вы могли бы сделать это лучше.
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, чтобы сохранить код сухим, избегая проблем, выявленных Джошуа Блохом.

Крейг Ноа
источник
0

Здесь есть два вопроса. Во-первых, обычно используются одни и те же термины для типов контейнеров и типов их содержимого, особенно с примитивными типами, такими как числа. Термин double, например, используется для описания значения с плавающей запятой двойной точности и контейнера, в котором оно может быть сохранено.

Вторая проблема заключается в том, что хотя отношения is-a между контейнерами, из которых могут считываться различные типы объектов, ведут себя так же, как отношения между самими объектами, те, которые существуют между контейнерами, в которые могут быть помещены различные типы объектов, ведут себя противоположно отношениям между их содержимым. , Каждая клетка, которая, как известно, содержит экземпляр, Catбудет клеткой, которая содержит экземпляр Animal, но не обязательно должна быть клеткой, которая содержит экземпляр SiameseCat. С другой стороны, каждая клетка, которая может содержать все экземпляры, Catбудет клеткой, которая может содержать все экземпляры SiameseCat, но не обязательно должна быть клеткой, которая может содержать все экземпляры Animal. Единственный вид клетки, который может содержать все экземпляры Catи может быть гарантированно, никогда не содержит ничего, кроме экземпляраCatКлетка Cat. Любой другой вид клетки либо не сможет принять некоторые случаи, Catкоторые он должен принять, либо будет способен принимать вещи, которые не являются примерами Cat.

Supercat
источник