×Закрыть

Композиция vs Наследование в Java

Image via Shutterstock.

В чём отличие между абстрактным классом и интерфейсом?
В чём отличие между композицией и наследованием?

Так получилось, что эти вопросы я задал довольно большому количеству людей на собеседованиях. И, как мне кажется, есть определённое непонимание этих базовых концепций, вернее, расхождение между теорией и практикой. Данная статья призвана внести ясность и улучшить мир код.

Немного теории

С композицией всё просто. Большой объект состоит из меньших и выполняет (делегирует) какую-то работу с их помощью. Например, автомобиль состоит из кузова, двигателя, колёс и т.д. А метод ехать() реализован что-то вроде: двигатель.работает(), колёса.крутятся() и поэтому кузов.перемещается().

С наследованием, на мой взгляд, сложнее. То есть определение «механизм языка, позволяющий описать новый класс на основе уже существующего» — это, конечно, хорошо, а когда его использовать?

Есть критерий, что композиция — это отношение has-a, тогда как наследование — is-a. Есть принцип Лисков, третья буква в абревиатуре SOLID, который утверждает, что наследуемый класс должен дополнять, а не замещать поведение базового класса. Об этом, кстати, прямо намекает ключевое слово extends в Java. Есть Джошуа Блох, который в Effective Java говорит, что наследование — это сильная связь.

На мой взгляд, даже сам термин наследование не очень удачный, не отображает суть, и правильнее было бы использовать «дополнение», но традицию не изменить. И вообще, было бы неплохо различать наследование классов (дополнение реализации?!) и наследование интерфейсов (дополнение контракта — это пожалуйста).

Переходим к практике

А теперь от игры словами давайте перейдём к написанию кода.

Итак, задача. Нужно разработать модуль генерации отчётов для банка. Каждый отчёт состоит из трёх частей — заголовка (header), собственно тела отчёта (body) и колонтитула (footer). Формируют их некие методы. Формирование header и footer для всех отчётов одинаково и меняться не будет. Поэтому их код разумно переиспользовать. Body для каждого отчёта, естественно, специфично. Начинаем с двух отчётов, дальнейшие пока согласовываются с заказчиком.

Собственно, вопрос сводится к такому: есть четыре блока кода — header(), footer(), body1() и body2(). Как разложить их по классам?

И тут происходит переломный момент. Несмотря на все правильные определения вначале, многие почему-то предлагают такое решение.

class BaseReport {
  void printHeader() {
    // 100 lines of header code
  }
  void printFooter() {
    // 50 lines of footer code
  }
  abstract void printBody() {}
  
  void print() {
    printHeader();
    printBody();
    printFooter();
  }
}

сlass Report1 extends BaseReport {
  @Overrride
  void printBody() {
    // specific body of Report1
  }
}

BaseReport report1 = new Report1();
report1.print();

Аналогично для второго отчёта. Вроде как хороший вариант, применён шаблон проектирования Template Method, но есть нюанс.

Почему так делает большинство кандидатов, я не знаю. Думаю, дело в литературе, где наследование объясняется на неудачных примерах, в результате формируется убеждение, что главное в ООП — это наличие иерархии классов, а о Лисков и Блохе сразу не упоминают.

Если вы видите проблему, поздравьте себя, уровень вашего мастерства явно выше Junior, если не видите — сейчас проблема будет :-)

По этой схеме пишутся второй, третий и так далее отчёты, а, допустим, в пятом появляется уточнение — колонтитул (footer) не нужен. Окей, ломать не строить, его можно убрать.

class Report5 extends BaseReport {
  @Overrride
  void printBody() {
    // specific body of Report 5
  }

  @Overrride
  void printFooter() {
  }
}

Пустой метод выглядит немного странно, но задачу свою выполняет.

Идём дальше (в следующих примерах я буду опускать body). В шестом отчёте нужно после колонтитула добавить ещё какой-то блок, там, список использованной литературы (appendix). Есть два варианта — либо по аналогии с printBody() добавить абстрактный метод printAppendix() в базовый класс, в этом классе его переопределить, а во всех предыдущих отчётах добавить его пустым, либо исхитриться так:

class Report6 extends BaseReport {
  @Override
  void printFooter() {
    super.printFooter();
    printAppendix();
  }
  
  void printAppendix() {
    // 50 lines of appendix code
  }
}

Коряво, но пусть будет. Дальше, в седьмом отчёте нужно сделать полностью другой заголовок. Тут (или даже раньше) можно, конечно, начать возмущаться. Как же так? Ведь в условиях задачи было недвусмысленно сказано, заголовок и колонтитул изменяться никогда не будут. Да, небольшой подвох. Даже не подвох, а обычная рабочая ситуация. Ну, изменились требования, бывает. Что же теперь делать?

class Report7 extends BaseReport {
  @Override
  void printHeader() {
    // 30 lines of completely another header code
  }
}

Окей, ещё один отчёт с таким же другим заголовком. Так, что ли?

class Report8 extends Report7 {
  // using printHeader() from the parent class

  @Overrride
  void printBody() {
    // specific body of Report 8
  }
}

А если заголовок как в 7-ом отчёте, а колонтитул как в 10-ом? От кого наследоваться?

Или так. Восьмой отчёт практически соответствует начальным условиям. Только в стандартном заголовке выводится текущая дата, а здесь её не нужно. Это же совсем маленькое (показывает пальцами) изменение, правда? Т.е. у нас есть 100 строк кода, которые формируют заголовок, в 78-ой выводится эта дата, как её убрать?

Скопипастить и оставить 99 строк? Плохо!

Снова модифицировать базовый класс (и заодно всю кучу его наследников) и добавлять булевый метод, типа, нужно ли выводить дату с return true по умолчанию, а здесь его переопределять? Тоже нездоровое решение.

Очевидно, наш дизайн зашёл в тупик, и виной этому, увы, неправильное применение наследования. Мы вовсю противоречим принципу Лисков и только тем и занимаемся, что переопределяем поведение.

Как исправить ситуацию

Давайте посмотрим на задачу с самого начала. От «отчёты состоят из трёх блоков, два из которых неизменны» она превратилась в «разные отчёты состоят из неких частей, какие-то из них общие или похожи, но не всегда». Требования эволюционировали (так обычно и бывает), а наследование вроде как и помогло нам избежать дублирования кода в первых двух классах, но мешало дальше, причём всё больше. Вот и проявилась сильная связность — все последующие отчёты мы буквально запихивали в «схему».

Как быть? Собственно, слово «состоит» в условии задачи уже намекает, что композиция будет более подходящим решением в этом случае.

Итак, у нас будет не базовый класс, а интерфейс.

interface Report {
  void print();
}

Далее, у нас будут, допустим, классы DefaultHeader и DefaultFooter тоже с методами print(), что наводит на мысль и его вынести в интерфейс ReportSection, а может Report и ReportSection будут одним и тем же.

class Report1 implements Report {
  DefaultHeader header;
  Body body;
  DefaultFooter footer;

  @Override
  void print() {
    header.print();
    body.print();
    footer.print();
  }
  
  // nested class
  class Body {
  }
}

И ответы на все заданные выше вопросы — отчёт без колонтитула, с списком после колонтитула, с другим заголовком, снова с другим заголовком — решаются элементарно.

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

class Report8 implements Report {
  HeaderFactory headers;
  Body body;
  DefaultFooter footer;

  @Override
  void print() {
    ReportSection header = headers.create(WITHOUT_DATE);

    header.print();
    body.print();
    footer.print();
  }
}

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

Также хотелось бы обратить внимание, что код стал объекто-ориентированным header.print(), а не процедурным printHeader(), как раньше.

Резюме

Итак, нельзя сказать, что какие-то подходы правильные или неправильные, всё зависит от ситуации, но

1) если общая функциональность выносится в родительский класс;

2) появляются слова Base и abstract;

3) в наследниках переопределяются или используются методы родительского класса

