Saya ingin dapat menulis kelas Java dalam satu paket yang dapat mengakses metode non-publik dari kelas dalam paket lain tanpa harus membuatnya menjadi subclass dari kelas lain. Apakah ini mungkin?
Berikut ini adalah trik kecil yang saya gunakan di JAWA untuk mereplikasi mekanisme teman C ++.
Katakanlah saya punya kelas Romeo
dan kelas lain Juliet
. Mereka berada dalam paket yang berbeda (keluarga) karena alasan kebencian.
Romeo
ingin cuddle
Juliet
dan Juliet
ingin hanya membiarkannya Romeo
cuddle
.
Dalam C ++, Juliet
akan menyatakan Romeo
sebagai (kekasih) friend
tetapi tidak ada hal seperti itu di java.
Inilah kelas dan triknya:
Wanita pertama:
package capulet;
import montague.Romeo;
public class Juliet {
public static void cuddle(Romeo.Love love) {
Objects.requireNonNull(love);
System.out.println("O Romeo, Romeo, wherefore art thou Romeo?");
}
}
Jadi metode Juliet.cuddle
adalah public
tetapi Anda perlu Romeo.Love
untuk menyebutnya. Ini menggunakan ini Romeo.Love
sebagai "tanda tangan keamanan" untuk memastikan bahwa hanya Romeo
dapat memanggil metode ini dan memeriksa bahwa cinta itu nyata sehingga runtime akan membuang NullPointerException
jika itu null
.
Sekarang anak laki-laki:
package montague;
import capulet.Juliet;
public class Romeo {
public static final class Love { private Love() {} }
private static final Love love = new Love();
public static void cuddleJuliet() {
Juliet.cuddle(love);
}
}
Kelas Romeo.Love
bersifat publik, tetapi konstruktornya adalah publik private
. Karena itu siapa pun dapat melihatnya, tetapi hanya Romeo
dapat membuatnya. Saya menggunakan referensi statis sehingga Romeo.Love
yang tidak pernah digunakan hanya dibangun sekali dan tidak berdampak optimasi.
Oleh karena itu, Romeo
bisa cuddle
Juliet
dan hanya dia bisa karena hanya dia bisa membangun dan akses sebuah Romeo.Love
contoh, yang dibutuhkan oleh Juliet
untuk cuddle
dia (atau dia akan menampar Anda dengan NullPointerException
).
Romeo
'sLove
untukJulia
kekal dengan mengubahlove
bidang menjadifinal
;-).Para perancang Java secara eksplisit menolak gagasan tentang pertemanan karena ia bekerja di C ++. Anda memasukkan "teman" Anda dalam paket yang sama. Keamanan pribadi, yang dilindungi, dan dikemas diberlakukan sebagai bagian dari desain bahasa.
James Gosling ingin Java menjadi C ++ tanpa kesalahan. Saya percaya dia merasa teman itu kesalahan karena melanggar prinsip OOP. Paket memberikan cara yang masuk akal untuk mengatur komponen tanpa terlalu murni tentang OOP.
NR menunjukkan bahwa Anda bisa curang menggunakan refleksi, tetapi bahkan itu hanya berfungsi jika Anda tidak menggunakan SecurityManager. Jika Anda mengaktifkan keamanan standar Java, Anda tidak akan bisa menipu dengan refleksi kecuali Anda menulis kebijakan keamanan untuk mengizinkannya secara khusus.
sumber
friend
melanggar OOP (khususnya, lebih dari akses paket tidak) maka dia benar-benar tidak memahaminya (sangat mungkin, banyak orang salah paham).Konsep 'teman' berguna di Jawa, misalnya, untuk memisahkan API dari implementasinya. Adalah umum untuk kelas implementasi membutuhkan akses ke internal kelas API tetapi ini tidak boleh diekspos ke klien API. Ini dapat dicapai dengan menggunakan pola 'Friend Accessor' sebagaimana dirinci di bawah ini:
Kelas diekspos melalui API:
Kelas yang menyediakan fungsionalitas 'teman':
Contoh akses dari kelas dalam paket implementasi 'teman':
sumber
Ada dua solusi untuk pertanyaan Anda yang tidak melibatkan menjaga semua kelas dalam paket yang sama.
Yang pertama adalah menggunakan Friend Accessor / Paket Friend yang dijelaskan dalam (Desain API Praktis, Tulach 2008).
Yang kedua adalah menggunakan OSGi. Ada artikel di sini menjelaskan bagaimana OSGi menyelesaikan ini.
Pertanyaan Terkait: 1 , 2 , dan 3 .
sumber
Sejauh yang saya tahu, itu tidak mungkin.
Mungkin, Anda bisa memberi kami beberapa detail tentang desain Anda. Pertanyaan seperti ini kemungkinan merupakan hasil dari kekurangan desain.
Pertimbangkan saja
sumber
Jawaban eirikma mudah dan luar biasa. Saya mungkin menambahkan satu hal lagi: alih-alih memiliki metode yang dapat diakses publik, getFriend () untuk mendapatkan teman yang tidak dapat digunakan, Anda dapat melangkah lebih jauh dan melarang mendapatkan teman tanpa token: getFriend (Service.FriendToken). FriendToken ini akan menjadi kelas publik dalam dengan konstruktor pribadi, sehingga hanya Layanan yang dapat membuat instantiate.
sumber
Berikut adalah contoh kasus penggunaan yang jelas dengan
Friend
kelas yang dapat digunakan kembali . Manfaat dari mekanisme ini adalah kesederhanaan penggunaan. Mungkin bagus untuk memberi kelas unit tes akses yang lebih banyak daripada aplikasi lainnya.Untuk memulai, berikut adalah contoh cara menggunakan
Friend
kelas.Kemudian dalam paket lain Anda dapat melakukan ini:
The
Friend
kelas adalah sebagai berikut.Namun, masalahnya adalah ia bisa disalahgunakan seperti:
Sekarang, mungkin benar bahwa
Other
kelas tidak memiliki konstruktor publik, oleh karena itu membuatAbuser
kode di atas tidak mungkin. Namun, jika kelas Anda memang memiliki konstruktor publik maka mungkin disarankan untuk menduplikasi kelas Teman sebagai kelas dalam. AmbilOther2
kelas ini sebagai contoh:Dan kemudian
Owner2
kelas akan seperti ini:Perhatikan bahwa
Other2.Friend
kelas memiliki konstruktor pribadi, sehingga menjadikan ini cara yang jauh lebih aman untuk melakukannya.sumber
Solusi yang diberikan mungkin bukan yang paling sederhana. Pendekatan lain didasarkan pada ide yang sama seperti di C ++: anggota pribadi tidak dapat diakses di luar paket / ruang lingkup pribadi, kecuali untuk kelas tertentu yang pemiliknya punya teman sendiri.
Kelas yang membutuhkan akses teman ke anggota harus membuat "kelas teman" abstrak publik publik di mana kelas yang memiliki properti tersembunyi dapat mengekspor akses ke, dengan mengembalikan subclass yang menerapkan metode implementasi akses. Metode "API" kelas teman bisa bersifat pribadi sehingga tidak dapat diakses di luar kelas yang membutuhkan akses teman. Satu-satunya pernyataan adalah panggilan ke anggota yang dilindungi abstrak yang mengimplementasikan kelas ekspor.
Berikut kodenya:
Pertama tes yang memverifikasi bahwa ini benar-benar berfungsi:
Kemudian Layanan yang membutuhkan akses teman ke paket pribadi anggota Entity:
Akhirnya: kelas Entity yang menyediakan akses yang ramah ke anggota pribadi paket hanya ke class application.service.Service.
Oke, saya harus akui itu sedikit lebih lama dari "layanan teman :: Layanan;" tetapi dimungkinkan untuk mempersingkat saat mempertahankan waktu kompilasi dengan menggunakan anotasi.
sumber
Di Jawa dimungkinkan untuk memiliki "persahabatan terkait paket". Ini bisa bermanfaat untuk pengujian unit. Jika Anda tidak menentukan pribadi / publik / dilindungi di depan metode, itu akan menjadi "teman dalam paket". Kelas dalam paket yang sama akan dapat mengaksesnya, tetapi akan menjadi pribadi di luar kelas.
Aturan ini tidak selalu diketahui, dan merupakan perkiraan yang baik untuk kata kunci "teman" C ++. Saya merasa ini pengganti yang baik.
sumber
Saya pikir bahwa kelas teman di C ++ seperti konsep kelas dalam di Jawa. Menggunakan kelas-dalam Anda sebenarnya dapat mendefinisikan kelas tertutup dan kelas tertutup. Kelas tertutup memiliki akses penuh ke anggota publik dan pribadi dari kelas terlampir itu. lihat tautan berikut: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
sumber
Saya pikir, pendekatan menggunakan pola pengaksesan teman terlalu rumit. Saya harus menghadapi masalah yang sama dan saya menyelesaikan menggunakan copy constructor yang baik dan lama, yang dikenal dari C ++, di Java:
Dalam aplikasi Anda, Anda dapat menulis kode berikut:
Keuntungan dari metode ini adalah hanya aplikasi Anda yang memiliki akses ke data yang dilindungi. Ini bukan pengganti kata kunci teman. Tapi saya pikir itu sangat cocok ketika Anda menulis perpustakaan khusus dan Anda perlu mengakses data yang dilindungi.
Setiap kali Anda harus berurusan dengan instance ProtectedContainer Anda dapat membungkus ProtectedAccessor Anda di sekitarnya dan Anda mendapatkan akses.
Ini juga bekerja dengan metode yang dilindungi. Anda mendefinisikannya terlindungi di API Anda. Kemudian dalam aplikasi Anda, Anda menulis kelas pembungkus pribadi dan mengekspos metode yang dilindungi sebagai publik. Itu dia.
sumber
ProtectedContainer
dapat disubklasifikasikan di luar paket!Jika Anda ingin mengakses metode yang dilindungi, Anda bisa membuat subkelas dari kelas yang ingin Anda gunakan yang memperlihatkan metode yang ingin Anda gunakan sebagai publik (atau internal ke namespace menjadi lebih aman), dan memiliki instance kelas itu di kelas Anda (gunakan sebagai proxy).
Sejauh menyangkut metode pribadi (saya pikir) Anda kurang beruntung.
sumber
Saya setuju bahwa dalam kebanyakan kasus kata kunci teman tidak perlu.
Dan akhirnya, jika benar-benar diperlukan, ada pola pengakses teman yang disebutkan dalam jawaban lain.
sumber
Tidak menggunakan kata kunci atau lebih.
Anda dapat "menipu" menggunakan refleksi dll, tetapi saya tidak akan merekomendasikan "curang".
sumber
Metode yang saya temukan untuk menyelesaikan masalah ini adalah membuat objek accessor, seperti:
Kode pertama yang memanggil
getAccessor()
"klaim kepemilikan" dari accessor. Biasanya, ini adalah kode yang menciptakan objek.Ini juga memiliki keunggulan dibandingkan mekanisme teman C ++, karena memungkinkan Anda membatasi akses pada level per-instance , dibandingkan dengan level per-kelas . Dengan mengontrol referensi accessor, Anda mengontrol akses ke objek. Anda juga dapat membuat banyak pengakses, dan memberikan akses yang berbeda untuk masing-masingnya, yang memungkinkan kontrol yang baik atas kode apa yang dapat mengakses apa:
Akhirnya, jika Anda ingin sesuatu menjadi sedikit lebih terorganisir, Anda dapat membuat objek referensi, yang menyatukan semuanya. Ini memungkinkan Anda untuk mengklaim semua pengakses dengan satu pemanggilan metode, serta menyatukannya dengan instance tertautnya. Setelah Anda memiliki referensi, Anda dapat membagikan aksesor ke kode yang membutuhkannya:
Setelah banyak memukul-mukul (bukan jenis yang baik), ini adalah solusi terakhir saya, dan saya sangat menyukainya. Ini fleksibel, mudah digunakan, dan memungkinkan kontrol yang sangat baik atas akses kelas. ( Akses dengan referensi saja sangat berguna.) Jika Anda menggunakan protected sebagai ganti private untuk accessor / referensi, subkelas Foo bahkan dapat mengembalikan referensi yang diperluas dari
getReference
. Itu juga tidak memerlukan refleksi apa pun, sehingga dapat digunakan di lingkungan apa pun.sumber
Pada Java 9, modul dapat digunakan untuk menjadikan ini tidak menjadi masalah dalam banyak kasus.
sumber
Saya lebih suka delegasi atau komposisi atau kelas pabrik (tergantung pada masalah yang menyebabkan masalah ini) untuk menghindari menjadikannya kelas publik.
Jika itu adalah masalah "antarmuka / implementasi kelas dalam paket yang berbeda", maka saya akan menggunakan kelas pabrik umum yang akan dalam paket yang sama dengan paket impl dan mencegah pemaparan dari kelas impl.
Jika itu adalah masalah "Saya benci membuat kelas / metode ini hanya untuk menyediakan fungsionalitas ini untuk beberapa kelas lain dalam paket yang berbeda", maka saya akan menggunakan kelas delegasi publik dalam paket yang sama dan hanya mengekspos bagian fungsionalitas itu saja. dibutuhkan oleh kelas "orang luar".
Beberapa keputusan ini didorong oleh arsitektur classloading server target (bundel OSGi, WAR / EAR, dll.), Penerapan dan konvensi penamaan paket. Misalnya, solusi yang diajukan di atas, pola 'Friend Accessor' pintar untuk aplikasi java normal. Saya ingin tahu apakah akan sulit untuk mengimplementasikannya di OSGi karena perbedaan gaya classloading.
sumber
Saya tidak tahu apakah itu berguna bagi siapa pun tetapi saya menanganinya dengan cara berikut:
Saya membuat antarmuka (AdminRights).
Setiap kelas yang seharusnya dapat memanggil fungsi tersebut harus mengimplementasikan AdminRights.
Kemudian saya membuat fungsi HasAdminRights sebagai berikut:
sumber
Saya pernah melihat solusi berbasis refleksi yang melakukan "teman memeriksa" saat runtime menggunakan refleksi dan memeriksa tumpukan panggilan untuk melihat apakah kelas yang memanggil metode diizinkan untuk melakukannya. Menjadi cek runtime, ia memiliki kelemahan yang jelas.
sumber