Когда мне следует расширить класс Java Swing?

35

Мое текущее понимание реализации Наследования состоит в том, что следует расширять класс только при наличии отношения 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кнопку и изменить его. Подкласс просто становится неприятностью в этом случае.

Paras
источник
3
Зачем расширять JButtonи вызывать открытые методы в его конструкторе, если вы можете просто создать JButtonи вызвать те же открытые методы вне класса?
17
держись подальше от этого профессора, если сможешь. Это довольно далеко от лучших практик . Дублирование кода, как вы иллюстрировали, очень неприятный запах.
njzk2
7
Просто спросите его, почему, не стесняйтесь сообщить его мотивацию здесь.
Алекс
9
Я хочу понизить голос, потому что это ужасный код, но я также хочу поднять голос, потому что это здорово, что вы спрашиваете, а не слепо принимаете то, что говорит плохой профессор.
Фонд Моники иск
1
@ Алекс Я обновил вопрос с обоснованием профессора
пункты

Ответы:

21

Это нарушает принцип подстановки Лискова, поскольку не OkayButtonможет быть заменен в любом месте, где Buttonожидается a . Например, вы можете изменить ярлык любой кнопки, как вам нравится. Но делать это с OkayButtonнарушением его внутренних инвариантов.

Это классическое неправильное использование наследования для повторного использования кода. Вместо этого используйте вспомогательный метод.

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

USR
источник
Это не нарушает LSP, если OkayButtonу вас нет инварианта, о котором вы думаете. Пользователь OkayButtonможет утверждать, что не имеет никаких дополнительных инвариантов, он может просто рассматривать текст по умолчанию и намереваться полностью поддерживать изменения в тексте. Все еще не очень хорошая идея, но не по этой причине.
HVd
Это не нарушает, потому что вы можете. Т.е., если метод activateButton(Button btn)ожидает кнопку, вы можете легко дать ему экземпляр OkayButton. Принцип не означает, что он должен иметь точно такую ​​же функциональность, поскольку это сделает наследование практически бесполезным.
Себб
1
@Sebb, но вы не можете использовать его с функцией, setText(button, "x")потому что это нарушает (предполагаемый) инвариант. Это правда, что этот конкретный OkayButton может не иметь какого-либо интересного инварианта, поэтому я думаю, что выбрал плохой пример. Но это возможно. Например, что если обработчик кликов говорит if (myText == "OK") ProcessOK(); else ProcessCancel();? Тогда текст является частью инварианта.
USR
@usr Я не думал, что текст является частью инварианта. Вы правы, тогда это нарушено.
Себб
50

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

DeadMG
источник
20
+1. Для получения дополнительной информации посмотрите иерархию классов Swing AbstractButton . Тогда посмотрите иерархию JToggleButton . Наследование используется для концептуализации различных типов кнопок. Но он не используется для дифференциации бизнес-концепций ( да, нет, продолжить, отменить ... ).
Laiv
2
@Laiv: даже при наличии различных типов для, например JButton, JCheckBox, JRadioButton, это просто уступка разработчиков будучи знакомы с другими инструментариев, как простой AWT, где такие типы являются стандартными. В принципе, все они могут обрабатываться одним классом кнопок. Это комбинация модели и делегата пользовательского интерфейса, которая имеет реальное значение.
Хольгер
Да, они могут быть обработаны одной кнопкой. Однако фактическая иерархия может быть представлена ​​как хорошая практика. Создать новую кнопку или просто делегировать управление событием и поведением другим компонентам - вопрос предпочтений. Если бы был я, я бы расширил компоненты Swing, чтобы смоделировать мою кнопку owm и инкапсулировать ее поведение, действия, ... просто, чтобы упростить ее реализацию в проекте и упростить для юниоров. В веб-средах я предпочитаю веб-компоненты слишком большой обработке событий. В любом случае. Вы правы, мы можем отделить пользовательский интерфейс от поведения.
Laiv
12

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

(кстати, ученый Google никогда не слышал о цитируемой вами статье - у вас есть более точная ссылка?)

Жюль
источник
Что именно является «стандартным способом»? Я согласен, что написание подкласса для каждого компонента, вероятно, плохая идея, но официальные учебные пособия по Swing все еще поддерживают эту практику. Я считаю, что профессор просто следует стилю, показанному в официальной документации фреймворка.
Приходите с
@COMEFROM Я признаю, что не прочитал весь учебник по Swing, так что, возможно, я пропустил, где используется этот стиль, но страницы, на которые я смотрел, обычно используют базовые классы, а не подклассы дисков, например, docs.oracle .com / JavaSE / учебник / uiswing / компоненты / button.html
Жюль
@ Джулс, извините за путаницу, я имел в виду, что курс / статья, которую преподаёт профессор, называется Best Practices in Java
Paras
1
В целом верно, но есть еще одно важное исключение - JPanel. На самом деле это более полезно, чем подкласс JFrame, поскольку панель - это просто абстрактный контейнер.
Ордус
10

