Коли я повинен розширити клас Java Swing?


35

Моє сучасне розуміння реалізації Успадкування полягає в тому, що слід розширювати клас лише тоді, коли є відношення IS-A . Якщо батьківський клас може додатково мати більш конкретні дочірні типи з різною функціональністю, але вони матимуть спільні елементи, абстраговані у батьків.

Я ставлю під сумнів це розуміння через те, що рекомендує нам робити мій професор Java. Він порекомендував для JSwingпрограми, яку ми будуємо на уроці

Необхідно розширити всі JSwingкласи ( JFrame, JButton, JTextBox, і т.д.) в окремі призначені для користувача класи і вказати GUI відповідного налаштування в них (наприклад , розмір компонента, мітки компонента, і т.д.)

Поки що добре, але далі він радить, що кожен JButton повинен мати власний розширений клас, хоча єдиним відмінним фактором є їх марка.

Наприклад, якщо у GUI є дві кнопки Гаразд та Скасувати . Він рекомендує розширити їх, як зазначено нижче:

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функції.

Так це стандартна практика?

До речі, курс, де це обговорювалося, називається Кращі практики програмування на Java

[Відповідь професора]

Тож я обговорив проблему з професором і підняв усі моменти, згадані у відповідях.

Його виправданням є те, що підкласифікація надає код для багаторазового використання, дотримуючись стандартів дизайну GUI. Наприклад, якщо розробник використовував користувацькі Okayта Cancelкнопки в одному вікні, буде зручніше розмістити ті самі кнопки і в інших Windows.

Я маю на увазі причину, але все-таки це просто використання спадщини та надання коду тендітним.

Пізніше будь-який розробник може випадково зателефонувати setTextна Okayкнопку та змінити її. Підклас просто стає неприємним у такому випадку.


3
Навіщо розширювати JButtonта викликати публічні методи у своєму конструкторі, коли ви можете просто створити JButtonта викликати ті самі публічні методи поза класом?

17
тримайтеся подалі від цього професора, якщо можете. Це далеко не найкраща практика . Дублювання коду, як ви проілюстрували, дуже неприємний запах.
njzk2

7
Просто запитайте його, чому, не соромтеся доносити тут свою мотивацію.
Олексій

9
Я хочу оскаржити те, що це жахливий код, але я також хочу внести заяву, тому що це чудово, що ви ставите питання, а не сліпо приймаєте те, що говорить поганий професор.
Фонд позову Моніки

1
@Alex Я оновив питання з виправданням професора
Paras

Відповіді:


21

Це порушує Принцип заміщення Ліскова, оскільки не OkayButtonможе бути замінено місце в будь-якому місці Button. Наприклад, ви можете змінити мітку будь-якої кнопки за вашим бажанням. Але робити це з OkayButtonпорушенням його внутрішніх інваріантів.

Це класичне неправильне використання спадщини для повторного використання коду. Використовуйте замість цього хелперний метод.

Ще одна причина цього не робити - це лише звичний спосіб досягти того самого, що і лінійний код.


Це не порушує LSP, якщо OkayButtonне є інваріант, про який ви думаєте. OkayButtonМоже стверджувати , що не мають будь - які додаткові інваріанти, він може просто розглядати текст як по замовчуванням , і має намір повністю підтримати зміни в текст. Все-таки не гарна ідея, але не з цієї причини.
hvd

Це не порушує, тому що ти можеш. Тобто, якщо метод activateButton(Button btn)очікує кнопку, ви можете легко надати йому примірник OkayButton. Принцип не означає, що він повинен мати точно такий же функціонал, оскільки це зробить спадщину в значній мірі марною.
Себб

1
@Sebb, але ви не можете використовувати його з функцією, setText(button, "x")оскільки це порушує (припускається) інваріант. Це правда, що цей конкретний OkayButton може не мати будь-якого цікавого інваріанта, тому я гадаю, що я вибрав поганий приклад. Але це може. Наприклад, що робити, якщо скаже обробник кліків if (myText == "OK") ProcessOK(); else ProcessCancel();? Тоді текст є частиною інваріанта.
usr

@usr Я не думав про те, щоб текст був частиною інваріанта. Ти маєш рацію, це порушено тоді.
Себб

