Мое текущее понимание реализации Наследования состоит в том, что следует расширять класс только при наличии отношения IS-A . Если родительский класс может дополнительно иметь более конкретные дочерние типы с различной функциональностью, но будет совместно использовать общие элементы, абстрагированные в родительском.
Я подвергаю сомнению это понимание из-за того, что мой профессор Java рекомендует нам делать. Он рекомендовал для JSwing
приложения, которое мы строим в классе
Необходимо расширить все JSwing
классы ( JFrame
, JButton
, JTextBox
, и т.д.) в отдельные пользовательские классы и указать GUI соответствующей настройки в них (например , размер компонента, метки компонента, и т.д.)
Пока все хорошо, но он далее советует, что у каждого JButton должен быть свой собственный расширенный класс, хотя единственным отличительным фактором является его метка.
Например, если в графическом интерфейсе есть две кнопки Okay и Cancel . Он рекомендует расширить их, как показано ниже:
class OkayButton extends JButton{
MainUI mui;
public OkayButton(MainUI mui) {
setSize(80,60);
setText("Okay");
this.mui = mui;
mui.add(this);
}
}
class CancelButton extends JButton{
MainUI mui;
public CancelButton(MainUI mui) {
setSize(80,60);
setText("Cancel");
this.mui = mui;
mui.add(this);
}
}
Как видите, разница только в setText
функции.
Так это стандартная практика?
Кстати, курс, где это обсуждалось, называется Best Programming Practices на Java
[Ответ от проф.]
Поэтому я обсудил проблему с профессором и затронул все вопросы, упомянутые в ответах.
Его оправдание заключается в том, что подклассы предоставляют код многократного использования при соблюдении стандартов проектирования графического интерфейса. Например, если разработчик использовал пользовательские кнопки Okay
и Cancel
кнопки в одном окне, будет проще разместить те же кнопки в других окнах.
Я понимаю причину, которую я предполагаю, но все же это просто использование наследования и создание хрупкого кода.
Позже, любой разработчик может случайно вызвать setText
на Okay
кнопку и изменить его. Подкласс просто становится неприятностью в этом случае.
JButton
и вызывать открытые методы в его конструкторе, если вы можете просто создатьJButton
и вызвать те же открытые методы вне класса?Ответы:
Это нарушает принцип подстановки Лискова, поскольку не
OkayButton
может быть заменен в любом месте, гдеButton
ожидается a . Например, вы можете изменить ярлык любой кнопки, как вам нравится. Но делать это сOkayButton
нарушением его внутренних инвариантов.Это классическое неправильное использование наследования для повторного использования кода. Вместо этого используйте вспомогательный метод.
Другая причина не делать этого состоит в том, что это просто запутанный способ достижения того же, что и линейный код.
источник
OkayButton
у вас нет инварианта, о котором вы думаете. ПользовательOkayButton
может утверждать, что не имеет никаких дополнительных инвариантов, он может просто рассматривать текст по умолчанию и намереваться полностью поддерживать изменения в тексте. Все еще не очень хорошая идея, но не по этой причине.activateButton(Button btn)
ожидает кнопку, вы можете легко дать ему экземплярOkayButton
. Принцип не означает, что он должен иметь точно такую же функциональность, поскольку это сделает наследование практически бесполезным.setText(button, "x")
потому что это нарушает (предполагаемый) инвариант. Это правда, что этот конкретный OkayButton может не иметь какого-либо интересного инварианта, поэтому я думаю, что выбрал плохой пример. Но это возможно. Например, что если обработчик кликов говоритif (myText == "OK") ProcessOK(); else ProcessCancel();
? Тогда текст является частью инварианта.Это абсолютно ужасно во всех возможных отношениях. В наиболее используйте функцию фабрики для производства JButtons. Вы должны наследовать их, только если у вас есть серьезные потребности в расширении.
источник
JButton
,JCheckBox
,JRadioButton
, это просто уступка разработчиков будучи знакомы с другими инструментариев, как простой AWT, где такие типы являются стандартными. В принципе, все они могут обрабатываться одним классом кнопок. Это комбинация модели и делегата пользовательского интерфейса, которая имеет реальное значение.Это очень нестандартный способ написания кода на Swing. Как правило, вы редко создаете подклассы компонентов пользовательского интерфейса Swing, чаще всего JFrame (для настройки дочерних окон и обработчиков событий), но даже это подклассирование не является необходимым и многими не рекомендуется. Предоставление настройки текста для кнопок и т. Д. Обычно выполняется путем расширения класса AbstractAction (или интерфейса Action, который он предоставляет). Это может обеспечить текст, значки и другие необходимые визуальные настройки и связать их с реальным кодом, который они представляют. Это гораздо лучший способ написания кода пользовательского интерфейса, чем примеры, которые вы показываете.
(кстати, ученый Google никогда не слышал о цитируемой вами статье - у вас есть более точная ссылка?)
источник
JPanel
. На самом деле это более полезно, чем подклассJFrame
, поскольку панель - это просто абстрактный контейнер.ИМХО, лучшие практики программирования на Java определены книгой Джошуа Блоха «Эффективная Java». Здорово, что твой учитель дает тебе ООП упражнения, и важно научиться читать и писать стили программирования других людей. Но за пределами книги Джоша Блоха мнения о лучших практиках довольно сильно различаются.
Если вы собираетесь расширить этот класс, вы также можете воспользоваться наследованием. Создайте класс MyButton для управления общим кодом и подкласс его для переменных частей:
Когда это хорошая идея? Когда вы используете типы, которые вы создали! Например, если у вас есть функция для создания всплывающего окна и подпись:
Те типы, которые вы только что создали, не приносят никакой пользы. Но:
Теперь вы создали что-то полезное.
Компилятор проверяет, что showPopUp принимает OkButton и CancelButton. Кто-то, читающий код, знает, как эта функция предназначена для использования, потому что такого рода документация вызовет ошибку во время компиляции, если она устареет. Это ОСНОВНАЯ выгода. 1 или 2 эмпирических исследования преимуществ безопасности типов показали, что понимание человеческого кода было единственным измеримым преимуществом.
Это также предотвращает ошибки, когда вы меняете порядок кнопок, передаваемых в функцию. Эти ошибки трудно обнаружить, но они также довольно редки, так что это НЕМНОГО преимущество. Это использовалось, чтобы продавать безопасность типов, но не было эмпирически доказано, что это особенно полезно.
Первая форма функции более гибкая, поскольку она отображает любые две кнопки. Иногда это лучше, чем ограничивать, какие кнопки это займет. Просто помните, что вам нужно протестировать функцию «любая кнопка» при большем количестве обстоятельств - если она работает только тогда, когда вы передаете ей абсолютно правильные виды кнопок, вы никому не оказываете услугу, притворяясь, что она будет использовать любую кнопку.
Проблема с объектно-ориентированным программированием заключается в том, что он ставит телегу впереди лошади. Сначала нужно написать функцию, чтобы узнать, что означает подпись, стоит ли создавать для нее типы. Но в Java сначала нужно создать типы, чтобы можно было написать сигнатуру функции.
По этой причине вы можете захотеть написать некоторый код Clojure, чтобы увидеть, как здорово сначала написать свои функции. Вы можете быстро программировать, максимально обдумывая асимптотическую сложность, и допущение Clojure об неизменности предотвращает ошибки, как это делают типы в Java.
Я до сих пор фанат статических типов для больших проектов и теперь использую некоторые функциональные утилиты, которые позволяют мне сначала писать функции, а потом называть типы в Java . Просто мысль.
PS Я сделал
mui
указатель окончательным - он не должен быть изменяемым.источник
Я был бы готов поспорить, что ваш учитель на самом деле не верит, что вы всегда должны расширять компонент Swing, чтобы использовать его. Бьюсь об заклад, они просто используют это в качестве примера, чтобы заставить вас практиковать расширение классов. Я бы пока не слишком беспокоился о лучших мировых практиках.
При этом в реальном мире мы предпочитаем композицию, а не наследование .
Ваше правило «следует расширять класс только при наличии отношения IS-A» является неполным. Он должен заканчиваться на "... и нам нужно изменить поведение класса по умолчанию" большими жирными буквами.
Ваши примеры не соответствуют этим критериям. Вы не должны
extend
вJButton
классе просто установить его текст. Вам не нужно просто добавлятьextend
вJFrame
класс компоненты. Вы можете делать эти вещи очень хорошо, используя их реализации по умолчанию, поэтому добавление наследования просто добавляет ненужные сложности.Если я увижу класс
extends
другого класса, я буду удивляться, что этот класс меняется . Если ты ничего не меняешь, не заставляй меня смотреть весь класс.Возвращаясь к вашему вопросу: когда вы должны расширять класс Java? Когда у вас есть действительно очень хорошая причина продлить класс.
Вот конкретный пример: один из способов сделать пользовательское рисование (для игры или анимации, или просто для пользовательского компонента) - это расширение
JPanel
класса. (Более подробная информация о том , что здесь .) Вы продлеваетеJPanel
класс , потому что вам нужно переопределить вpaintComponent()
функцию. Делая это, вы фактически меняете поведение класса . Вы не можете сделать пользовательскую рисование сJPanel
реализацией по умолчанию .Но, как я уже сказал, ваш учитель, вероятно, просто использует эти примеры в качестве предлога, чтобы заставить вас практиковаться в расширении классов.
источник
Border
что рисует дополнительные украшения. Или создайтеJLabel
и передайте пользовательскуюIcon
реализацию к нему. Нет необходимости создавать подклассJPanel
для рисования (и почемуJPanel
вместоJComponent
).