... скорее всего, что-то пошло не так и проблемы не за горами.

Можно сформулировать даже проще. Наследование — один из базовых принципов ООП. Не используйте наследование! (имеется в виду наследование классов; дополнять интерфейсы можно).

Методом исключения получается, что «хорошее» наследование — это добавление новых методов, которые используют исключительно вновь добавленные поля этого класса, но никак не родительские методы.

Тут бы надо привести пример этого самого правильного наследования, но поскольку абсолютное большинство задач гораздо проще решаются композицией и наследованием интерфейсов(!), я, честно говоря, затрудняюсь это сделать.

Вернёмся к примеру с автомобилем. Допустим, есть класс Автомобиль с методами ездить(), сигналить() и т.д. Как бы могло выглядеть его наследование? Что значит дополнение автомобиля не с точки зрения программирования, а в обычном житейском понимании?

Предположим, мы хотим сделать Боевой Автомобиль, который умеет всё то же самое, что и обычный (отношение is-a), но кроме того на нём будет установлен пулемёт с методом стрелять().

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

Можно было бы обратиться к биологии с её, на первый взгляд, незыблемой иерархией классов, но даже там всякие рыбообразные дельфины и нелетающие пингвины подпортят концепцию :-)

Рекомендую также почитать статьи:
— Интерфейс vs. Классы;
— Принцип подстановки Барбары Лисков;
— Я не знаю ООП.


P.S. Для усвоения материала напомню классическую задачу про наследование.

Для геометрических фигур есть методы подсчёта периметра и площади. Рассмотрим прямоугольник со сторонами a и b, т. е. класс с двумя сеттерами. Его периметр определяется по формуле P = 2a + 2b, а площадь S = ab. Квадрат — это частный случай прямоугольника, его единственная характеристика — длина стороны a (достаточно одного сеттера), а формулы периметра и площади можно переиспользовать, полагая b = a.

Вопрос. Как, учитывая эти факты, построить иерархию классов: унаследовать Прямоугольник от Квадрата или Квадрат от Прямоугольника?

Авторский ответ на задачу

То, что квадрат является частным случаем прямоугольника никоим образом не означает, что должна быть иерархия классов и наследование. Нам достаточно интерфейса Фигура и класса Прямоугольник, который его реализует. Нет причин не сделать его иммутабельным, без сеттеров, с конструктором на два аргумента. Для частного случая — квадрата — может быть либо конструктор с одним аргументом, либо, что лучше, как рекомендует Блох, статический фабричный метод с названием.

LinkedIn

Лучшие комментарии пропустить

Также хотелось бы обратить внимание, что код стал объекто-ориентированным header.print(), а не процедурным printHeader(), как раньше.
Эта фраза — квинтэссенция всей статьи: ООП — это когда выражения с точечкой. :)
ReportSection header = headers.create(WITHOUT_DATE);
Главное, что композиция даёт нам полную гибкость.
Ну круто, супер гибкость — добавим вытягивание зависимости прямо в методе вывода. Своим опусом вы породили не меньше инвалидов от программирование чем университетские сказки про три кита.
Вопрос. Как, учитывая эти факты, построить иерархию классов: унаследовать Прямоугольник от Квадрата или Квадрат от Прямоугольника?
Просто экскурс в историю:
Задача про прямоугольник и квадрат для демонстрации ООП появилась как часть задачи про графический редактор, а не про геометрические фигуры. И демонстрировала в основном трейдоф между производительность и переиспользованием кода.
.
P.S. Посыл про то что наследованием классов надо пользоваться очень аккуратно правильный, но это не отменяет всех тех глупостей которые есть в статье.

152 комментария

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

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

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

Я також з вами згідний, що по відношенню до наведеного вище прикладу можна дискутувати по цим концепціям але назва статті "

Композиция vs Наследование в Java
" закликає нас до ідейного холівару ;)

От себя могу добавить, что наследование это «конституция» приложения, а композиция (использование подклассво) это сопутствующие законы этого приложения. Они могут не пеесекаться, но обязаны четко следовать логике наследования методов. Абстрактные классы обижены зря — это статут того, как будут вести себя наследники (завещание). Интерфейсы — нотариусы. //и нет, я не юрист

Возник ряд вопросов собственно по собеседованию:
О собеседованиях на какую позицию/роль идет речь?
С какой целью на собеседовании вы задаете вопросы об отличии абстрактного класса от интерфейса, наследования от композиции?
С какой целью даете задание на проектирование?
Как по ответам вы решаете, принять кандидата на работу или нет?
Как и кем определяется правильность ответа или решения?

Спасибо за вопросы.

1. Middle java developer.
2. Вводные вопросы для общей оценки знаний кандидата в ООП. Например, кандидат может не знать понятия интерфейса.
3. Практически проверить понимание проектирования на простом примере.
4. Решение принимает менеджер с учётом в том числе технического фидбека. Если кандидат даёт правильные ответы, фидбек позитивный.
5. Если кандидат предлагает дизайн, позволяющий без копи-паста скомпоновать отчёты, ответ засчитывается. Мною. Если говорит, что не знает, как решить задачу, ответ не засчитывается.

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

А теперь про собеседование.

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

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

Спринт и марафон — это разные дисциплины :)На простом примере вы проверите только то как человек справляется с простыми (а в вашем случае универскими) задачами.

Спасибо, кэп. А на сложном примере — как со сложными задачами. А как с работой будет справляться — дайте угадаю — по результатам испытательного срока?

Если бы вы привели какую-то конкретику, цитаты из комментов
Так привел же:
1)
Также хотелось бы обратить внимание, что код стал объекто-ориентированным header.print(), а не процедурным printHeader(), как раньше.
Оказывается сообщения можно посылать не только другим объектам.
2)
вы приравняли наследование и то что часто называют «наследование состояния»
3)
Методом исключения получается, что «хорошее» наследование — это добавление новых методов, которые используют исключительно вновь добавленные поля этого класса, но никак не родительские методы.
Своим «хорошим» наследованием вы требуете отказаться от отказаться от целого пласта полиморфизма.
4)
Пункт 3 «в наследниках переопределяются» противоречит принципу Лисков, который, конечно, тоже не догма, а сигнал.
А тут помимо ООП, возникают еще и вопросы про понимание СОЛИД.
---
Спасибо, кэп. А на сложном примере — как со сложными задачами.
Плохая новость:
На сложном примере вы вообще ничего не проверите. Ибо собеседование это стрессовая ситуация, и если у вас нормальные условия труда, то результаты будут не релевантны.
А как с работой будет справляться — дайте угадаю — по результатам испытательного срока?
Где-то так. Задача собеседования оценить общую (общую техническую) адекватность кандидата и то как он вольется в команду.
Собеседование — это не экзамен, тут не эффективно использовать понятие «ответ засчитан или нет».
Оказывается сообщения можно посылать не только другим объектам

Мне видится, что посылка сообщения — это несколько более широкое понятие чем вызов метода. И, да, мне кажется, что предпочтительнее, чтобы в коде было именно объект.метод(), я собираюсь написать пост на эту тему.

Богдан, раз вы критикуете этот момент, может не откажете в любезности взглянуть на черновик? Или сразу какой-то аргумент приведёте, может я полностью заблуждаюсь?

Своим «хорошим» наследованием вы требуете отказаться от отказаться от целого пласта полиморфизма.

Я вообще призываю наследованием не пользоваться, чего тут пластами мелочиться :-) Также честно написал, что затрудняюсь привести хороший обучающий пример. В комментах два раза упомянули иерархию UI-контролов. Может вы что-то добавите из рабочей практики или упомянутого пласта?

Пункт 3 «в наследниках переопределяются» противоречит принципу Лисков

Спасибо, вы совершенно верно поправили.

Нет, переопределение не противоречит. Оно может противоречить, а может и нет.

Нужно сформулировать как-то мягче, в стиле «если уж переопределяйте, то крайне аккуратно, помните про LSP, не изменяйте поведение»

