Kapan saya harus memperpanjang kelas Java Swing?

35

Pemahaman saya saat ini tentang implementasi Warisan adalah bahwa seseorang hanya perlu memperluas kelas jika ada hubungan IS-A . Jika kelas induk lebih lanjut dapat memiliki tipe anak yang lebih spesifik dengan fungsionalitas yang berbeda tetapi akan berbagi elemen umum yang diabstraksi dalam induk.

Saya mempertanyakan pemahaman itu karena apa yang direkomendasikan oleh profesor Jawa saya untuk kita lakukan. Dia merekomendasikan untuk JSwingaplikasi yang sedang kita buat di kelas

Satu harus memperpanjang semua JSwingkelas ( JFrame, JButton, JTextBox, dll) ke dalam kelas kustom terpisah dan menentukan kustomisasi terkait GUI di dalamnya (seperti ukuran komponen, komponen label, dll)

Sejauh ini bagus, tetapi ia lebih lanjut menyarankan bahwa setiap JButton harus memiliki kelas ekstensi kustom sendiri meskipun satu-satunya faktor pembeda adalah label mereka.

Untuk misalnya Jika GUI memiliki dua tombol, Oke dan Batalkan . Dia merekomendasikan mereka harus diperpanjang seperti di bawah ini:

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);        
    }
}

Seperti yang Anda lihat, satu-satunya perbedaan adalah setTextfungsi.

Jadi apakah ini praktik standar?

Btw, kursus di mana ini dibahas disebut Praktik Pemrograman Terbaik di Jawa

[Balas dari Prof]

Jadi saya membahas masalah dengan profesor dan mengangkat semua poin yang disebutkan dalam jawaban.

Pembenarannya adalah bahwa subclassing menyediakan kode yang dapat digunakan kembali sambil mengikuti standar desain GUI. Misalnya jika pengembang telah menggunakan tombol Okaydan kustom Canceldi satu Window, akan lebih mudah untuk menempatkan tombol yang sama di Windows lain juga.

Saya mendapatkan alasan saya kira, tapi tetap saja hanya mengeksploitasi warisan dan membuat kode rapuh.

Kemudian, setiap pengembang sengaja bisa memanggil setTextpada Okaytombol dan mengubahnya. Subkelas hanya menjadi gangguan dalam kasus itu.

Paras
sumber
3
Mengapa perluas JButtondan panggil metode publik dalam konstruktornya, ketika Anda bisa membuat JButtondan memanggil metode publik yang sama di luar kelas?
17
menjauhlah dari profesor itu jika Anda bisa. Ini cukup jauh dari praktik terbaik . Duplikasi kode seperti yang Anda gambarkan adalah bau yang sangat buruk.
njzk2
7
Tanyakan saja kepadanya mengapa, jangan ragu untuk menyampaikan motivasinya di sini.
Alex
9
Saya ingin mengundurkan diri karena itu kode yang buruk, tetapi saya juga ingin mengundurkan diri karena bagus jika Anda bertanya alih-alih menerima secara membabi buta apa yang dikatakan profesor yang buruk.
Dana Gugatan Monica
1
@Alex Saya telah memperbarui pertanyaan dengan justifikasi prof
Paras

Jawaban:

21

Ini melanggar Prinsip Substitusi Liskov karena OkayButtontidak dapat diganti di tempat yang Buttondiharapkan. Misalnya, Anda dapat mengubah label tombol apa saja sesuka Anda. Tetapi melakukan itu dengan OkayButtonmelanggar invarian internalnya.

Ini adalah penyalahgunaan klasik warisan untuk penggunaan kembali kode. Gunakan metode pembantu sebagai gantinya.

Alasan lain untuk tidak melakukan ini adalah bahwa ini hanyalah cara berbelit-belit untuk mencapai hal yang sama dengan kode linier.

