Mengapa memilih kelas batin non-statis daripada yang statis?

37

Pertanyaan ini adalah tentang apakah membuat kelas bersarang di Jawa menjadi kelas bersarang statis atau kelas bersarang dalam. Saya mencari di sekitar sini dan di Stack Overflow, tetapi tidak bisa menemukan pertanyaan mengenai implikasi desain dari keputusan ini.

Pertanyaan yang saya temukan menanyakan tentang perbedaan antara kelas bersarang statis dan batin, yang jelas bagi saya. Namun, saya belum menemukan alasan yang meyakinkan untuk pernah menggunakan kelas bersarang statis di Jawa - dengan pengecualian kelas anonim, yang saya tidak mempertimbangkan untuk pertanyaan ini.

Inilah pemahaman saya tentang efek menggunakan kelas bersarang statis:

  • Kurang kopling: Kami umumnya mendapatkan lebih sedikit kopling, karena kelas tidak dapat langsung mengakses atribut kelas luarnya. Kurang kopling umumnya berarti kualitas kode yang lebih baik, pengujian lebih mudah, refactoring, dll.
  • Tunggal Class: Pemuat kelas tidak perlu mengurus kelas baru setiap kali, kami membuat objek dari kelas luar. Kami hanya mendapatkan objek baru untuk kelas yang sama berulang kali.

Untuk kelas dalam, saya biasanya menemukan bahwa orang menganggap akses ke atribut kelas luar sebagai pro. Saya mohon untuk berbeda dalam hal ini dari sudut pandang desain, karena akses langsung ini berarti kita memiliki sambungan tinggi dan jika kita ingin mengekstraksi kelas bersarang ke dalam kelas tingkat atas yang terpisah, kita hanya dapat melakukannya setelah mengubah itu menjadi kelas bersarang statis.

Jadi pertanyaan saya sampai pada ini: Apakah saya salah dalam mengasumsikan bahwa akses atribut yang tersedia untuk kelas dalam non-statis mengarah ke kopling tinggi, maka untuk kualitas kode yang lebih rendah, dan bahwa saya menyimpulkan dari ini bahwa kelas bersarang (non-anonim) harus umumnya statis?

Atau dengan kata lain: Apakah ada alasan yang meyakinkan mengapa seseorang lebih memilih kelas batin yang bersarang?

jujur
sumber
2
dari tutorial Java : "Gunakan kelas bersarang non-statis (atau kelas dalam) jika Anda memerlukan akses ke bidang dan metode non-publik instance terlampir. Gunakan kelas bersarang statis jika Anda tidak memerlukan akses ini."
nyamuk
5
Terima kasih atas kutipan tutorialnya, tetapi ini hanya mengulangi apa bedanya, bukan mengapa Anda ingin mengakses bidang instance.
Frank
ini lebih merupakan panduan umum, memberi petunjuk tentang apa yang lebih disukai. Saya menambahkan penjelasan yang lebih rinci tentang bagaimana saya menafsirkannya dalam jawaban ini
agas
1
Jika kelas dalam tidak tergabung erat dengan kelas penutup, mungkin kelas itu seharusnya bukan kelas batin.
Kevin Krumwiede

Jawaban:

23

Joshua Bloch dalam Item 22 bukunya "Effective Java Second Edition" memberi tahu kapan harus menggunakan kelas bertingkat mana dan mengapa. Ada beberapa kutipan di bawah ini:

Salah satu penggunaan umum dari kelas anggota statis adalah sebagai kelas pembantu publik, hanya berguna dalam hubungannya dengan kelas luarnya. Misalnya, pertimbangkan enum yang menjelaskan operasi yang didukung oleh kalkulator. Operation enum harus menjadi kelas anggota statis publik dari Calculatorkelas. Klien Calculatorkemudian dapat merujuk ke operasi menggunakan nama seperti Calculator.Operation.PLUSdan Calculator.Operation.MINUS.