Собеседование — это не экзамен, тут не эффективно использовать понятие «ответ засчитан или нет».

И я так считаю, но мне нужно выражаться яснее. Разрешите переформулировать. Если кандидат в задаче про отчёты может перейти к композиции при изменении требований, его техническая адекватность оценивается выше, нежели позиция «Общий код всегда должен выноситься в базовый класс, других вариантов я предолжить не могу».

И, да, мне кажется, что предпочтительнее, чтобы в коде было именно объект.метод(), я собираюсь написать пост на эту тему.
Будем надеятся что у вас получится что-то адекватное. А то для неадквата есть такой персонаж как Егор Бугаенко.
Или сразу какой-то аргумент приведёте, может я полностью заблуждаюсь?
Пока только один аргумент: не вижу ни какой разницы :)
Я вообще призываю наследованием не пользоваться, чего тут пластами мелочиться :-)
целого пласта полиморфизма.
Я так понимаю что вы просто не правильно прочитали мое сообщение, я говорил о полиморфизме, а не о наследовании (состояния).
Если кандидат в задаче про отчёты может перейти к композиции при изменении требований, его техническая адекватность оценивается выше, нежели позиция «Общий код всегда должен выноситься в базовый класс, других вариантов я предолжить не могу».
1) Моя критика была к задаче про квадрат. Моя ошибка, задача про репорты вполне вменяема для начала разговора.
2) Тут важнее негибкость позиции человека чем его ответ, ибо на наследовании задачу про репорты вполне можно решать (я бы решал так как описал ниже, но наследование имеет право на жизнь), при условии что их типы не формируются в рантайме.
Богдан, раз вы критикуете этот момент, может не откажете в любезности взглянуть на черновик?
1) Луше если это буду не только я, а несколько человек (2-3)
2) Зависит от стадии черновика. Если это хотя бы РС1, то могу посмотреть.
2.1) В замечаниях попадут те моменты которые я посчитаю «шизофреническим бредом» (если такие будут). Те моменты с которыми я просто не согласен все равно будут в комментах :)
2.2) Подобная «вычитка» обычно занимает 3-5 дней.

Спасибо за ответы. Они дают контекст для того, что описано в статье.

2. Вводные вопросы для общей оценки знаний кандидата в ООП. Например, кандидат может не знать понятия интерфейса.
Если кандидат не знает, то собеседование заканчивается?
3. Практически проверить понимание проектирования на простом примере.
Затруднюсь сказать, чего бы я ожидал от мидла в плане проектирования.

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

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

В статье говорится, что большинство кандидатов выбирают реализацию с наследованием. Я бы спросил их: а какие еще есть варианты? какие у каждого варианта достоинства и недостатки? почему выбран именно такой вариант?

Я не вижу проблемы в том, что кандидат не знает каких-то паттернов или выберет «не тот» вариант. Потому что с большой вероятностью на проекте уже есть устоявшаяся архитектура и он будет добавлять к существующим 50 отчетам 51-й. И будет смотреть на них как на пример. И если в существующих отчетах есть копи-пейст, то печаль-печаль. А дальше будет код ревью более опытными товарищами по команде. Ну не верю я, что вы дадите ему сразу с нуля проектировать новую систему, или новый оригинальный модуль в имеющейся системе.

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

Давайте будем на собеседовании и в заданиях / вопросах ближе к повседневной работе, к существующему проекту и процессам на нем. Так будет, на мой взгяд, и проще и полезнее для всех участников :)

Иван, спасибо за подробное изложение вашего подхода к собеседованиям. В общем и целом, я, естественно, солидарен. Ниже мы с Serhiy Serbin приблизительно то же самое обсуждали, только акценты по-другому расставлены были.

Если кандидат не знает, то собеседование заканчивается?

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

Меня в собеседованиях всегда смущали вопросы типа «назовите принципы ООП»

Я тоже не сторонник спрашивать чеканные формулировки.

то есть задачу, результатом которой будет код. И он либо работает, либо нет.

Солидарен. Набросок кода лучше разговоров, работающий код лучше наброска.

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

Алгоритмические задачи типа разворота строки, как предлагали вы, или возведение натурального числа в степень, как предлагал Сергей, с одной стороны не помешают, с другой — я пока не понимаю, с каким весом их оценивать.

Вопросы что и как важны, но важнейший, на мой взгляд, — почему.

Спасибо, верный акцент.

Я не вижу проблемы в том, что кандидат не знает каких-то паттернов или выберет «не тот» вариант.

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

Ну не верю я, что вы дадите ему сразу с нуля проектировать новую систему, или новый оригинальный модуль в имеющейся системе.

Систему и модуль — конечно, нет. А новые классы ему, скорее всего, создавать придётся. И я беспокоюсь, как они будут скомпонованы.

Давайте будем на собеседовании и в заданиях / вопросах ближе к повседневной работе, к существующему проекту и процессам на нем

Безусловно, стараюсь как могу.

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

Конкретно для вашего примера таки да не подходит тот вариант наследования, но зачем плодить разные класы для

DefaultHeader
и
DefaultFooter
, создали бы один интерфейс для хранения списка компонентов внутри отчета и хранили бы там вашы Header, Footer, Appendix, Body и все что душа пожелает.
Вопрос из разряда почему люки круглые или как измерить расстояние с помощью градусника. А в чем отличие между теплым и мягким?

Извините, вы, по-моему, разные вещи в одну кучу смешали.

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

По вашему, композиция и наследование — это тоже несравнимые категории? На мой взгляд обе эти техники позволяют переиспользовать код и, поэтому, могут противопоставляться

Это Вы лихо придумали: задать вопрос с двумя неправильными вариантами ответа. Как и в версии «В чём отличие между композицией и наследованием?» вопрос задан не верно.

Не буду спорить, вопрос задан некоректно, с подвохом и ложной дихотомией. Но это же статья, а не собеседование. Не разделяю возмущения :-) Если что, на собеседовании вопросы в таком стиле я не задаю.

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

Конечно. Вот я и пытаюсь, как умею, это и объяснить.

Конкретно для вашего примера таки да не подходит тот вариант наследования

Рад, что для вас это очевидно. Если бы я не сталкивался с противоположными примерами, вряд ли бы написал эту статью.

но зачем плодить разные класы для DefaultHeader и DefaultFooter, создали бы один интерфейс для хранения списка компонентов внутри отчета и хранили бы там вашы Header, Footer, Appendix, Body и все что душа пожелает.

Вроде как именно так и написано. Разные блоки наверное будут реализовывать один интерфейс. Тогда их действительно можно хранить в списке. Всё же, главный посыл был о неприменимости наследования. Как строить композицию — тоже важный вопрос, но выходит за рамки статьи.

Тёплое и мягкое — классический пример несравнимых категорий.
Ну можно например ответить, что на теплом приятно лежать когда холодно, а не мягком когда устал :).
На мой взгляд обе эти техники позволяют переиспользовать код и, поэтому, могут противопоставляться

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

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

Ну ОК, Вы меня успокоили, что таких как в конце вопросов не задаете на собеседованиях :).

По тех. части вроде тут все обсудили, я сначала написал комментарий, а потом заметил что теме пару дней и все продискусированно до меня :)
dou.ua/...heritance-in-java/#965593

Ось вам приклад на наслідування:
Згадайте класичний Composite в системах, де треба робити формочки, кнопочки.
Там зазвичай класи Node, Group, Button, Panel, ... наслідуються один від одного.
І тут не обійтися лише одними інтерфейсами. Та ж сама Node не може обійтися без стану, скажем, без X та Y.

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

І тут не обійтися лише одними інтерфейсами.
Ось тут не певен.

Хорошая статья. Для тех, кто начал понимать бредовость наследования, есть отличная замена java — go. Там есть только композиция плюс расширение интерфейсов. Также в go не нужно перечислять многочисленные интерфейсы, реализуемые классом.

А что же делать, если у меня у классов общие поля/методы ?