ИМХО, лучшие практики программирования на Java определены книгой Джошуа Блоха «Эффективная Java». Здорово, что твой учитель дает тебе ООП упражнения, и важно научиться читать и писать стили программирования других людей. Но за пределами книги Джоша Блоха мнения о лучших практиках довольно сильно различаются.

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

class MyButton extends JButton{
    protected final MainUI mui;
    public MyButton(MainUI mui, String text) {
        setSize(80,60);
        setText(text);
        this.mui = mui;
        mui.add(this);        
    }
}

class OkayButton extends MyButton{
    public OkayButton(MainUI mui) {
        super(mui, "Okay");
    }
}

class CancelButton extends MyButton{
    public CancelButton(MainUI mui) {
        super(mui, "Cancel");
    }
}

Когда это хорошая идея? Когда вы используете типы, которые вы создали! Например, если у вас есть функция для создания всплывающего окна и подпись:

public void showPopUp(String text, JButton ok, JButton cancel)

Те типы, которые вы только что создали, не приносят никакой пользы. Но:

public void showPopUp(String text, OkButton ok, CancelButton cancel)

Теперь вы создали что-то полезное.

  1. Компилятор проверяет, что showPopUp принимает OkButton и CancelButton. Кто-то, читающий код, знает, как эта функция предназначена для использования, потому что такого рода документация вызовет ошибку во время компиляции, если она устареет. Это ОСНОВНАЯ выгода. 1 или 2 эмпирических исследования преимуществ безопасности типов показали, что понимание человеческого кода было единственным измеримым преимуществом.

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

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

Проблема с объектно-ориентированным программированием заключается в том, что он ставит телегу впереди лошади. Сначала нужно написать функцию, чтобы узнать, что означает подпись, стоит ли создавать для нее типы. Но в Java сначала нужно создать типы, чтобы можно было написать сигнатуру функции.

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

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

PS Я сделал muiуказатель окончательным - он не должен быть изменяемым.

GlenPeterson
источник
6
Из ваших 3-х точек 1 и 3 также могут быть обработаны с помощью универсальных кнопок JButtons и правильной схемы именования входных параметров. пункт 2 на самом деле является недостатком в том, что он делает ваш код более тесно связанным: что если вы действительно хотите, чтобы порядок кнопок был обратным? Что делать, если вместо кнопок OK / Отмена вы хотите использовать кнопки Да / Нет? Сделаете ли вы совершенно новый метод showPopup, который принимает YesButton и Nobutton? Если вы просто используете JButton по умолчанию, вам не нужно сначала создавать типы, потому что они уже существуют.
Nzall
Расширяя сказанное @NateKerkhofs, современная IDE покажет вам формальные имена аргументов, пока вы набираете фактические выражения.
Соломон Слоу
8

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

При этом в реальном мире мы предпочитаем композицию, а не наследование .

Ваше правило «следует расширять класс только при наличии отношения IS-A» является неполным. Он должен заканчиваться на "... и нам нужно изменить поведение класса по умолчанию" большими жирными буквами.

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

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

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

Вот конкретный пример: один из способов сделать пользовательское рисование (для игры или анимации, или просто для пользовательского компонента) - это расширение JPanelкласса. (Более подробная информация о том , что здесь .) Вы продлеваете JPanelкласс , потому что вам нужно переопределить в paintComponent()функцию. Делая это, вы фактически меняете поведение класса . Вы не можете сделать пользовательскую рисование с JPanelреализацией по умолчанию .

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

Кевин Воркман
источник
1
Ой, подождите, вы можете установить, Borderчто рисует дополнительные украшения. Или создайте JLabelи передайте пользовательскую Iconреализацию к нему. Нет необходимости создавать подкласс JPanelдля рисования (и почему JPanelвместо JComponent).
Хольгер
1
Я думаю, что у вас есть правильная идея, но, возможно, могли бы выбрать лучшие примеры.
1
Но я хочу повторить, что ваш ответ правильный, и вы делаете хорошие замечания . Я просто думаю, что конкретный пример и учебник поддерживают его слабо.
1
@KevinWorkman Я не знаю о разработке игр на Swing, но я сделал несколько пользовательских интерфейсов в Swing, и «не расширяя классы Swing, чтобы легко подключать компоненты и средства визуализации через конфигурацию», довольно стандартно там.
1
«Бьюсь об заклад, они просто используют это в качестве примера, чтобы заставить вас ...» Один из моих главных раздражителей! Инструктора , которые учат , как сделать X , используя ужасно невменяемые примеры почему делать X.
Соломон Slow