Salah satu penggunaan umum dari kelas anggota tidak statis adalah untuk mendefinisikan Adaptor yang memungkinkan turunan dari kelas luar untuk dilihat sebagai turunan dari beberapa kelas yang tidak terkait. Sebagai contoh, implementasi dari Mapantarmuka biasanya menggunakan kelas anggota nonstatic untuk menerapkan mereka dilihat koleksi , yang dikembalikan oleh Map's keySet, entrySetdan valuesmetode. Demikian pula, implementasi dari interface pengumpulan, seperti Setdan List, biasanya menggunakan kelas anggota yang tidak statis untuk mengimplementasikan iterator mereka:

// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
    ... // Bulk of the class omitted

    public Iterator<E> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<E> {
        ...
    }
}

Jika Anda mendeklarasikan kelas anggota yang tidak memerlukan akses ke instance terlampir, selalu letakkan staticpengubah dalam deklarasi, menjadikannya kelas statis daripada kelas anggota tidak statis.

erakitin
sumber
1
Saya tidak yakin bagaimana / apakah adaptor ini mengubah tampilan MySetinstance kelas luar ? Saya bisa membayangkan sesuatu seperti tipe path-dependent menjadi perbedaan, yaitu myOneSet.iterator.getClass() != myOtherSet.iterator.getClass(), tetapi sekali lagi, di Jawa ini sebenarnya tidak mungkin, karena kelasnya akan sama untuk masing-masing.
Frank
@ Frank Ini adalah kelas adaptor yang cukup khas - memungkinkan Anda melihat set sebagai aliran elemen-elemennya (sebuah Iterator).
user253751
@immibis terima kasih, saya tahu betul apa yang dilakukan iterator / adaptor, tetapi pertanyaan ini tentang bagaimana penerapannya.
Frank
@ Terus terang saya katakan bagaimana hal itu mengubah tampilan contoh luar. Itulah yang dilakukan oleh adaptor - mengubah cara Anda melihat beberapa objek. Dalam hal ini, objek itu adalah instance luar.
user253751
10

Anda benar dalam mengasumsikan bahwa akses atribut yang tersedia untuk kelas dalam non-statis mengarah ke kopling tinggi, maka untuk menurunkan kualitas kode, dan kelas dalam (non-anonim dan non-lokal ) umumnya harus statis.

Implikasi desain dari keputusan untuk membuat kelas dalam non-statis diletakkan di Java Puzzlers , Puzzle 90 (huruf tebal dalam kutipan di bawah ini adalah milik saya):

Setiap kali Anda menulis kelas anggota, tanyakan pada diri sendiri, Apakah kelas ini benar-benar membutuhkan contoh terlampir? Jika jawabannya tidak, buatlah static. Kelas-kelas batin kadang berguna, tetapi mereka dapat dengan mudah menimbulkan komplikasi yang membuat program sulit dipahami. Mereka memiliki interaksi yang kompleks dengan obat generik (Puzzle 89), refleksi (Puzzle 80), dan pewarisan (puzzle ini) . Jika Anda menyatakan Inner1sebagai static, masalahnya hilang. Jika Anda juga menyatakan Inner2demikian static, Anda sebenarnya dapat memahami apa yang dilakukan oleh program: bonus yang bagus.

Singkatnya, jarang satu kelas cocok untuk menjadi kelas dalam dan kelas bawah. Secara umum, jarang tepat untuk memperluas kelas batin; jika Anda harus, berpikir panjang dan keras tentang contoh terlampir. Juga, lebih memilih statickelas bertingkat daripada yang bukan static. Sebagian besar kelas anggota dapat dan harus diumumkan static.

Jika Anda tertarik, analisis yang lebih rinci tentang Puzzle 90 disediakan dalam jawaban ini di Stack Overflow .


Perlu dicatat bahwa pada dasarnya adalah versi lanjutan dari panduan yang diberikan dalam tutorial Java Classes and Objects :

Gunakan kelas bertingkat non-statis (atau kelas dalam) jika Anda memerlukan akses ke bidang dan metode non-publik instance terlampir. Gunakan kelas bersarang statis jika Anda tidak memerlukan akses ini.