usr
sumber
Ini tidak melanggar LSP kecuali OkayButtonmemiliki invarian yang Anda pikirkan. The OkayButtondapat mengklaim tidak memiliki invarian tambahan, itu hanya dapat menganggap teks sebagai default dan bermaksud untuk sepenuhnya mendukung modifikasi pada teks. Masih bukan ide yang baik, tetapi bukan karena alasan itu.
hvd
Itu tidak melanggar itu karena Anda bisa. Yaitu, jika suatu metode activateButton(Button btn)mengharapkan sebuah tombol, Anda dapat dengan mudah memberikannya OkayButton. Prinsipnya tidak berarti bahwa ia harus memiliki fungsionalitas yang persis sama, karena ini akan membuat warisan menjadi sangat tidak berguna.
Sebb
1
@ Sleb tetapi Anda tidak dapat menggunakannya dengan fungsi setText(button, "x")karena itu melanggar invarian (diasumsikan). Memang benar bahwa OkayButton tertentu ini mungkin tidak memiliki invarian yang menarik, jadi saya kira saya mengambil contoh yang buruk. Tapi mungkin saja. Misalnya, bagaimana jika penangan klik mengatakan if (myText == "OK") ProcessOK(); else ProcessCancel();? Maka teks adalah bagian dari invarian.
usr
@ Usr Saya tidak berpikir tentang teks yang menjadi bagian dari invarian. Anda benar, itu dilanggar saat itu.
Sebb
50

Benar-benar mengerikan dalam segala hal yang mungkin. Paling- paling , gunakan fungsi pabrik untuk menghasilkan JButtons. Anda hanya boleh mewarisi dari mereka jika Anda memiliki beberapa kebutuhan ekstensi serius.

DeadMG
sumber
20
+1. Untuk referensi lebih lanjut, lihat hierarki kelas Swing AbstractButton . Kemudian lihat hierarki JToggleButton . Warisan digunakan untuk membuat konsep berbagai jenis tombol. Tapi itu tidak digunakan untuk membedakan konsep bisnis ( Ya, Tidak, Lanjutkan, Batalkan ... ).
Laiv
2
@Laiv: bahkan memiliki jenis yang berbeda untuk, misalnya JButton, JCheckBox, JRadioButton, hanya konsesi untuk pengembang menjadi akrab dengan toolkit lain, seperti AWT polos, di mana jenis tersebut standar. Pada prinsipnya, mereka semua dapat ditangani oleh kelas tombol tunggal. Kombinasi dari model dan delegasi UI yang membuat perbedaan nyata.
Holger
Ya mereka bisa ditangani dengan satu tombol. Namun hierarki yang sebenarnya dapat diekspos sebagai praktik yang baik. Untuk membuat Tombol baru atau hanya untuk mendelegasikan kontrol acara dan behaivor ke komponen lain adalah masalah preferensi. Jika saya, saya akan memperluas komponen Swing untuk memodelkan tombol owm saya dan merangkum behaivors, tindakan, ... Hanya untuk memudahkan implementasinya dalam proyek dan membuatnya mudah untuk junior. Dalam web envs saya lebih suka komponen web daripada terlalu banyak penanganan acara. Bagaimanapun. Anda benar kami dapat memisahkan UI dari Behaivors.
Laiv
12

Ini adalah cara menulis kode Swing yang sangat tidak standar. Biasanya, Anda jarang membuat subkelas komponen Swing UI, paling umum JFrame (untuk mengatur windows anak dan pengendali acara), tetapi bahkan subklas tidak diperlukan dan tidak disarankan oleh banyak orang. Memberikan kustomisasi teks ke tombol dan sebagainya biasanya dilakukan dengan memperluas kelas AbstractAction (atau antarmuka Action yang disediakannya). Ini dapat memberikan teks, ikon, dan kustomisasi visual lain yang diperlukan dan menautkannya ke kode aktual yang diwakilinya. Ini adalah cara yang jauh lebih baik untuk menulis kode UI daripada contoh yang Anda tunjukkan.

(Ngomong-ngomong, google scholar belum pernah mendengar makalah yang Anda kutip - apakah Anda memiliki referensi yang lebih tepat?)

