Saya memiliki kelas di mana setiap metode dimulai dengan cara yang sama:
class Foo {
public void bar() {
if (!fooIsEnabled) return;
//...
}
public void baz() {
if (!fooIsEnabled) return;
//...
}
public void bat() {
if (!fooIsEnabled) return;
//...
}
}
Apakah ada cara yang bagus untuk meminta (dan semoga tidak menulis setiap waktu) fooIsEnabled
bagian untuk setiap metode publik di kelas?
java
design-patterns
kristina
sumber
sumber
Jawaban:
Saya tidak tahu tentang elegan, tapi di sini ada implementasi yang bekerja menggunakan Java built-in
java.lang.reflect.Proxy
yang memberlakukan bahwa semua pemanggilan metodeFoo
dimulai dengan memeriksaenabled
keadaan.main
metode:Foo
antarmuka:FooFactory
kelas:Seperti yang telah ditunjukkan oleh orang lain, sepertinya dibutuhkan terlalu banyak untuk apa yang Anda butuhkan jika Anda hanya memiliki sedikit metode untuk dikhawatirkan.
Yang mengatakan, pasti ada manfaatnya:
Foo
implementasi metode tidak perlu khawatir tentang masalahenabled
lintas sektoral. Sebaliknya, kode metode hanya perlu khawatir tentang apa tujuan utama metode ini, tidak lebih.Foo
kelas dan keliru "lupa" untuk menambahkanenabled
cek. Theenabled
perilaku cek secara otomatis diwarisi oleh metode baru ditambahkan.enabled
pemeriksaan, sangat mudah untuk melakukannya dengan aman dan di satu tempat.Spring
, meskipun mereka pasti bisa menjadi pilihan yang baik juga.Agar adil, beberapa kerugiannya adalah:
FooImpl
kelas adalah jelek.Foo
, Anda harus membuat perubahan dalam 2 titik: kelas implementasi dan antarmuka. Bukan masalah besar, tapi masih sedikit lebih banyak pekerjaan.EDIT:
Komentar Fabian Streitel membuat saya berpikir tentang 2 gangguan dengan solusi saya di atas yang, saya akui, saya tidak senang dengan diri saya sendiri:
Untuk menyelesaikan poin # 1, dan untuk setidaknya meringankan masalah dengan poin # 2, saya akan membuat anotasi
BypassCheck
(atau yang serupa) yang bisa saya gunakan untuk menandai metode diFoo
antarmuka yang saya tidak ingin melakukan " mengaktifkan cek ". Dengan cara ini, saya tidak memerlukan string sihir sama sekali, dan itu menjadi jauh lebih mudah bagi pengembang untuk menambahkan metode baru dengan benar dalam kasus khusus ini.Menggunakan solusi anotasi, kode akan terlihat seperti ini:
main
metode:BypassCheck
anotasi:Foo
antarmuka:FooFactory
kelas:sumber
Ada banyak saran bagus .. apa yang dapat Anda lakukan untuk mengatasi masalah Anda adalah berpikir dalam Pola Negara dan menerapkannya.
Lihatlah potongan kode ini .. mungkin ini akan membuat Anda mendapatkan ide. Dalam skenario ini sepertinya Anda ingin memodifikasi seluruh metode implementasi berdasarkan keadaan internal objek. Harap diingat bahwa jumlah metode dalam objek diketahui sebagai perilaku.
Harap Anda menyukainya!
PD: Apakah implementasi Pola Negara (juga dikenal sebagai Strategi tergantung pada konteks .. tetapi prinsip-prinsipnya sama).
sumber
bar()
dalamFooEnabledBehavior
danbar()
dalamFooDisabledBehavior
mungkin berbagi banyak kode yang sama, dengan bahkan mungkin satu baris berbeda di antara keduanya. Anda bisa dengan sangat mudah, terutama jika kode ini dipertahankan oleh junior devs (seperti saya), berakhir dengan kekacauan besar yang tidak dapat dipertahankan dan tidak dapat diuji. Itu bisa terjadi dengan kode apa pun, tetapi ini kelihatannya sangat mudah untuk gagal sangat cepat. +1, karena saran yang bagus.Ya, tapi ini sedikit pekerjaan, jadi itu tergantung seberapa penting bagi Anda.
Anda dapat mendefinisikan kelas sebagai antarmuka, menulis implementasi delegasi, dan kemudian menggunakan
java.lang.reflect.Proxy
untuk mengimplementasikan antarmuka dengan metode yang melakukan bagian bersama dan kemudian memanggil delegate dengan syarat.Anda
MyInvocationHandler
dapat terlihat seperti ini (penanganan kesalahan dan perancah kelas dihilangkan, dengan asumsifooIsEnabled
didefinisikan di suatu tempat yang dapat diakses):Tidak terlalu cantik. Tetapi tidak seperti berbagai komentator, saya akan melakukannya, karena saya pikir pengulangan adalah risiko yang lebih penting daripada kepadatan jenis ini, dan Anda akan dapat menghasilkan "rasa" kelas nyata Anda, dengan pembungkus yang agak sulit dipahami ini ditambahkan pada sangat lokal hanya dalam beberapa baris kode.
Lihat dokumentasi Java untuk detail tentang kelas proxy dinamis.
sumber
Pertanyaan ini terkait erat dengan pemrograman berorientasi aspek . AspectJ adalah ekstensi AOP dari Java dan Anda dapat melihatnya untuk mendapatkan beberapa ispirasi.
Sejauh yang saya tahu tidak ada dukungan langsung untuk AOP di Jawa. Ada beberapa pola GOF yang terkait dengannya, seperti misalnya Metode dan Strategi Templat tetapi tidak akan benar-benar menyelamatkan Anda dari baris kode.
Di Jawa dan sebagian besar bahasa lain, Anda dapat menentukan logika berulang yang Anda butuhkan dalam fungsi dan mengadopsi apa yang disebut pendekatan pengkodean disiplin di mana Anda memanggilnya pada waktu yang tepat.
Namun ini tidak sesuai dengan kasus Anda karena Anda ingin kode yang difaktorkan keluar untuk dapat kembali
checkBalance
. Dalam bahasa yang mendukung makro (seperti C / C ++) Anda bisa mendefinisikancheckSomePrecondition
dancheckSomePostcondition
sebagai makro dan mereka hanya akan diganti oleh preprocessor sebelum kompiler bahkan dipanggil:Java tidak memiliki ini di luar kotak. Ini mungkin menyinggung seseorang, tetapi saya memang menggunakan pembuatan kode otomatis dan mesin templat untuk mengotomatiskan tugas pengkodean berulang di masa lalu. Jika Anda memproses file Java Anda sebelum mengompilasinya dengan preprocessor yang sesuai, misalnya Jinja2, Anda bisa melakukan sesuatu yang mirip dengan apa yang mungkin dalam C.
Kemungkinan pendekatan Java murni
Jika Anda mencari solusi Java murni, apa yang Anda temukan mungkin tidak akan ringkas. Tapi, itu masih bisa memfaktorkan bagian umum dari program Anda dan menghindari duplikasi kode dan bug. Anda dapat melakukan sesuatu seperti ini (ini semacam strategi yang terinspirasi pola). Perhatikan bahwa dalam C # dan Java 8, dan dalam bahasa lain yang fungsinya sedikit lebih mudah ditangani, pendekatan ini mungkin terlihat bagus.
Agak skenario dunia nyata
Anda mengembangkan kelas untuk mengirim bingkai data ke robot industri. Robot membutuhkan waktu untuk menyelesaikan suatu perintah. Setelah perintah selesai, ia mengirimkan Anda kembali bingkai kontrol. Robot dapat rusak jika menerima perintah baru ketika sebelumnya masih dieksekusi. Program Anda menggunakan
DataLink
kelas untuk mengirim dan menerima bingkai ke dan dari robot. Anda perlu melindungi akses keDataLink
instance.Panggilan benang antarmuka pengguna
RobotController.left
,right
,up
ataudown
ketika pengguna mengklik tombol, tetapi juga panggilanBaseController.tick
secara berkala, untuk perintah forwarding mengaktifkan kembali ke pribadiDataLink
misalnya.sumber
protect
yang lebih mudah untuk digunakan kembali dan didokumentasikan. Jika Anda memberi tahu pengelola masa depan bahwa kode penting harus dilindungiprotect
, Anda sudah memberi tahu dia apa yang harus dilakukan. Jika aturan perlindungan berubah, kode baru masih dilindungi. Ini persis alasan di balik mendefinisikan fungsi tetapi OP perlu "mengembalikanreturn
" yang fungsi tidak bisa lakukan.Saya akan mempertimbangkan refactoring. Pola ini sangat merusak pola KERING (Jangan ulangi sendiri). Saya percaya ini melanggar tanggung jawab kelas ini. Tetapi ini tergantung pada kendali Anda terhadap kode. Pertanyaan Anda sangat terbuka - di mana Anda menelepon
Foo
contoh?Saya kira Anda memiliki kode seperti
mungkin Anda harus menyebutnya seperti ini:
Dan tetap bersih. Misalnya, masuk:
a
logger
tidak bertanya pada dirinya sendiri , apakah debug diaktifkan. Itu hanya log.Sebagai gantinya, kelas panggilan perlu memeriksa ini:
Jika ini adalah perpustakaan dan Anda tidak dapat mengontrol panggilan kelas ini, lempar
IllegalStateException
yang menjelaskan mengapa, jika panggilan ini ilegal dan menyebabkan masalah.sumber
IMHO solusi paling elegan dan berkinerja terbaik untuk ini adalah memiliki lebih dari satu implementasi Foo, bersama dengan metode pabrik untuk membuat satu:
Atau variasi:
Seperti yang ditunjukkan oleh sstan, lebih baik menggunakan antarmuka:
Adaptasikan ini dengan keadaan Anda yang lain (apakah Anda membuat Foo baru setiap kali atau menggunakan kembali contoh yang sama, dll.)
sumber
Foo
, dan lupa untuk menambahkan versi no-op dari metode dalam kelas yang diperluas, sehingga mem-bypass perilaku yang diinginkan.isFooEnabled
di InstansiasiFoo
. Masih terlalu dini. Dalam kode asli, ini dilakukan ketika suatu metode dijalankan. NilaiisFooEnabled
dapat berubah sementara itu.fooIsEnabled
mungkin konstan. Tetapi tidak ada yang memberitahu kita bahwa itu konstan. Jadi saya mempertimbangkan kasus umum.Saya punya pendekatan lain: punya a
}
dan kemudian Anda bisa melakukannya
Mungkin Anda bahkan dapat mengganti
isFooEnabled
denganFoo
variabel yangFooImpl
dapat digunakan untuk atauNullFoo.DEFAULT
. Maka panggilan itu lebih sederhana lagi:BTW, ini disebut "pola Null".
sumber
(isFooEnabled ? foo : NullFoo.DEFAULT).bar();
itu agak canggung. Miliki implementasi ketiga yang mendelegasikan ke salah satu implementasi yang ada. Alih-alih mengubah nilai bidangisFooEnabled
, target delegasi dapat diubah. Ini mengurangi jumlah cabang dalam kodeFoo
dalam kode panggilan! Bagaimana kita bisa tahuisFooEnabled
? Ini adalah bidang internal di kelasFoo
.Dalam pendekatan fungsional yang serupa dengan jawaban @ Colin, dengan fungsi lambda Java 8 , dimungkinkan untuk membungkus fitur bersyarat beralih mengaktifkan / menonaktifkan kode ke metode penjaga (
executeIfEnabled
) yang menerima tindakan lambda, yang kode yang akan dijalankan secara kondisional dapat berlalu.Meskipun dalam kasus Anda, pendekatan ini tidak akan menyimpan baris kode apa pun, dengan MENINGGALKAN ini, Anda sekarang memiliki opsi untuk memusatkan perhatian fitur beralih lainnya, ditambah AOP atau masalah debugging seperti pencatatan, diagnostik, pembuatan profil, dkk.
Salah satu manfaat menggunakan lambdas di sini adalah bahwa penutupan dapat digunakan untuk menghindari perlunya membebani
executeIfEnabled
metode.Sebagai contoh:
Dengan tes:
sumber
Seperti yang ditunjukkan dalam jawaban lain, Pola Desain Strategi adalah pola desain yang tepat untuk diikuti untuk menyederhanakan kode ini. Saya telah mengilustrasikannya di sini menggunakan metode doa melalui refleksi, tetapi ada sejumlah mekanisme yang dapat Anda gunakan untuk mendapatkan efek yang sama.
sumber
Proxy
.foo.fooIsEnabled ...
? A priori, ini adalah bidang internal objek, kita tidak bisa, dan tidak mau, melihatnya di luar.Kalau saja java sedikit lebih baik dalam fungsional. Menurutnya solusi yang paling OOO adalah membuat kelas yang membungkus fungsi tunggal sehingga disebut hanya ketika foo diaktifkan.
dan kemudian mengimplementasikan
bar
,baz
danbat
sebagai kelas anonim yang diperluasFunctionWrapper
.Ide lain
Gunakan solusi nullgl glglgl tetapi kelas make
FooImpl
danNullFoo
kelas dalam (dengan konstruktor pribadi) dari kelas di bawah ini:dengan cara ini Anda tidak perlu khawatir mengingat untuk digunakan
(isFooEnabled ? foo : NullFoo.DEFAULT)
.sumber
Foo foo = new Foo()
untuk meneleponbar
Anda akan menulisfoo.bar.call()
Sepertinya kelas tidak melakukan apa-apa ketika Foo tidak diaktifkan jadi mengapa tidak mengungkapkan ini pada tingkat yang lebih tinggi di mana Anda membuat atau mendapatkan contoh Foo?
Ini hanya berfungsi jika isFooEnabled adalah konstanta. Secara umum, Anda dapat membuat anotasi sendiri.
sumber
fooIsEnabled
suatu metode dipanggil. Anda melakukan ini sebelum instantiasi dariFoo
. Masih terlalu dini. Nilai dapat berubah sementara itu.isFooEnabled
adalah bidang contohFoo
objek.Saya tidak terbiasa dengan sintaksis Java. Asumsi bahwa di Jawa, ada polimorfisme, properti statis, kelas abstrak & metode:
sumber
new bar()
seharusnya berarti?Name
dengan huruf kapital.bar()
. Jika Anda perlu mengubahnya, Anda akan menemui ajal.Pada dasarnya Anda memiliki bendera yang jika diatur, panggilan fungsi harus dilewati. Jadi saya pikir solusi saya akan konyol, tetapi ini dia.
Berikut ini adalah implementasi dari Proxy sederhana, jika Anda ingin menjalankan beberapa kode sebelum menjalankan fungsi apa pun.
sumber
isEnabled()
. A priori, diaktifkan adalah memasak internalFoo
, tidak terbuka.Ada solusi lain, menggunakan delegate (pointer to function). Anda dapat memiliki metode unik yang pertama melakukan validasi dan kemudian memanggil metode yang relevan sesuai dengan fungsi (parameter) untuk dipanggil. Kode C #:
sumber