Jadi, jawaban atas pertanyaan yang Anda ajukan dengan kata lain per tutorial adalah, satu-satunya alasan yang meyakinkan untuk menggunakan non-statis adalah ketika akses ke bidang dan metode non-publik instance lampiran diperlukan .

Kata-kata tutorial agak luas (ini mungkin menjadi alasan mengapa Java Puzzlers berusaha untuk memperkuatnya dan mempersempitnya). Secara khusus, secara langsung mengakses bidang contoh terlampir tidak pernah benar-benar diperlukan dalam pengalaman saya - dalam arti bahwa cara-cara alternatif seperti melewatkan ini sebagai parameter konstruktor / metode selalu berubah lebih mudah untuk di-debug dan dipelihara.


Secara keseluruhan, pertemuan saya (yang sangat menyakitkan) dengan debugging kelas dalam yang secara langsung mengakses bidang instance terlampir membuat kesan kuat bahwa praktik ini menyerupai penggunaan negara global, bersama dengan kejahatan yang diketahui terkait dengannya.

Tentu saja, Java membuatnya sehingga kerusakan seperti "quasi global" terkandung di dalam kelas tertutup, tetapi ketika saya harus men-debug kelas batin tertentu, rasanya seperti bantuan band tidak membantu mengurangi rasa sakit: Saya masih memiliki untuk mengingat semantik dan rincian "asing" alih-alih berfokus sepenuhnya pada analisis objek bermasalah tertentu.


Demi kelengkapan, mungkin ada kasus di mana alasan di atas tidak berlaku. Misalnya, per bacaan javadocs map.keySet saya , fitur ini menyarankan penggabungan yang ketat dan sebagai hasilnya, membatalkan argumen terhadap kelas non-statis:

Mengembalikan Settampilan kunci yang ada di peta ini. Himpunan didukung oleh peta, sehingga perubahan pada peta tercermin dalam himpunan, dan sebaliknya ...

Bukan berarti di atas entah bagaimana akan membuat kode yang terlibat lebih mudah untuk mempertahankan, menguji dan men-debug pikiran Anda, tetapi setidaknya bisa memungkinkan seseorang untuk berpendapat bahwa komplikasi cocok / dibenarkan oleh fungsionalitas yang dimaksud.

agas
sumber
Saya membagikan pengalaman Anda tentang masalah yang disebabkan oleh pemasangan tinggi ini, tapi saya tidak setuju dengan penggunaan tutorial yang diperlukan . Anda berkata pada diri sendiri, bahwa Anda tidak pernah benar-benar membutuhkan akses bidang, jadi sayangnya, ini juga tidak sepenuhnya menjawab pertanyaan saya.
Frank
@ Jujur seperti yang Anda lihat, interpretasi saya tentang panduan tutorial (yang tampaknya didukung oleh Java Puzzlers) seperti, gunakan kelas-kelas non-statis hanya ketika Anda dapat membuktikan bahwa ini diperlukan, dan khususnya, membuktikan bahwa cara-cara alternatif lebih buruk . Patut dicatat bahwa dalam pengalaman saya cara-cara alternatif selalu menjadi lebih baik
agas
Itulah intinya. Jika selalu lebih baik, lalu mengapa kita repot-repot?
Frank
@ Jujur jawaban lain memberikan contoh menarik bahwa ini tidak selalu begitu. Per bacaan saya tentang map.keySet javadocs , fitur ini menyarankan kopling ketat dan sebagai hasilnya, membatalkan argumen terhadap kelas non-statis "Set didukung oleh peta, jadi perubahan pada peta tercermin dalam set, dan sebaliknya ... "
nyamuk
1

Beberapa kesalahpahaman:

Kurang kopling: Kami umumnya mendapatkan lebih sedikit kopling, karena kelas [statis dalam] tidak dapat langsung mengakses atribut kelas luarnya.