Не совсем понял вопроса

Есть два класса, у которых есть общие поля и методы. Если нет наследования, то методы и поля будут дублироваться?

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

Я и не вас спрашивал. Но если вы ответили — то как здесь работает принцип Liskov? То если есть базовый класс, то я могу присвоить его переменной любой объект наследника. Как это работает в Go?

В Go нет наследования, поэтому там нет ни базовых классов, ни наследников. Соответственно, в Go не нужны костыли в виде принципа подстановки Liskov.

Если несколько классов содержат пересекающийся набор методов, то их можно спрятать за интерфейсом с этими методами.

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

Так получилось, что эти вопросы я задал довольно большому количеству людей на собеседованиях. И, как мне кажется, есть определённое непонимание этих базовых концепций, вернее, расхождение между теорией и практикой. Данная статья призвана внести ясность и улучшить мир код.
Да перестаньте просто эти бесполезные вопросы задавать на собеседовании.
Да перестаньте просто эти бесполезные вопросы задавать на собеседовании.

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

Які там базові речі ООП. Тут не можуть типи даних в мові програмування перелічити...

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

Боюсь, мы сейчас уйдём от темы.

Я правильно понимаю, что мы солидарны в том, что знания нужно проверять через написание кода? Поэтому я и предложил вам привести свой пример.

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

Да, умение программирование нужно проверять, при помощи (барабанная дробь) программирования, то есть — написания кода.

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

— задавая вопросы по ООП, вы всегда будете скатываться в синтетические и абсурдные примеры

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

— вашему собеседнику, надо будет хаотично догадываться, чего же вы от него на самом деле ожидаете

Кажется, вы домысливаете за собеседника и за меня. В компоновке кода нет единственно правильных решений. Мне важно посмотреть ход рассуждений кандидата.

вот даю 99%, что вы сейчас лукавите. задавая вопрос по ООП, на собеседовании, вы ожидаете ответ совпадающий с вашим текущим желанием. какие там рассуждения? пронаследуем квадрат от прямоугольника, или квадрат это прямоугольник с двумя одинаковыми сторонами? а сделаем конструктор или static метод? а может вообще квадрат и прямоугольник это n-угольник? или вообще это два треугольника, или 4ре точки, или вектор, или определенный интеграл от const функции? мне кажется такие беседы подходят для моего сына, вот ему будет интересно о таком поговорить, но никак не человеку претендующему на позицию программиста.

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

Сергей, извините, кажется, вы меня не слышите и продолжаете додумывать.

Ложную дихотомию на собеседовании я не использую, не та ситуация. Задачу про квадрат не задаю. Здесь я её привёл в несколько провокативном виде для того, чтобы напомнить, что отношение is-a в обычной жизни не всегда означает наследование в ООП.

У кандидата я спрашиваю то, что реально используется на проекте — знание фреймворков и компоновку кода. Задача про отчёты — об этой самой компоновке.

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

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

я же предлагаю задавать вопросы — которые независимы от болтологии и имеют некую четкую систему оценки. ну хотя бы более менее четкую. плюс, я категорически советую не задавать вопросы базирующиеся на вашем ТЕКУЩЕМ опыте — проекте. как правило, это самое недальновидное, что можно сделать. потому что — обычно этот опыт не очень позитивный и вы под прессом текущих проблем, можете просто не расслышать рациональное зерно в словах вашего собеседника.

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

Вас не затруднит процитировать часть топ-комментария, где утверждается, что задача про отчёты синтетическая? В любом случае, вот вы говорите, что она надуманная, а мне почти в каждом проекте их приходилось делать. И другие комментаторы сталкивались. Специфика работы. Я бы даже так сказал, что это за энтерпрайз проект без отчётов? :-)

я прихожу к вам на собеседование — я вообще понятие не имею, чего вы там делаете.

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

Мне кажется, вы себе нарисовали определённую картину и яростно с ней спорите. Вы лучше спросите, и я вам отвечу — перед собеседованием обсуждается проект, технологии и задачи. Если, допустим, кандидат отличный С-embedded-developer — честь ему и хвала, но наш проект о другом, и отнимать друг у друга время нет смысла.

да хватит заливать

В таком тоне и выражениях можете с домашними разговаривать, если они вам позволяют, а со мной не надо.

я же предлагаю задавать вопросы — которые независимы от болтологии и имеют некую четкую систему оценки.

Я вас услышал. Будет более ценно, если вы привёдете примеры таких вопросов. Ок, возведение натурального числа в степень, что ещё?

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

плюс, я категорически советую не задавать вопросы базирующиеся на вашем ТЕКУЩЕМ опыте — проекте

Почему? Аргументируйте, пожалуйста.

Я рассуждаю так. Вот мы работаем, допустим, с таким-то фреймворком, столкнулись с такими-то проблемами, решаем их так-то. Почему не спросить об этом кандидата? Если решает их так же — хорошо, человек в теме. Если по-другому — тоже хорошо, по крайней мере, в курсе проблемы. Если решение лучше — вообще отлично! Ну, а если не сталкивался — это не показатель, говорим тогда о других аспектах.

можете просто не расслышать рациональное зерно в словах вашего собеседника.

Опять вы за меня додумали. Почему?
Расскажите, как вы это проводите собеседование, какие вопросы задаёте, может и я изменю подход.

Я не уверен, что таким образом можно проверить понимание дизайна, шаблонов проектирования и т.д

Это уже мягче. Я тоже не уверен, но пока придумал вот так. Какой вариант вы бы предложили?

вам какой результат нужен — рабочий код или шаблоны?

А почему вы противопоставляете? На мой взгляд, нужно и то и другое.

я ещё только учусь, не судите строго
Композиция vs Наследование в Java
Luxoft — Java Tech Lead
Нам достаточно интерфейса Фигура и класса Прямоугольник

Дать задачу с двумя вариантами ответа. Оба из который неправильные. Аплодисменты.

Извините, Ринат. Провокативно, не отрицаю. Но разве на работе и в жизни не бывает таких ситуаций, когда нужно выходить за рамки поставленных условий?

ru.wikipedia.org/wiki/Ложная_дилемма

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

Любой программист с ненулевым опытом работы с ООП прочитав её скажет, что оба ответа бессмысленны

Да, мне искренне хотелось, чтобы так и было.

А правильна відповідь яка?

Я запитував Ріната Веліахмедова

а как же принцип never repeat yourself? ваш код выглядит как сплошной копипаст, однако, он дает долю flexibility, но количество кода растет пропорционально
кстати, репорт не должен сам себя генерировать/печатать — в этом изначальная ошибка вашего подхода

а как же принцип never repeat yourself? ваш код выглядит как сплошной копипаст, однако, он дает долю flexibility, но количество кода растет пропорционально

DRY — это хороший принцип. Но в приведённом примере DRY с помощью наследования не справился с изменившимися требованиями. Поэтому предлагается перейти к композиции и, если необходимо, снова вспомнить о DRY, но уже другими способами.

кстати, репорт не должен сам себя генерировать/печатать — в этом изначальная ошибка вашего подхода

С одной стороны, это просто пример вынесения общего кода в базовый класс, может не самый удачный, но я к нему привык. В комментариях на это указывали, что должен быть ReportPrinter.
С другой — как вам такое мнение www.yegor256.com/.../objects-end-with-er.html ?

DRY не такой уж хороший принцип. Он в каком-то смыслае «архивирует код» избавляя его от дубликатов, но создавая при этом лишние сущности и усиливая связность. Вносить изменения в такой код гораздо сложнее.

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

«Преждевременная оптимизация — корень всех зол» — это правило было актуально раньше, и как показывает практика, не менее актуально и сейчас.

одно дело — связность
другое дело, когда по солюшену разбросано 5-6 кусков одинакового кода, в который надо добавить функционал. тоже ведь из жизни ситуация

