Если Address наследуется от PhoneNumber, какие принципы ООП он нарушает?

Была книга, в которой говорилось о наличии класса PhoneNumber, а затем мы определяли бы класс Address, наследуемый от PhoneNumber, и я как-то сказал, что мы не можем этого сделать, потому что адрес — это не номер телефона, а чтобы наследовать, это должно быть отношение «является». Например: собака — это животное, и мы можем сделать так, чтобы Dog наследовалось от Animal.

Но поскольку мы должны следовать LSP — принципу замены Лисков, то правило "является" на самом деле здесь не является определяющим фактором, потому что квадрат «является» прямоугольником (с шириной == высотой), но LSP говорит, что мы не можем определить класс Square и наследовать от класса Rectangle. Простое объяснение на английском, я думаю, таково: объект aRect может ответить на сообщение setWidthAndHeight(w, h), но aSquare не может ответить на него правильно и позволить всей программе работать правильно.

Удивительно, но класс Address, наследующий класс PhoneNumber, нарушает отношение "есть", но не нарушает LSP. Тогда формально, какой принцип ООП он нарушает?


person nonopolarity    schedule 24.02.2013    source источник
comment
единственное, что он нарушает, это здравый смысл, но пока вы не знаете всю систему и ее компоненты, вы не можете просто судить о дизайне, основанном на двух объектах.   -  person mpm    schedule 24.02.2013
comment
Я думал, что 5 принципов ООП: S.O.L.I.D. заключается в том, что мы не просто руководствуемся здравым смыслом и делаем все, что хотим, а следуем принципам   -  person nonopolarity    schedule 24.02.2013
comment
Трудно ответить на этот вопрос, не зная еще нескольких деталей, но похоже, что Address может иметь более одной причины для изменения, если он наследует от PhoneNumber, что нарушает принцип единой ответственности. Более того, кто сказал, что SOLID были принципами ООП (рассмотрите GRASP, Закон Деметры, DRY, KISS).   -  person RA.    schedule 24.02.2013
comment
Что означает 'THE'? :-)   -  person mcalex    schedule 24.02.2013
comment
Пример с прямоугольником только показывает, что не каждое A является B, и его следует проектировать как наследование. Однако это не противоречит правилу, что каждое наследование должно быть отношением А есть Б.   -  person Fabian Schmengler    schedule 24.02.2013
comment
Почему aSquare не может ответить setWidthAndHeight(w, h)?   -  person Bob Horn    schedule 24.02.2013
comment
@BobHorn Он может ответить, но нарушает постусловие setWidthAndHeight: если w и h оба являются положительными числами, ширина и высота aSquare должны равняться им соответственно.   -  person A. Rodas    schedule 24.02.2013
comment
@A.Rodas Это не нарушило бы никаких условий, если бы оно выдавало исключение или иным образом обрабатывало вызов таким образом, чтобы объект оставался квадратным. Я не утверждаю, что квадрат должен быть получен из прямоугольника, просто он может, если вы этого хотите, и готов иметь дело с тем, как квадрат обрабатывает вызов.   -  person Bob Horn    schedule 24.02.2013
comment
@BobHorn Я думаю, что LSP заключается в том, что программа должна работать правильно (и не иметь побочных эффектов). Итак, если один из width и height игнорируется, то это побочный эффект, или если исходная программа устанавливает height на 0 и устанавливает width на 750 так, чтобы это была горизонтальная линия, теперь внезапно она становится большой рамкой. из 750 x 750, потому что в результате 0 был проигнорирован.   -  person nonopolarity    schedule 24.02.2013
comment
@BobHorn Да. Если вы выдаете исключение, когда w не равно h, вы усиливаете предусловие в подтипе, а это не разрешено в соответствии с правилами о предусловиях и постусловиях (другая ссылка: стр. 7 этой статьи ObjectMentor).   -  person A. Rodas    schedule 25.02.2013
comment
@A.Rodas Отличный момент. Спасибо тебе за это!   -  person Bob Horn    schedule 25.02.2013
comment
@fab: неизменяемый квадрат — это форма неизменного прямоугольника (а также форма квадрата с возможностью запроса размера); оба они, в свою очередь, являются формами прямоугольника с возможностью запроса размера. Никакая форма квадратного объекта, вероятно, не будет формой прямоугольника, размер и ширина которого могут быть установлены на независимые значения, хотя многие формы квадрата могут быть формами прямоугольника, который может быть запрошен независимой установкой высоты и ширины, но может или может не выполнять такую ​​просьбу точно так, как она дана.   -  person supercat    schedule 07.03.2013
comment
@supercat все верно. Но как это относится к моему комментарию?   -  person Fabian Schmengler    schedule 08.03.2013
comment
@fab: пригодность наследования для отношения «квадрат является своего рода прямоугольником» зависит от типа квадрата и типа прямоугольника.   -  person supercat    schedule 08.03.2013


Ответы (2)


Во-первых, это действительно действительно нарушает LSP.

Как правило, вы не ожидаете, что сможете заменить адреса на телефонные номера. Это тот «здравый смысл», о котором все говорят в комментариях.

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

Во-вторых, это нарушает ISP (принцип разделения интерфейсов).

В нем говорится, что набор методов, доступных в классе, должен быть минимальным, необходимым для функционирования объектов класса.

Если класс Address наследует (или может обоснованно наследовать) набор методов PhoneNumber, которые никогда не используются при работе с фактическим уличным адресом (например, метод getAreaCode(), который не определен для адресов домов), то его интерфейс не минимальный.

Суть в том, что принципы ООП на самом деле являются просто рекомендациями. Определенно можно создать странный пример кода, который нарушает принцип SOLID, но фактически не приводит к ошибкам. Это не означает, что вы обходите правило; это просто означает, что вы получите гораздо больше ошибок, как только попытаетесь расширить класс.

person chm    schedule 26.02.2013

В основном есть две причины использовать наследование или расширение.

  1. реализация повторного использования (для DRY)
  2. подтипы (связанные с LSP)

Я не знаю всех принципов, но в вашем случае это нарушает здравый смысл, как прокомментировал @mpm.

Потому что даже если ваш код удовлетворяет всем принципам, он все равно может быть неуместным. Другими словами, принципы не могут охватывать все.

person InsaneRabbit    schedule 25.02.2013