IIRC - Sebenarnya, bisa. (Tentu saja jika mereka adalah bidang objek, itu tidak akan memiliki objek luar untuk dilihat. Meskipun demikian, jika ia mendapatkan objek seperti itu dari mana saja, maka ia dapat langsung mengakses bidang tersebut).

Kelas Tunggal: Pemuat kelas tidak perlu mengurus kelas baru setiap kali, kami membuat objek dari kelas luar. Kami hanya mendapatkan objek baru untuk kelas yang sama berulang kali.

Saya tidak yakin apa yang Anda maksud di sini. Pemuat kelas hanya terlibat dua kali secara total, satu kali untuk setiap kelas (saat kelas dimuat).


Rambling mengikuti:

Di Javaland kita agak takut membuat kelas. Saya pikir itu harus terasa seperti konsep yang signifikan. Biasanya milik file sendiri. Perlu boilerplate yang signifikan. Perlu perhatian pada desainnya.

Lawan bahasa lain - mis. Scala, atau mungkin C ++: Sama sekali tidak ada drama yang menciptakan tempat kecil (kelas, struct, apa pun) kapan pun dua bit data tersebut tergabung bersama. Aturan akses yang fleksibel membantu Anda dengan mengurangi pelat, memungkinkan Anda menjaga kode terkait secara fisik lebih dekat. Ini mengurangi 'jarak pikiran' Anda di antara dua kelas.

Kita dapat mengabaikan kekhawatiran desain atas akses langsung, dengan menjaga kerahasiaan kelas dalam.

(... Jika Anda bertanya 'mengapa kelas internal publik tidak statis ?' Itu pertanyaan yang sangat bagus - saya rasa saya belum pernah menemukan kasus yang dibenarkan bagi mereka).

Anda dapat menggunakannya kapan saja hal ini membantu pembacaan kode dan menghindari pelanggaran KERING dan boilerplate. Saya tidak memiliki contoh yang siap karena itu tidak sering terjadi dalam pengalaman saya, tetapi itu memang terjadi.


Kelas dalam statis masih merupakan pendekatan standar yang baik , btw. Non-statis memiliki bidang tersembunyi tambahan yang dihasilkan melalui mana kelas batin merujuk ke luar.

Iain
sumber
0

Dapat digunakan untuk membuat pola pabrik. Pola pabrik sering digunakan ketika objek yang dibuat perlu parameter konstruktor tambahan berfungsi, tetapi membosankan untuk menyediakan setiap waktu:

Mempertimbangkan:

public class QueryFactory {
    @Inject private Database database;

    public Query create(String sql) {
        return new Query(database, sql);
    }
}

public class Query {
    private final Database database;
    private final String sql;

    public Query(Database database, String sql) {
        this.database = database;
        this.sql = sql;
    } 

    public List performQuery() {
        return database.query(sql);
    }
}

Digunakan sebagai:

Query q = queryFactory.create("SELECT * FROM employees");

q.performQuery();

Hal yang sama dapat dicapai dengan kelas dalam yang tidak statis:

public class QueryFactory {
    @Inject private Database database;

    public class Query {
        private final String sql;

        public Query(String sql) {
            this.sql = sql;
        }

        public List performQuery() {
            return database.query(sql);
        }
    }
}

Digunakan sebagai:

Query q = queryFactory.new Query("SELECT * FROM employees");

q.performQuery();

Pabrik mendapat manfaat dari memiliki metode yang disebutkan secara khusus, seperti create, createFromSQLatau createFromTemplate. Hal yang sama dapat dilakukan di sini juga dalam bentuk beberapa kelas batin:

public class QueryFactory {

    public class SQL { ... }
    public class Native { ... }
    public class Builder { ... }

}

Lebih sedikit parameter passing yang dibutuhkan dari pabrik ke kelas yang dibangun, tetapi keterbacaan dapat sedikit menderita.

Membandingkan:

Queries.Native q = queries.new Native("select * from employees");

vs.

Query q = queryFactory.createNative("select * from employees");
john16384
sumber