Jules
sumber
Apa sebenarnya "cara standar" itu? Saya setuju bahwa menulis subkelas untuk setiap komponen mungkin merupakan ide yang buruk, tetapi tutorial Swing resmi masih mendukung praktik ini. Saya rasa profesor hanya mengikuti gaya yang ditunjukkan dalam dokumentasi resmi kerangka kerja.
DATANG DARI
@COMEFROM Saya akui tidak membaca seluruh tutorial Swing, jadi mungkin saya telah melewatkan di mana gaya ini digunakan, tetapi halaman yang saya lihat cenderung menggunakan kelas dasar dan tidak mendorong subclass, misalnya docs.oracle .com / javase / tutorial / uiswing / components / button.html
Jules
@Jules menyesal tentang kebingungan, maksud saya kursus / makalah yang profesor ajarkan disebut Praktik Terbaik di Jawa
Paras
1
Secara umum benar, tetapi ada satu pengecualian utama lainnya - JPanel. Ini sebenarnya lebih berguna untuk subkelas dari itu JFrame, karena panel hanyalah wadah abstrak.
Ordous
10

IMHO, Praktik Pemrograman Terbaik di Jawa didefinisikan oleh buku Joshua Bloch, "Java Efektif." Sangat bagus bahwa guru Anda memberi Anda latihan OOP dan penting untuk belajar membaca dan menulis gaya pemrograman orang lain. Tetapi di luar buku Josh Bloch, pendapat cukup beragam tentang praktik terbaik.

Jika Anda akan memperpanjang kelas ini, Anda mungkin juga memanfaatkan warisan. Buat kelas MyButton untuk mengelola kode umum dan sub-kelas untuk bagian variabel:

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");
    }
}

Kapan ini ide yang bagus? Saat Anda menggunakan tipe yang Anda buat! Misalnya, jika Anda memiliki fungsi untuk membuat jendela sembulan dan tanda tangannya adalah:

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

Jenis yang baru saja Anda buat tidak ada gunanya sama sekali. Tapi:

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

Sekarang Anda telah membuat sesuatu yang bermanfaat.

  1. Compiler memverifikasi bahwa showPopUp mengambil OkButton dan CancelButton. Seseorang yang membaca kode tahu bagaimana fungsi ini dimaksudkan untuk digunakan karena dokumentasi semacam ini akan menyebabkan kesalahan waktu kompilasi jika keluar dari tanggal. Ini adalah manfaat UTAMA. Studi empiris 1 atau 2 dari manfaat keselamatan jenis menemukan bahwa pemahaman kode manusia adalah satu-satunya manfaat yang dapat diukur.

  2. Ini juga mencegah kesalahan saat Anda membalik urutan tombol yang Anda lewati ke fungsi. Kesalahan ini sulit dikenali, tetapi juga jarang, jadi ini adalah manfaat MINOR. Ini digunakan untuk menjual keamanan tipe, tetapi belum secara empiris terbukti sangat berguna.

  3. Bentuk pertama dari fungsi ini lebih fleksibel karena akan menampilkan dua tombol. Terkadang itu lebih baik daripada membatasi tombol seperti apa yang akan diambil. Ingatlah bahwa Anda harus menguji fungsi tombol apa saja dalam lebih banyak keadaan - jika hanya berfungsi ketika Anda melewatinya dengan tepat tombol yang tepat, Anda tidak melakukan bantuan apa pun kepada siapa pun dengan berpura-pura akan mengambil tombol apa pun.

Masalah dengan Pemrograman Berorientasi Objek adalah menempatkan keranjang di depan kuda. Anda harus menulis fungsi terlebih dahulu untuk mengetahui tanda tangan apa yang perlu diketahui jika perlu membuat jenis untuk tanda tangan tersebut. Tetapi di Jawa, Anda harus membuat tipe Anda terlebih dahulu sehingga Anda dapat menulis fungsi tanda tangan.

Untuk alasan ini, Anda mungkin ingin menulis beberapa kode Clojure untuk melihat betapa hebatnya menulis fungsi Anda terlebih dahulu. Anda dapat membuat kode dengan cepat, memikirkan kompleksitas asimptotik sebanyak mungkin, dan asumsi Clojure tentang immutabilitas mencegah bug sebanyak jenis di Jawa.

Saya masih penggemar tipe statis untuk proyek besar dan sekarang menggunakan beberapa utilitas fungsional yang memungkinkan saya untuk menulis fungsi terlebih dahulu dan beri nama tipe saya nanti di Jawa . Hanya pemikiran saja.

PS Saya membuat muipointer final - tidak perlu bisa berubah.