другое дело, когда по солюшену разбросано 5-6 кусков одинакового кода, в который надо добавить функционал.
Ничего страшного. Сегодня надо добавить одинаковый, а завтра — разный. И вот это «заврта» случается гораздо чаще, чем кажется.

Каждый класс или метод должен быть отдельным логическим элементом. ИМХО, худшая причина для создания метода/класса — это устранение дублирующего кода.

Проблема, с которой борится DRY это на самом деле не дублирующий код, а копипаст. Когда пишется одинаковый код — это хорошо, а когда бездумно копируеся — нет.

Спасибо за обсуждение задачи, в конце статьи добавлен авторский ответ

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

А как же 4 линии через 9 точек, выход за рамки и всё такое? Не всегда нужно выбирать из предложенных вариантов.

А как же 4 линии через 9 точек, выход за рамки и всё такое? Не всегда нужно выбирать из предложенных вариантов.
почему в шапке почему без шапки.
Как-то не похоже что вы девочка, которой надо угадывать чего она хочет.

Задача про квадрат — не капризничание, а проверка усвоения материала в стиле:

— Здесь нельзя яблоки есть, они отравленные. Поняли?
— Да!
— Какое съедите — чёрное или синее?
— Синее!

Мужик, ты в самом деле серьезно?

Могу я спросить, каков бы был ваш ответ на задачу про квадрат?

Пожалуйста, у меня был бы абстрактный класс Фигура с абстрактными методами для подсчета площади, периметра, чем-то еще. И все мои квадраты, кружки прямоугольники просто бы реализовывали этот базовый класс. Как-то не круто кастить что квадрат к прямоугольнику, что прямоугольник к квадрату. Главное чтобы фигуры были readonly.

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

Пожалуйста, у меня был бы абстрактный класс Фигура с абстрактными методами для подсчета площади, периметра, чем-то еще. И все мои квадраты, кружки прямоугольники просто бы реализовывали этот базовый класс. Как-то не круто кастить что квадрат к прямоугольнику, что прямоугольник к квадрату. Главное чтобы фигуры были readonly.

Правильный ответ, конечно. Мой ответ — в конце статьи.

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

Тут, извините, не понял. Что значит «обмазывать людей»? Или это опечатка «обмаНывать»?

Читали в Википедии статью, где предлагается одиннадцать вариантов ответа на эту задачу? Там, правда, не прямоугольник и квадрат, но окружность и эллипс, однако это не меняет сути:
en.wikipedia.org/...ki/Circle-ellipse_problem

Спасибо за ссылку. Не подозревал, что существует так много вариантов ответов.

Такое чувство, что люксофт платит бонусы за статьи на доу. Просто не знаю, что еще может побудить накатывать столь унылые опусы.

Может, раз такое чувство, подскажите к кому конкретно в Люксофте за деньгами обратиться?

Побудили собеседования, я же написал об этом.

Отличный бред... Посмотри любую библиотеку компонентов/контролов и убейсибяапстену © :)

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

Я не утверждаю, что наследование категорически неприменимо. Тот же принцип Лисков не отрицает наследование, а лишь позволяет проверить, не ошибаемся ли мы сходу.

Вот авторитетное мнение, эксперта что наследование — г#вно. И я на 146% согласен. Когда в примерах идет речь о кружочках, фигурках и прочих, оторванных от реальности сущностей, это хорошая концепция.
youtu.be/G6LJkWwZGuc?t=587
смотреть с 9:47

Также хотелось бы обратить внимание, что код стал объекто-ориентированным header.print(), а не процедурным printHeader(), как раньше.
Эта фраза — квинтэссенция всей статьи: ООП — это когда выражения с точечкой. :)
ReportSection header = headers.create(WITHOUT_DATE);
Главное, что композиция даёт нам полную гибкость.
Ну круто, супер гибкость — добавим вытягивание зависимости прямо в методе вывода. Своим опусом вы породили не меньше инвалидов от программирование чем университетские сказки про три кита.
Вопрос. Как, учитывая эти факты, построить иерархию классов: унаследовать Прямоугольник от Квадрата или Квадрат от Прямоугольника?
Просто экскурс в историю:
Задача про прямоугольник и квадрат для демонстрации ООП появилась как часть задачи про графический редактор, а не про геометрические фигуры. И демонстрировала в основном трейдоф между производительность и переиспользованием кода.
.
P.S. Посыл про то что наследованием классов надо пользоваться очень аккуратно правильный, но это не отменяет всех тех глупостей которые есть в статье.

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

Как вы считатете, будет ли полезно читателям и мне, если вы не только укажете на глупости, но и исправите их?
Уверен что вам нет. Это не оскорбление, а банальная правда, ибо чтобы сдвинуть парадигму мышления надо кучу времени, знаю в том числе и по себе.
Читателям возможно, но уже хорошо что им сказали что статье не нужно верить, авось задумаются.
Перечислять все, банально не хочу тратить время.
void print() {
printHeader();
printBody();
printFooter();
}
Новость — это вполне нормальный код, наследование тут уместно, до того момента пока оно решает конкретную проблему. На этом примере, если количество разных репортов статично, не требует изменения в рантайме, а допускает перекомпиляцию приложения.
Если вдруг нам нужна таки динамичность, то
HeaderFactory headers;
Body body;
DefaultFooter footer;
Может иметь смысл заменить на
List<ReportBlock> blocks;
И самое важное:
Принимать то или иное решение надо на основании анализа требований, а не построения догадок. Все догадки должны быть переведены в раздел «требования», путем обсуждения с людьми от бизнеса.
1) если общая функциональность выносится в родительский класс;
2) появляются слова Base и abstract;
3) в наследниках переопределяются или используются методы родительского класса
... скорее всего, что-то пошло не так и проблемы не за горами.
Пункты 1 и 3 не признак проблем, если они приводят к проблемам, то исходная проблема таки не в клозетах, а в головах.
Пункт 2 — это скорее всего проблема, но она не имеет отношения к наследованию, а в принципе в подходе к дизайну.
На этому думаю пока хватит.

Спасибо за ответ.

Да, действительно, начальную задачу можно решить с помощью наследования и Template Pattern-a. Да, вначале утверждается, что требования не могут меняться, а потом они меняются. Имхо, лучший возможный ответ кандидата — условие изменилось, переделываем на композицию. Не буду отрицать, что задачи и про отчёты и про квадрат сформулированы несколько провокационно.

Что в моих пунктах, что в вашем ответе, используется выражение «скорее всего», что смягчает категоричность высказывания.

Пункт 3 «в наследниках переопределяются» противоречит принципу Лисков, который, конечно, тоже не догма, а сигнал.

Я неоднократно наблюдал в проектах, когда создавалась иерархия классов, а потом, при изменении требований, наследование становилось неподходящим приёмом, а переделка на композицию, на реальном проекте, а не на собеседовании, при всей поддержке IDE — это всё таки переделка, с ценой в виде времени-денег-тестирования.

Безусловно, и этот аргумент не означает, что композицию нужно превентивно использовать всегда, а лишь в 97% случаев :-)

Пункт 3 «в наследниках переопределяются» противоречит принципу Лисков
Нет, переопределение не противоречит. Оно может противоречить, а может и нет.
И почему-то вы умалчиваете, что композиция очень легко может привести к нарушению ISP, а само поддержание ISP так же стоит трудозатрат.
Я неоднократно наблюдал в проектах
Практически любая техника в кривых руках приводит к плачевным результатам.
когда создавалась иерархия классов, а потом, при изменении требований, наследование становилось неподходящим приёмом, а переделка на композицию, на реальном проекте, а не на собеседовании, при всей поддержке IDE — это всё таки переделка, с ценой в виде времени-денег-тестирования.
Если уж мы пришли к аргументам «Я неоднократно наблюдал в проектах», то я наблюдал что передать наследование на композицию, при необходимости, стоит несравнимо мало к реализации/доставки самой фичи.
А вот переделка Report8 с фабрикой и еще чем-то будет стоять таки денег, ибо потребует изменения, как минимум, 2-3 интерфейсов и инициализации многих классов Report*
Безусловно, и этот аргумент не означает
Предыдущий опыт — это не аргумент, ибо из существования не следует всеобщность. Аргумент может быть построен на анализе предыдущего опыта, но этого я не увидел в статье.

