Apa idiom "Jalankan Sekitar" ini (atau serupa) yang telah saya dengar? Mengapa saya mungkin menggunakannya, dan mengapa saya tidak ingin menggunakannya?
Apa idiom "Jalankan Sekitar" ini (atau serupa) yang telah saya dengar? Mengapa saya mungkin menggunakannya, dan mengapa saya tidak ingin menggunakannya?
Jawaban:
Pada dasarnya itu adalah pola di mana Anda menulis metode untuk melakukan hal-hal yang selalu diperlukan, misalnya alokasi dan pembersihan sumber daya, dan membuat penelepon menyampaikan "apa yang ingin kita lakukan dengan sumber daya". Sebagai contoh:
public interface InputStreamAction
{
void useStream(InputStream stream) throws IOException;
}
// Somewhere else
public void executeWithFile(String filename, InputStreamAction action)
throws IOException
{
InputStream stream = new FileInputStream(filename);
try {
action.useStream(stream);
} finally {
stream.close();
}
}
// Calling it
executeWithFile("filename.txt", new InputStreamAction()
{
public void useStream(InputStream stream) throws IOException
{
// Code to use the stream goes here
}
});
// Calling it with Java 8 Lambda Expression:
executeWithFile("filename.txt", s -> System.out.println(s.read()));
// Or with Java 8 Method reference:
executeWithFile("filename.txt", ClassName::methodName);
Kode panggilan tidak perlu khawatir tentang sisi terbuka / bersih-bersih - itu akan ditangani oleh executeWithFile
.
Ini terus terang menyakitkan di Jawa karena penutupan begitu bertele-tele, dimulai dengan Java 8 ekspresi lambda dapat diimplementasikan seperti dalam banyak bahasa lain (misalnya ekspresi C # lambda, atau Groovy), dan kasus khusus ini ditangani sejak Java 7 dengan try-with-resources
dan AutoClosable
stream.
Meskipun "mengalokasikan dan membersihkan" adalah contoh khas yang diberikan, ada banyak contoh lain yang mungkin - penanganan transaksi, pencatatan, pelaksanaan beberapa kode dengan lebih banyak keistimewaan, dll. Ini pada dasarnya sedikit seperti pola metode templat tetapi tanpa pewarisan.
Ungkapan Execute Around digunakan ketika Anda harus melakukan sesuatu seperti ini:
//... chunk of init/preparation code ...
task A
//... chunk of cleanup/finishing code ...
//... chunk of identical init/preparation code ...
task B
//... chunk of identical cleanup/finishing code ...
//... chunk of identical init/preparation code ...
task C
//... chunk of identical cleanup/finishing code ...
//... and so on.
Untuk menghindari pengulangan semua kode mubazir ini yang selalu dieksekusi "sekitar" tugas aktual Anda, Anda akan membuat kelas yang mengatasinya secara otomatis:
//pseudo-code:
class DoTask()
{
do(task T)
{
// .. chunk of prep code
// execute task T
// .. chunk of cleanup code
}
};
DoTask.do(task A)
DoTask.do(task B)
DoTask.do(task C)
Idiom ini memindahkan semua kode berlebihan yang rumit ke satu tempat, dan membuat program utama Anda jauh lebih mudah dibaca (dan dapat dipelihara!)
Lihatlah posting ini untuk contoh C #, dan artikel ini untuk contoh C ++.
Metode Execute Around adalah tempat Anda memberikan kode arbitrer ke suatu metode, yang dapat melakukan setup dan / atau kode teardown dan menjalankan kode Anda di antaranya.
Java bukan bahasa yang saya pilih untuk melakukan ini. Ini lebih gaya untuk melewati penutupan (atau ekspresi lambda) sebagai argumen. Padahal benda bisa dibilang setara dengan penutupan .
Bagi saya, metode Execute Around adalah semacam Inversion of Control (Dependency Injection) yang dapat Anda ubah ad hoc, setiap kali Anda memanggil metode tersebut.
Tapi itu juga bisa diartikan sebagai contoh Kopling Kontrol (menceritakan metode apa yang harus dilakukan dengan argumennya, secara harfiah dalam kasus ini).
Saya melihat Anda memiliki tag Java di sini jadi saya akan menggunakan Java sebagai contoh meskipun polanya tidak spesifik platform.
Idenya adalah bahwa kadang-kadang Anda memiliki kode yang selalu melibatkan boilerplate yang sama sebelum Anda menjalankan kode dan setelah Anda menjalankan kode. Contoh yang baik adalah JDBC. Anda selalu mengambil koneksi dan membuat pernyataan (atau pernyataan yang disiapkan) sebelum menjalankan kueri yang sebenarnya dan memproses set hasil, dan kemudian Anda selalu melakukan pembersihan boilerplate yang sama di akhir - menutup pernyataan dan koneksi.
Gagasan dengan mengeksekusi sekitar adalah lebih baik jika Anda dapat memperhitungkan kode boilerplate. Itu menghemat beberapa mengetik, tetapi alasannya lebih dalam. Ini adalah prinsip jangan-ulangi-sendiri (KERING) di sini - Anda mengisolasi kode ke satu lokasi jadi jika ada bug atau Anda perlu mengubahnya, atau Anda hanya ingin memahaminya, semuanya ada di satu tempat.
Hal yang agak sulit dengan jenis faktor-keluar adalah bahwa Anda memiliki referensi bahwa bagian "sebelum" dan "setelah" perlu dilihat. Dalam contoh JDBC ini akan mencakup Koneksi dan Pernyataan (Disiapkan). Jadi untuk menangani itu Anda pada dasarnya "membungkus" kode target Anda dengan kode boilerplate.
Anda mungkin terbiasa dengan beberapa kasus umum di Jawa. Salah satunya adalah filter servlet. Lain adalah AOP sekitar saran. Yang ketiga adalah berbagai kelas xxxTemplate di Spring. Dalam setiap kasus Anda memiliki beberapa objek pembungkus di mana kode "menarik" Anda (katakanlah permintaan JDBC dan pemrosesan kumpulan hasil) disuntikkan. Objek wrapper melakukan bagian "sebelum", memanggil kode yang menarik dan kemudian melakukan bagian "setelah".
Lihat juga Code Sandwiches , yang mensurvei konstruksi ini di banyak bahasa pemrograman dan menawarkan beberapa ide riset yang menarik. Mengenai pertanyaan spesifik mengapa seseorang dapat menggunakannya, makalah di atas menawarkan beberapa contoh nyata:
Situasi seperti itu muncul setiap kali suatu program memanipulasi sumber daya bersama. API untuk kunci, soket, file, atau koneksi database mungkin memerlukan program untuk secara eksplisit menutup atau melepaskan sumber daya yang sebelumnya diperoleh. Dalam bahasa tanpa pengumpulan sampah, programmer bertanggung jawab untuk mengalokasikan memori sebelum digunakan dan melepaskannya setelah digunakan. Secara umum, berbagai tugas pemrograman memerlukan program untuk membuat perubahan, beroperasi dalam konteks perubahan itu, dan kemudian membatalkan perubahan. Kami menyebutnya sandwich kode situasi seperti itu.
Dan kemudian:
Sandwich kode muncul dalam banyak situasi pemrograman. Beberapa contoh umum berkaitan dengan perolehan dan pelepasan sumber daya yang langka, seperti kunci, deskriptor file, atau koneksi soket. Dalam kasus yang lebih umum, perubahan sementara dari status program mungkin memerlukan sandwich kode. Misalnya, program berbasis GUI untuk sementara waktu mengabaikan input pengguna, atau kernel OS dapat menonaktifkan sementara gangguan perangkat keras. Kegagalan untuk mengembalikan keadaan sebelumnya dalam kasus ini akan menyebabkan bug serius.
Makalah ini tidak mengeksplorasi mengapa tidak menggunakan idiom ini, tetapi ia menjelaskan mengapa idiom mudah salah tanpa bantuan tingkat bahasa:
Sandwich kode yang rusak muncul paling sering di hadapan pengecualian dan aliran kontrol tak terlihat yang terkait. Memang, fitur bahasa khusus untuk mengelola sandwich kode muncul terutama dalam bahasa yang mendukung pengecualian.
Namun, pengecualian bukan satu-satunya penyebab sandwich kode yang rusak. Setiap kali perubahan dibuat ke kode tubuh , jalur kontrol baru dapat muncul yang mem-bypass kode setelah . Dalam kasus yang paling sederhana, pengelola hanya perlu menambahkan
return
pernyataan untuk sandwich ini tubuh untuk memperkenalkan cacat baru, yang dapat menyebabkan kesalahan diam. Ketika kode tubuh besar dan sebelum dan sesudah dipisahkan secara luas, kesalahan semacam itu bisa sulit dideteksi secara visual.
Saya akan mencoba menjelaskan, seperti yang saya lakukan pada anak berusia empat tahun:
Contoh 1
Santa datang ke kota. Elf-nya mengkode apa pun yang mereka inginkan di belakangnya, dan kecuali mereka mengubah beberapa hal akan menjadi sedikit berulang:
Atau ini:
.... ad mual satu juta kali dengan sejuta hadiah berbeda: perhatikan bahwa satu-satunya hal yang berbeda adalah langkah 2. Jika langkah kedua adalah satu-satunya hal yang berbeda, maka mengapa Santa menduplikasi kode, yaitu mengapa ia menggandakan langkah 1 dan 3 satu juta kali? Sejuta hadiah berarti bahwa ia mengulangi langkah 1 dan 3 juta kali dengan sia-sia.
Menjalankan sekitar membantu untuk memecahkan masalah itu. dan membantu menghilangkan kode. Langkah 1 dan 3 pada dasarnya konstan, memungkinkan langkah 2 menjadi satu-satunya bagian yang berubah.
Contoh # 2
Jika Anda masih belum mendapatkannya, berikut adalah contoh lain: pikirkan sandwhich: roti di luar selalu sama, tetapi apa yang ada di dalam berubah tergantung pada jenis sand yang Anda pilih (.eg ham, keju, selai, selai kacang dll). Roti selalu ada di luar dan Anda tidak perlu mengulanginya satu miliar kali untuk setiap jenis pasir yang Anda buat.
Sekarang jika Anda membaca penjelasan di atas, mungkin Anda akan merasa lebih mudah untuk mengerti. Saya harap penjelasan ini membantu Anda.
Ini mengingatkan saya pada pola desain strategi . Perhatikan bahwa tautan yang saya tunjuk menyertakan kode Java untuk polanya.
Jelas seseorang dapat melakukan "Jalankan Sekitar" dengan membuat kode inisialisasi dan pembersihan dan hanya meneruskan strategi, yang kemudian akan selalu dibungkus dengan kode inisialisasi dan pembersihan.
Seperti halnya teknik yang digunakan untuk mengurangi pengulangan kode, Anda tidak boleh menggunakannya sampai Anda memiliki setidaknya 2 kasus di mana Anda membutuhkannya, bahkan mungkin 3 (a la the YAGNI principle). Ingatlah bahwa pengulangan kode yang dihapus mengurangi pemeliharaan (lebih sedikit salinan kode berarti lebih sedikit waktu yang dihabiskan untuk memperbaiki salinan di setiap salinan), tetapi juga meningkatkan pemeliharaan (lebih banyak kode total). Jadi, biaya dari trik ini adalah Anda menambahkan lebih banyak kode.
Jenis teknik ini berguna untuk lebih dari sekedar inisialisasi dan pembersihan. Ini juga baik untuk ketika Anda ingin membuatnya lebih mudah untuk memanggil fungsi Anda (misalnya Anda bisa menggunakannya dalam wizard sehingga tombol "berikutnya" dan "sebelumnya" tidak perlu pernyataan kasus raksasa untuk memutuskan apa yang harus dilakukan untuk pergi ke halaman berikutnya / sebelumnya.
Jika Anda ingin idiom asyik, ini dia:
//-- the target class
class Resource {
def open () { // sensitive operation }
def close () { // sensitive operation }
//-- target method
def doWork() { println "working";} }
//-- the execute around code
def static use (closure) {
def res = new Resource();
try {
res.open();
closure(res)
} finally {
res.close();
}
}
//-- using the code
Resource.use { res -> res.doWork(); }