50

Це абсолютно страшно всіляко. У найбільш використовуйте функцію фабрики для виробництва JButtons. Ви повинні успадковувати їх лише в тому випадку, якщо у вас є серйозні потреби у продовженні.


20
+1. Для додаткової довідки подивіться ієрархію класів Swing у програмі AbstractButton . Потім подивіться ієрархію JToggleButton . Спадкування використовується для концептуалізації різних типів кнопок. Але він не використовується для розмежування бізнес-концепцій ( Так, Ні, Продовжити, Скасувати ... ).
Лаїв

2
@Laiv: навіть при наявності різних типів для, наприклад JButton, JCheckBox, JRadioButton, це просто поступка розробників будучи знайомі з іншими інструментаріїв, як простий AWT, де такі типи є стандартними. В принципі, всі вони могли оброблятись одним класом кнопок. Фактична різниця полягає в поєднанні моделі та делегата інтерфейсу користувача.
Холгер

Так, ними можна керувати однією кнопкою. Однак фактична ієрархія може бути викрита як хороша практика. Створювати нову кнопку або просто делегувати контроль події та поведінки іншим компонентам - питання переваг. Якби я, я би розширив компоненти Swing, щоб моделювати свою кнопку сови та інкапсулювати її поведінку, дії, ... Просто, щоб полегшити її реалізацію в проекті та полегшити для юніорів. У веб-середовищі я віддаю перевагу веб-компонентам надто великій кількості подій. Все одно. Ви маєте рацію, ми можемо роз'єднати інтерфейс користувача від Behaivors.
Лаїв

12

Це дуже нестандартний спосіб написання коду Swing. Як правило, ви рідко створюєте підкласи компонентів Swing UI, найчастіше JFrame (для того, щоб налаштувати дочірні вікна та обробники подій), але навіть цей підклас є непотрібним і багатьом не рекомендує. Забезпечення налаштування тексту на кнопки та інше зазвичай виконується шляхом розширення класу AbstractAction (або інтерфейсу Action, який він надає). Це може забезпечити текст, піктограми та інші необхідні візуальні налаштування та зв’язати їх із фактичним кодом, який вони представляють. Це набагато кращий спосіб написання коду інтерфейсу, ніж показані вами приклади.

(до речі, вчений google ніколи не чув про цитований вами документ - у вас є більш точна довідка?)


Що саме є "стандартним способом"? Я погоджуюся, що писати підклас для кожного компонента, мабуть, погана ідея, але офіційні навчальні посібники Swing все ще надають перевагу цій практиці. Я вважаю, що професор просто слідує стилю, показаному в офіційній документації рамки.
ПРИЙДАЙТЕ

@COMEFROM Я визнаю, що не прочитав весь підручник Swing, тому, можливо, я пропустив, де використовується цей стиль, але сторінки, на які я переглянув, мають тенденцію використовувати базові класи, а не керувати підкласами, наприклад, docs.oracle .com / javase / підручник / uiswing / компоненти / button.html
Jules

@Jules вибачте за плутанину, я мав на увазі, що курс / стаття, яку викладає професор, називається Кращі практики на Яві
Paras

1
Взагалі правда, але є ще один головний виняток - JPanel. Це насправді корисніше для підкласу, ніж це JFrame, оскільки панель є просто абстрактним контейнером.
Звичайний

10

IMHO, Найкращі практики програмування на 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покажчик остаточним - його не потрібно змінювати.


6
З ваших 3 балів, 1 і 3 так само добре обробляються загальними JButtons і правильною схемою іменування вхідних параметрів. пункт 2 насправді є недоліком у тому, що він робить ваш код більш щільно сполученим: що робити, якщо ви дійсно хочете, щоб замовлення кнопок було повернене? Що робити, якщо замість кнопок ОК / Скасувати потрібно використовувати кнопки Так / Ні? Чи ви зробите абсолютно новий метод showPopup, який приймає YesButton і Nobutton? Якщо ви просто використовуєте JButtons за замовчуванням, вам не потрібно робити свої типи спочатку, оскільки вони вже існують.
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.
Соломон повільно
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.