Согласен по всем пунктам. И, вообще, весьма признателен за филигранную аргументацию.

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

А нам обязательно хардкодить каждую разновидность отчета в виде отдельного класса?

Нет, конечно, в задаче нет никаких обязательных условий, даже те, которые таковыми заявлены :-)

Единственно, хочу заметить, что задача про компоновку кода. Пример с отчётами не совсем удачный, потому что провоцирует на всякие темплейты. Пусть будут не отчёты, а дома.

Ваш вариант решения?

Дело в том, что в моем случае речь как раз идет об xml-темплейтах отчетов и классе DocumentBuilder. Но и в случае с домами я бы не использовал сходу наследование.
Это как подтверждение того, что мы — джуны — тоже осторожно и с пониманием относимся к наследованию и не тычем его где попало)))

Попробую ответить ещё раз.

Эта фраза — квинтэссенция всей статьи: ООП — это когда выражения с точечкой. :)

Я не утверждал, что ООП — это когда выражения с точечкой. Вы приписали мне утверждение и высмеяли его. Кажется, это приём Pugna.

Ну круто, супер гибкость — добавим вытягивание зависимости прямо в методе вывода.

Целью статьи было показать ограничения применения наследования на примере Template Pattern. Как организовать композицию — это следующий вопрос.

Своим опусом вы породили не меньше инвалидов от программирование чем университетские сказки про три кита.

Сильно сомневаюсь, что вы можете подтвердить это утверждение ссылкой на статистику инвалидов. Скорее, это всё же ваше субъективное мнение.

Просто экскурс в историю:Задача про прямоугольник и квадрат для демонстрации ООП появилась как часть задачи про графический редактор, а не про геометрические фигуры.

Познавательно. А я по её мотивам хотел напомнить, что отношение is-a реальных объектов не означает наследования.

P.S. Посыл про то что наследованием классов надо пользоваться очень аккуратно правильный

Это единственный посыл статьи. Рад, что тут мы сходимся во мнениях и, как я понимаю, дискутируем о том, на каком примере его можно показать нагляднее?

И еще, по поводу вынесения какого — то основного функционала в базовый класс. Такой вопрос, и я буду благодарен если вы меня поправите \ дополните.

Есть цсв файлы, содержащие инфу про товар в БД — сео теги, описание, тайтл, цену, и т д . Есть ряд файлов, отличающийся структурой, и задачами — один содержит всю инфу, для большинства товаров и ведется уже лет 7 и весит гигабайт 2, второй заведен для прописывания описания в товарах, что недавно появились, третий имеет лишь часть полей первого документа и имеет доп. фильтрацию инфы.

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

Антон, не воспринимайте, пожалуйста как личный выпад, но вся эта статья призвана бороться с позицией

«Я, конечно, знаю, что джунам запрещают наследование. И на ДОУ вот недавно опять писали. Но, во-первых, я далеко не джун, а, во-вторых, у этих классов есть общий код. Надо вынести его в базовый класс!!!»

Не надо. Лучше сделать отдельный класс, который может обрабатывать СSV — читать, фильтровать и т.д. В итоге возвращать данные. И использовать (а не наследовать), создавая экземпляр этого класса в других. Первый выигрыш от такого подхода — его можно замокать для тестов.

«Я, конечно, знаю, что джунам запрещают наследование. И на ДОУ вот недавно опять писали. Но, во-первых, я далеко не джун, а, во-вторых, у этих классов есть общий код. Надо вынести его в базовый класс!!!»

Супер аргумент. Он настолько сильный ( в плане отбить желание строить дальнейший диалог), что на нем ответ можно было бы и закончить.

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

Супер аргумент. Он настолько сильный ( в плане отбить желание строить дальнейший диалог), что на нем ответ можно было бы и закончить.

Извините, Антон. Во-первых, это некоректный аргумент, во-вторых, как показали в одной из веток, слабый. Если позволите, я хотел бы взять эти слова назад.

По поводу предложенного решения проблемы

Поскольку в джаве нет множественного наследования, от одного класса можно как наследоваться, так и строить декоратор. Более того, в случае наследования код будет короче символов будет меньше. Но означает ли это, что нужно применять наследование?!

Я за вариант

Опять же, можно создать базовый класс, и определить в нем конструктор, который принимал бы как аргумент, обьект — читалку цсв,

только без слова «базовый».

Посмотрите, пожалуйста, статью www.yegor256.com/...omposable-decorators.html

Спасибо за статью, на фоне соседних домашних посиделок на кухне — как глоток свежего воздуха. По поводу вашего первого примера, согласно которому так делать не нужно, и почему большинство кандидатов решают эту задачу именно таким способом — потому что перед ними поставлена конкретная задача, которую нужно решить. И в данном контексте, предложенный вариант полностью решает поставленную задачу. Использование более гибкого подхода, а именно композиции, имхо, будет предложено, когда
а). Вы явно укажете в задаче, что архитектура этого модуля будет дополняться \ изменяться весьма извилистыми путями, так как заказчик str_replace(’м’, ’ч’, ’чудак’) - хотя это сплошь и рядом, и при планировании это надо учесть.
б). Тот, кому задали задачу при собеседовании, сам, исходя из своего опыта, схватится, и предложит композицию наследованию, хотя это должен быть человек либо который постоянно работал в коллективе, у которого SOLID и чистота \ крепкость архитектуры на первом месте, либо же это серьезный сеньор, с действительно солидным опытом, который не сидел на легаси проектах.

А так — если в рамках собеса и тем более в рамках работы, если задача поставлена

Каждый отчёт состоит из трёх частей — заголовка (header), собственно тела отчёта (body) и колонтитула (footer). Формируют их некие методы. Формирование header и footer для всех отчётов одинаково и меняться не будет.

то поверьте, в рамках поставленных дедлайнов, 70% сделают как в первом варианте, а второй сделают, отрефакторив первый, когда придет соотв. задача, это требующая, хотя это и нарушает принцип расширения, а не изменения :)

ПЫ СЫ :) Пишите Еще, подобные статьи весьма интересны и полезны!

Спасибо, такой комментарий мотивирует писать ещё.

Касательно собеседования:

а). Заказчик платит деньги и может менять требования. Это, считаю, нормально. Явно делать акцент — дать подсказку.
б). Вот кандидатов, которые «спохватятся и предложат композицию» я и стараюсь брать. И, хотелось чтобы разницу между наследованием и композицией понимали не только серьёзные сеньоры, а, извините, все.

ПЫ СЫ :) Пишите Еще, подобные статьи весьма интересны и полезны!
Интересны — может быть, явно не хуже чем очередной высер про ХРов или ЗП.
Но вот явно не полезны:
— люди, которые имеют представление про ООП, скорее посмеются или разозлятся очередному бреду;
— а вот людей без опыта и знаний, такая статья только подтолкнет к «инвалидности» (не хуже большинства универских программ по ООП)
унаследовать Прямоугольник от Квадрата или Квадрат от Прямоугольника
Это задача вообще не на наследование, и не на ООП. Это пример, демонстрирующий, что близость ООП к реальному миру — условна, и оно не призвано зеркалить в коде реальный мир, а призвано описывать взаимосвязи между абстракциями конкретной программной системы, для удобства и эффективности этой самой системы. Текстовка к этой задаче идет типа: вот смотрите, в реальной жизни квадрат «is a» прямоугольник, а в программе такая реализация будет избыточной.