GlenPeterson
sumber
6
Dari 3 poin Anda, 1 dan 3 juga dapat ditangani dengan JButtons generik dan skema penamaan parameter input yang tepat. poin 2 sebenarnya adalah kerugian karena membuat kode Anda lebih erat-berpasangan: bagaimana jika Anda ingin urutan tombol dibalik? Bagaimana jika, alih-alih tombol OK / Batalkan, Anda ingin menggunakan tombol Ya / Tidak? Apakah Anda akan membuat metode showPopup yang sepenuhnya baru yang menggunakan YesButton dan Nobutton? Jika Anda hanya menggunakan JButtons default, Anda tidak perlu membuat tipe Anda terlebih dahulu karena sudah ada.
Nzall
Memperluas apa yang dikatakan @NateKerkhofs, sebuah IDE modern akan menunjukkan kepada Anda nama argumen formal saat Anda mengetikkan ekspresi aktual.
Solomon Slow
8

Saya berani bertaruh bahwa guru Anda tidak benar-benar percaya bahwa Anda harus selalu memperluas komponen Swing untuk menggunakannya. Saya yakin mereka hanya menggunakan ini sebagai contoh untuk memaksa Anda berlatih kelas ekstensi. Saya tidak akan terlalu khawatir tentang praktik terbaik dunia nyata dulu.

Karena itu, di dunia nyata kami lebih menyukai komposisi daripada warisan .

Aturan Anda "satu harus hanya memperluas kelas jika ada hubungan IS-A" tidak lengkap. Itu harus diakhiri dengan "... dan kita perlu mengubah perilaku default kelas" dalam huruf tebal besar.

Contoh Anda tidak cocok dengan kriteria itu. Anda tidak harus extenddengan JButtonkelas hanya untuk mengatur teks-nya. Anda tidak harus extenddengan JFramekelas hanya untuk menambah komponen untuk itu. Anda dapat melakukan hal-hal ini dengan baik menggunakan implementasi default mereka, jadi menambahkan warisan hanya menambahkan komplikasi yang tidak perlu.

Jika saya melihat kelas yang kelas extendslain, saya akan bertanya-tanya apa kelas itu berubah . Jika Anda tidak mengubah apa pun, jangan buat saya melihat-lihat kelas sama sekali.

Kembali ke pertanyaan Anda: kapan Anda harus memperpanjang kelas Java? Ketika Anda memiliki alasan yang sangat, sangat, sangat bagus untuk memperpanjang kelas.

Berikut adalah contoh spesifik: salah satu cara untuk melakukan lukisan kustom (untuk game atau animasi, atau hanya untuk komponen kustom) adalah dengan memperluas JPanelkelas. (Lebih jauh tentang itu di sini .) Anda memperpanjang JPanelkelas karena Anda perlu mengesampingkan yang paintComponent()fungsi. Anda sebenarnya mengubah perilaku kelas dengan melakukan ini. Anda tidak dapat melakukan lukisan kustom dengan JPanelimplementasi standar .

Tetapi seperti yang saya katakan, guru Anda mungkin hanya menggunakan contoh-contoh ini sebagai alasan untuk memaksa Anda berlatih kelas tambahan.

Kevin Workman
sumber
1
Oh, tunggu, Anda dapat mengatur Borderbahwa cat dekorasi tambahan. Atau buat JLabeldan kirimkan Iconimplementasi kustom untuk itu. Tidak perlu subkelas JPaneluntuk melukis (dan mengapa JPanelbukannya JComponent).
Holger
1
Saya pikir Anda memiliki ide yang tepat di sini, tetapi mungkin bisa mengambil contoh yang lebih baik.
1
Tapi aku ingin mengulangi jawaban Anda benar dan Anda membuat poin yang baik . Saya hanya berpikir contoh spesifik dan tutorial mendukungnya dengan lemah.
1
@KevinWorkman Saya tidak tahu tentang pengembangan permainan Swing, tetapi saya telah melakukan beberapa UI khusus di Swing dan "tidak memperluas kelas Swing sehingga mudah untuk memasang komponen dan penyaji melalui konfigurasi" cukup standar di sana.
1
"Aku yakin mereka hanya menggunakan ini sebagai contoh untuk memaksamu untuk ..." Salah satu dari kencing utama saya! Instruktur yang mengajar bagaimana melakukan X menggunakan contoh mengerikan kenapa harus melakukan X.
Solomon Slow