Дополнительно не могу не отметить, что показывать различие композиции и наследования примером про машинки-пулеметики — это днище какое-то. Вы читали вообще, как сама Лисков демонстрировала Liskov Substitution Principle, какими словами он сформулирован? Это о программировании, а не о машинках. Кого от чего мы там отнаследуем и отнаследуем ли, зависит только от того, какие именно аспекты пулемета и машины нам нужны в нашей программе. Может никакие, а только строка с названием модели. Вот потом дети начитаются таких примеров и бред на собеседованиях несут, и написать что-то простейшее неспособны.

Конечно там не нужно наследование! Элементарный подвох на усвоение материала.

Я, собственно, два раза и пытался сказать, что іs-a ни разу не означает наследование.

С ним, на мой взгляд, не только у детей проблема. Повторяюсь, я затрудняюсь привести пример правильного. Может вы?

«is a» означает наследование, вы пытались сказать неправильно. LSP как раз об этом, о том, что «is a» должно строго соблюдаться. Вместо типа T всегда можно подставить его подтип S, потому что S «is a» T.

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

Спасибо вам за коментарии, учусь выражаться яснее.

Я пытался выразить мысль, что даже если кажется, что отношение между реальными объектами is-a (БМП — это машина, квадрат — это прямоугольник) — это никоим образом не означает, что должна быть соответствующая иерархия классов.

Вот я вам ответила выше, что в этом примере — инстанциация. БМП есть экземпляром машины, квадрат есть экземпляром прямоугольника. Наследование же и композиция — это отношения между типами, между классами. Это нельзя путать :(

Давайте пройдём ещё раз.

Квадрат — это бесспорно инстанциация, задача с детским подвохом. Но про БМП я в условии указал, что это такой же автомобиль, но с добавленным пулемётом и методом стрелять(). Принцип Лисков вроде бы не нарушается, БМП всегда можно использовать как машину. И всё равно, я бы рекомендовал не использовать наследование.

Ага, вы правы в таком смысле, БМП как тип делает все то, что делает машина, и тогда по отношению к машине тут наследование, а по отношению к пулемету — композиция. Если мы смотрим очень абстрактно на типы как таковые. А вообще все зависит от того, что нам в программе надо делать с машинами и пулеметами.

В некотором роде, пожалуй, вы и правы, люди, бывают, путают «is a» в смысле соотношения между классами, и «is a» в смысле инстанциации. В реальном мире мы не будем выделять Square в отдельный класс, разве что у нас игра для малышей, и квадраты у нас пляшут лезгинку, а все остальные прямоугольники танец маленьких лебедей, тогда будем. Если у нас просто какие-то геометрические вычисления, у нас будет класс Rectangle, и у некоторых экземпляров будет width=height, то есть эти экземпляры будут квадратами с точки зрения реальной жизни, но системе это будет пофигу, ничего специального с этим фактом она делать не будет.

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

Абсолютно верно, конечно.

Увы не тянет на хорошую статью.

— Имя класса крайне неудачное. Это скорее ReportPrinter.
— Композиция превыше наследование. Я лично считаю что джунам вообще надо запрещать наследовать, пока не научатся правильно составлять объекты. Поэтому с утверждением что

С композицией всё просто.
крайне не согласен, это то место которое большинство девелоперов понимают очень плохо, а зря. Вообще в конце статьи правильные выводы сделаны, но как-то сумбурно и недостаточно акцентов на правильных вещах.
— 
если общая функциональность выносится в родительский класс
общая функциональность в 95 случаев должа выноситься в отдельный тип, т.к. это скорее всего нарушение SOLID в том или ином виде. Опять же — наследование хуже композиции почти во всех случаях, за исключением «шаблонных».
— Насчет квадратов-прямоугольников — оба типа будут implements Figure. Никакого наследования. Тот факт, что у них есть занимательные геометрические сходства не означает что квадрат может замещаться только прямоугольником, скорее любая геометрическая фигура может замещаться другой. Когда думаете о наследование, думайте в первую очередь о полиморфизме. Худшее что может сделать разработичк — ввести иерархию классов только ради устранение дублирования кода.
— На кой черт в геометрических фигурах, которые типичные ValueObject, сеттеры?

Вообщем, правильные выводы в статье не видно за слегка сумбурным текстом. У меня в целом те же проблемы с изложением мыслей, поэтому и пишу Вам.

Спасибо за коментарий. Это моя первая статья, стараюсь как могу.

Я тоже считаю, что джунам нужно запретить наследование и именно это излагаю как умею.
Про квадрат и сеттеры — естественно. И достаточно одного типа. Уже два коментария с правильным ответом, боюсь интрига раскрыта.

Пример правильного наследования сможете подсказать?

Немного не понял ваш вопрос, о каком примере идет речь?

Худшее что может сделать разработчик — ввести иерархию классов только ради устранение дублирования кода.
. А для чего правильно вводить иерархию объектов? Если можно поподробнее. Спасибо.

Для полиморфизма. Вообще правильные применения наследования ищите в Design Patterns by GoF, кроме собственно словаря который могут использовать между собой разработчики, это наверное основное что дает эта недооценненно-переоценненная книга.

Напомните, пожалуйста, примеры правильного наследования из книги Design Patterns: Elements of Reusable Object-Oriented Software, если вы о ней. Я помню, что наследование — основной механизм повторного использования кода в шаблонах Factory Method и Template Method. Вы об этих примерах применения наследования говорили?

Я лично считаю что джунам вообще надо запрещать наследовать
А когда разрешать наследование? Что будет признаком, что разработчику можно разрешить использовать наследование? На какие вопросы и что должны ответить разработчики, чтобы им разрешили использовать наследование?
На кой черт в геометрических фигурах, которые типичные ValueObject, сеттеры?
Этот пример фигурировал еще в статье про The Liskov Substitution Principle за автором Robert C. Martin, который и описал SOLID принципы в начале 2000-х.

Также в книге Domain-Driven Design: Tackling Complexity in the Heart of Software автор Eric Evans пишет, что часто удобно иметь immutable Value Objects, но я нигде не нашел утверждения, что неизменяемость — обязательное условия для Value Object. Статья про Value Object на Wikipedia говорит тоже самое:

It is also useful for value objects to be immutable, as client code cannot put the value object in an invalid state or introduce buggy behaviour after instantiation.
Но не вижу причин, почему в отдельных случаях объект-значение не может быть изменяемым.

Я не знаю, когда разработчику можно разрешать наследование. Описал проблемы от плохого наследования, а пример хорошего не смог привести.

Себе я, например, позволяю иметь BaseIntegrationTest с шапкой аннотаций и кучу его наследников. Но это, так сказать, техническое использование, а не настоящее :-)

Каждый отчёт состоит из трёх частей — заголовка (header)
Далі можна не читати. Скільки можна орієнтуватися на візуальщину?
Звіт візуально складається із чого завгодно. Правильно розділити цю функціональність на дві. Перша частина отримує фільтр та повертає дані в xml або json форматі, а інша вже перетворює ці дані на якийсь контент (темплейтування). Тому базовий абстрактний клас звіта буде складатися лише із одного метода (який можна й в конструктор засунути) — повернути дані по фільтру. Все, ніяких футерів, хедерів та іншого.
Декомпозицію треба вчитися робити до спадкування. Це основа сталої системи.

Такий у нас візуальний ентерпрайз :-) Я вже звик до цього прикладу на співбесідах, але він не про темплейти. Хай буде замість звіта — будинок, header-a — дах, footer-a — фундамент. Стаття не зміниться.

Декомпозицію треба вчитися робити до спадкування.

От я і намагаюсь донести це.

Як і мій приклад. Підвезіть матеріали, далі вже розбереться хтось інший що з ними робити. Процес не міняється: виборка й обробка даних мусять бути окремо.

Згода. Я от не зміг приклад вдалого наслідування навести. Може ви?

Найкращими випадками вдалого спадкування були фільтрація готових структур даних та модифікація. Виборка користувачів системи та вивід тільки активних. Базовий клас вибирає користувачів. Спадкоємець виконує метод батьківського класу, фільтрує результат, та повертає його. Або модифікує в іншу структуру, наприклад, із масива робить XML

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

Але оці фільтрування і перетворення можуть використовуватись ще десь. Тоді їх краще оформити не в методах спадкоємця, а в лямбдах. І тоді, вибачте, у нас знову ж таки композиція.

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

Я не впевнений, чи існує взагалі задача, яку можна вирішити тільки за допомогою ООП...

DOM. Не вирішиш ані функціонально, ані процедурно

только это не задача, а одно из решений представления документов.

Якщо це одно з рішень, то покажіть інші. Або розкажіть про них

Расскажите, как выбранный Вами подход разрулит ситуацию, когда в документе (вдруг внезапно) появились другие составные части, например логотип, печать, поля с отметками и прочее. Или, далее, появляются документы без footer или без header или без и того и того. А потом появляются документы с любым из 100+ составных частей и эти составные части могут бесконечно добавляться. И всё это нужно продумать чтобы ни в коем случае не противоречить принципам SOLID.

Так я вроде об этом и написал. Композиция. Новый отчёт (или модификация старого) никак не связан с предыдущими. Если существующие части (header, footer и т.д.) подходят — инжектим и используем их, если нет — дописываем нужные. Вы статью до конца прочитали?

Дочитал. Но всё равно вижу дублирование кода в

@Override
void print() {
header.print();
body.print();
footer.print();
}
Если у вас 100 разных типов документов а метод print может отличаться на одну строку. В итоге у нас 100 классов с полями и методами похожими один в один но отличающимися на пару строк. DRY принцип нарушен. Это если придираться.

1) Статья в первую очередь о том, что наследование — это сильная связь и вредит гибкости, поэтому пользоваться им не надо (зачёркнуто) в исключительных случаях, осознавая последствия.
2) Похожая композиция — это не нарушение принципа DRY.
3) Если дела пойдут так, что отчёты будут практически сходны, используем шаблон фабрика (с параметрами), а если параметров становится много/неудобно, возвращаемся к п.2

Может и не явное нарушение DRY но дублирование кода огромно. Плюс один — что бы там ни было ты знаешь что делать и как делать (не думая как бы обойти ту или иную проблему), каким бы монстроидальным не получился твой проект в итоге. Но а вообще, на собеседовании нужно чётко ставить задачу. Например, указав, что проект большой и может расти — требования как водится могут меняться. Если кандидат и на это пишет вариант #1, то делать уже справедливые выводы.

На всякий случай ещё раз — у каждого подхода есть плюсы и минусы. Имхо, недостатки наследования сильно перевешивают. Именно тот факт, что многие принцип DRY воспринимают «так, надо в базовый класс вынести» и побудил меня написать эту статью.

Такой у меня стиль собеседования — чётко ставлю задачу, а потом понемногу меняю условия. Считаю способность думать важнее знания определений. Но это уже другая тема.

Я прошу прощения, что снова вмешиваюсь, но «сильная связь и вредит гибкости» — это как-то абсурдно. Если все связи будут слабыми, система не станет супер-гибкой, она рассыпется. Слабые связи нужны для гибкости — для обеспечения будущих изменений, сильные связи — для прочности, для того, чтобы при будущих изменениях, не поломалось ничто важное. И именно с этой мысли, как с трамплина, мы и видим, что все вещи, которые в проекте навек, которые описывают фундаментальные вещи в нем, должны быть прочными, а не гибкими.

система не станет супер-гибкой, она рассыпется
Нічого нікуди не розсипається. Вона стає іншою, але цілосність зберігається навіть краще, ніж в сильнопов’язаних архітектурах.
сильные связи — для прочности, для того, чтобы при будущих изменениях, не поломалось ничто важное.
Це працює і для слабкопов’язаних архітектур. Там просто інша декомпозиція, тому воно здається крихким.

Нет-нет, вы комментируйте, пожалуйста.

Но тут, извините, снова пытаемся словами из реального мира описать программирование. Вы хотите сказать, что tight coupling (например, наследование) как-то упрочняет дизайн приложения?!

Думаю, такой подход как раз разрулит нормально. Сделать composite с одим абстрактным методом — напечать себя. Ну а его наследники пусть уже печатают что хотят, в том числе и другие вложенные элементы.

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

Заменил header.print(); на footer.print();

странно. автор один, а заменяет другой. автор зааутсорсил уже и это?

У автора нет возможности редактировать статью.

«Так было в тикете! »

Как, учитывая эти факты, построить иерархию классов: унаследовать Прямоугольник от Квадрата или Квадрат от Прямоугольника?

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

Но если подумать, квадрат должен наследоваться от прямоугольника и задать b=a при инициализации. Ни говоря уже о том, что наследовать прямоугольник от квадрата просто логически некорректно.

Если квадрат наследуется от прямоугольника, как быть с сеттером второй стороны setB в классе Квадрат?

А если setB будет вызван после setA с другим значением?

ожидаемое поведение не меняется — мы задаем размер стороны прямоугольника, в случае квадрата все стороны равны, будь то А или Б, соответственно оба метода будут задавать размер грани.

1) Зачем сетить вторую сторону в квадрате?
2) Почему нельзщя просто сетить А в квадрате, когда сетят Б?
3) Зачем вообще там сеттеры, просто передавайте параметры в конструктор.

1) Незачем. И хорошо бы это как-то подчеркнуть, а унаследованый сеттер setB вносит неразбериху.
2) Ответил выше. Можно вызвать setA и setB в произвольном порядке с разными значениями.
3) Тепло :-)

1) Незачем. И хорошо бы это как-то подчеркнуть, а унаследованый сеттер setB вносит неразбериху.
Вы не будете использовать его, если будете работать с квадратом как с квадратом, зато получите возможность работать с квадратом как с прямоугольником.
Можно вызвать setA и setB в произвольном порядке с разными значениями.
Так зачем вы создали квадрат, если вам нужен прямоугольник. Это бессмысленно.
3) Тепло :-)
Лучше приведите пример хоть одной реальной библиотеки, где есть классы квадрата и прямоугольника, и где один унаследован от другого.

Я о том и толкую, что дочерний класс по определению содержит публичные методы родителя. И если метод есть, но пользоваться им «не надо» — это плохой дизайн.

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

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

Квадрат — это действительно прямоугольник. Отношение is-a? Значит наследник?

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

Квадрат — прямоугольник. Прямоугольник — не всегда квадрат. Но даже если отбросить эту деталь, мы не может наследовать прямоугольник от квадрата так как банально нарушается принцип подстановки.

правильный ответ — добавить класс треугольника, тогда становится очевидно, что класс должен сам устанавливать, сколько сторон можно задавать. Благо, треугольник может быть равнобедренным или равносторонним.

А разве само наличие сеттеров (что делает объект mutable!!) не есть признак плохого дизайна (по Josh Bloch)?

Это добрый совет от разработчика Java)) Если интересно можете почитать стр. 79 Effective Java

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

Набагато цікавіше зрозуміти алгебраїчні властивості об’єктів, тобто як бібліотека пропонує їх комбінувати.

На мою думку, зрозуміти якість бібліотеки можна тільки в контексті класу задач.

В прикладі з прямокутником і квадратом напрошується якась абстрактніша сутність, яка має властивості, такі як плоша чи периметр (зразу приходять в голову фрактали ;)...

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

и тут в игру вступает круг без сторон. На счет абстракции можно прикинуть вот тут ru.wikipedia.org/wiki/Четырёхугольник как же это все наследовать. Когда к примеру квадрат является как частным случаем прямоугольника так и ромба.

Фигура — имеет проперти area, perimeter... Далее определяем подклассы triangle, circle, square.., в которых будут разные наборы сеттеров и свои собственные проперти (стороны, радиусы...). А конкретная имплементация (квадрат, круг..) идет от соответствующего сабкласса. (так описано в MATLAB Programming for Engineers, не знаю, насколько это верно)

А почему там должен быть метод setB()? Мне кажется что для стороны квадрата/прямоугольника значение должно быть final.

Подписаться на комментарии