Tip Java 68: Pelajari cara mengimplementasikan pola Command di Java

Pola desain tidak hanya mempercepat fase desain proyek berorientasi objek (OO) tetapi juga meningkatkan produktivitas tim pengembangan dan kualitas perangkat lunak. Sebuah pola Command adalah pola perilaku objek yang memungkinkan kita untuk mencapai decoupling lengkap antara pengirim dan penerima. ( Pengirim adalah objek yang memanggil operasi, dan penerima adalah objek yang menerima permintaan untuk menjalankan operasi tertentu. Dengan decoupling, pengirim tidak memiliki pengetahuan tentang Receiverantarmuka.) Istilah permintaandi sini mengacu pada perintah yang akan dijalankan. Pola Perintah juga memungkinkan kita untuk mengubah kapan dan bagaimana permintaan dipenuhi. Oleh karena itu, pola Perintah memberi kita fleksibilitas dan juga ekstensibilitas.

Dalam bahasa pemrograman seperti C, pointer fungsi digunakan untuk menghilangkan pernyataan saklar raksasa. (Lihat "Tip 30 Java: Polimorfisme dan Java" untuk penjelasan lebih rinci.) Karena Java tidak memiliki penunjuk fungsi, kita dapat menggunakan pola Perintah untuk mengimplementasikan callback. Anda akan melihat ini beraksi pada contoh kode pertama di bawah ini, yang disebut TestCommand.java.

Pengembang yang terbiasa menggunakan pointer fungsi dalam bahasa lain mungkin tergoda untuk menggunakan Methodobjek Reflection API dengan cara yang sama. Misalnya, dalam artikelnya "Java Reflection," Paul Tremblett menunjukkan kepada Anda bagaimana menggunakan Refleksi untuk mengimplementasikan transaksi tanpa menggunakan pernyataan switch. Saya telah menolak godaan ini, karena Sun menyarankan agar tidak menggunakan API Refleksi ketika alat lain yang lebih alami untuk bahasa pemrograman Java sudah cukup. (Lihat Sumber untuk link ke artikel Tremblett dan untuk halaman tutorial Sun's Reflection.) Program Anda akan lebih mudah untuk di-debug dan dipelihara jika Anda tidak menggunakan Methodobjek. Sebaliknya, Anda harus menentukan antarmuka dan mengimplementasikannya di kelas yang melakukan tindakan yang diperlukan.

Oleh karena itu, saya sarankan Anda menggunakan pola Perintah yang dikombinasikan dengan mekanisme pengikatan dan pemuatan dinamis Java untuk mengimplementasikan pointer fungsi. (Untuk detail tentang mekanisme pengikatan dan pemuatan dinamis Java, lihat "The Java Language Environment - A White Paper" karya James Gosling dan Henry McGilton, yang tercantum di Resources.)

Dengan mengikuti saran di atas, kami mengeksploitasi polimorfisme yang disediakan oleh penerapan pola Perintah untuk menghilangkan pernyataan sakelar raksasa, menghasilkan sistem yang dapat diperluas. Kami juga memanfaatkan mekanisme pemuatan dan pengikatan dinamis unik Java untuk membangun sistem yang dinamis dan dapat diperluas secara dinamis. Ini diilustrasikan dalam contoh contoh kode kedua di bawah ini, yang disebut TestTransactionCommand.java.

Pola Perintah mengubah permintaan itu sendiri menjadi sebuah objek. Objek ini dapat disimpan dan diedarkan seperti objek lainnya. Kunci dari pola ini adalah Commandantarmuka, yang mendeklarasikan antarmuka untuk menjalankan operasi. Dalam bentuknya yang paling sederhana, antarmuka ini menyertakan executeoperasi abstrak . Setiap Commandkelas konkret menentukan pasangan penerima-tindakan dengan menyimpan Receiversebagai variabel instan. Ini menyediakan implementasi yang berbeda dari execute()metode untuk memanggil permintaan. The Receivermemiliki pengetahuan yang diperlukan untuk melaksanakan permintaan tersebut.

Gambar 1 di bawah ini menunjukkan Switch- agregasi Commandobjek. Ini memiliki flipUp()dan flipDown()operasi di antarmuka. Switchdisebut invoker karena menjalankan operasi eksekusi di antarmuka perintah.

Perintah konkret LightOnCommand,, mengimplementasikan executepengoperasian antarmuka perintah. Ia memiliki pengetahuan untuk memanggil Receiveroperasi objek yang sesuai . Ini bertindak sebagai adaptor dalam kasus ini. Dengan istilah adaptor, maksud saya bahwa Commandbenda beton adalah konektor sederhana, menghubungkan Invokerdan Receiverdengan antarmuka yang berbeda.

Klien membuat instance Invoker, the Receiver, dan objek perintah konkret.

Gambar 2, diagram sekuens, menunjukkan interaksi antar objek. Ini menggambarkan bagaimana Commandmemisahkan Invokerdari Receiver(dan permintaan yang dilakukan). Klien membuat perintah konkret dengan membuat parameter konstruktornya dengan yang sesuai Receiver. Kemudian menyimpannya Commanddi Invoker. The Invokerpanggilan kembali perintah beton, yang memiliki pengetahuan untuk melakukan yang diinginkan Action()operasi.

Klien (program utama dalam daftar) membuat Commandobjek konkret dan menyetelnya Receiver. Sebagai Invokerbenda, Switchmenyimpan Commandbenda beton . The Invokermengeluarkan permintaan dengan menelepon executepada Commandobjek. CommandObjek konkret memanggil operasi di atasnya Receiveruntuk melaksanakan permintaan tersebut.

Ide kuncinya di sini adalah bahwa perintah konkret mendaftarkan dirinya dengan Invokerdan Invokermemanggilnya kembali, mengeksekusi perintah pada Receiver.

Kode contoh pola perintah

Mari kita lihat contoh sederhana yang mengilustrasikan mekanisme callback yang dicapai melalui pola Command.

Contoh menunjukkan a Fandan a Light. Tujuan kami adalah untuk mengembangkan Switchyang dapat mengaktifkan atau menonaktifkan objek. Kita melihat bahwa Fandan Lightmemiliki antarmuka yang berbeda, yang berarti Switchharus independen dari Receiverantarmuka atau tidak memiliki pengetahuan tentang kode> Antarmuka penerima. Untuk mengatasi masalah ini, kita perlu melakukan parameterisasi masing-masing Switchs dengan perintah yang sesuai. Jelas, Switchterhubung ke Lightakan memiliki perintah yang berbeda dari yang Switchterhubung ke Fan. The Commandkelas harus abstrak atau interface untuk ini bekerja.

Ketika konstruktor untuk a Switchdipanggil, ia diparameterisasi dengan set perintah yang sesuai. Perintah akan disimpan sebagai variabel privat dari Switch.

Ketika operasi flipUp()dan flipDown()dipanggil, mereka hanya akan membuat perintah yang sesuai untuk execute( ). The Switchakan tidak tahu apa yang terjadi sebagai akibat dari execute( )yang disebut.

Kelas TestCommand.java Fan {public void startRotate () {System.out.println ("Fan sedang berputar"); } public void stopRotate () {System.out.println ("Fan tidak berputar"); }} kelas Lampu {public void turnOn () {System.out.println ("Lampu menyala"); } public void turnOff () {System.out.println ("Lampu mati"); }} Saklar kelas {Private Command UpCommand, DownCommand; Sakelar publik (Perintah Atas, Perintah Bawah) {UpCommand = Atas; // Perintah konkret mendaftar sendiri dengan invoker DownCommand = Down; } void flipUp () {// invoker memanggil kembali Command konkret, yang menjalankan Command pada penerima UpCommand. mengeksekusi (); } void flipDown () {DownCommand. mengeksekusi (); }} kelas LightOnCommand mengimplementasikan Command {private Light myLight; publik LightOnCommand (Light L) {myLight = L;} public void execute () {myLight. nyalakan( ); }} kelas LightOffCommand mengimplementasikan Command {private Light myLight; publik LightOffCommand (Light L) {myLight = L; } public void execute () {myLight. matikan( ); }} kelas FanOnCommand mengimplementasikan Command {private Fan myFan; publik FanOnCommand (Fan F) {myFan = F; } public void execute () {myFan. startRotate (); }} kelas FanOffCommand mengimplementasikan Command {private Fan myFan; publik FanOffCommand (Fan F) {myFan = F; } public void execute () {myFan. stopRotate (); }} kelas publik TestCommand {public static void main (String [] args) {Light testLight = new Light (); LightOnCommand testLOC = LightOnCommand baru (testLight); LightOffCommand testLFC = LightOffCommand baru (testLight); Ganti testSwitch = Saklar baru (testLOC, testLFC); testSwitch.flipUp (); testSwitch.flipDown ();Fan testFan = Fan baru (); FanOnCommand foc = FanOnCommand baru (testFan); FanOffCommand ffc = FanOffCommand baru (testFan); Sakelar ts = Sakelar baru (foc, ffc); ts.flipUp (); ts.flipDown (); }} Command.java antarmuka publik Perintah {public abstract void execute (); }

Perhatikan dalam contoh kode di atas bahwa pola Command benar-benar memisahkan objek yang memanggil operasi - (Switch )- dari orang yang memiliki pengetahuan untuk melakukannya - Lightdan Fan. Ini memberi kita banyak fleksibilitas: objek yang mengeluarkan permintaan harus tahu hanya bagaimana mengeluarkannya; tidak perlu mengetahui bagaimana permintaan akan dijalankan.

Pola perintah untuk mengimplementasikan transaksi

Pola Perintah juga dikenal sebagai pola tindakan atau transaksi. Mari kita pertimbangkan server yang menerima dan memproses transaksi yang dikirim oleh klien melalui koneksi soket TCP / IP. Transaksi ini terdiri dari satu perintah, diikuti oleh nol atau lebih argumen.

Pengembang mungkin menggunakan pernyataan switch dengan kasus untuk setiap perintah. Penggunaan Switchpernyataan selama pengkodean adalah tanda desain yang buruk selama fase desain proyek berorientasi objek. Perintah mewakili cara berorientasi objek untuk mendukung transaksi dan dapat digunakan untuk memecahkan masalah desain ini.

Dalam kode klien program TestTransactionCommand.java, semua permintaan dienkapsulasi ke dalam TransactionCommandobjek generik . The TransactionCommandkonstruktor dibuat oleh klien dan terdaftar dengan CommandManager. Permintaan yang diantrekan dapat dijalankan pada waktu yang berbeda dengan memanggil runCommands(), yang memberi kita banyak fleksibilitas. Ini juga memberi kita kemampuan untuk merakit perintah menjadi perintah gabungan. Saya juga memiliki CommandArgument,, CommandReceiverdan CommandManagerkelas dan subkelas TransactionCommand- yaitu AddCommanddan SubtractCommand. Berikut adalah uraian dari masing-masing kelas tersebut:

  • CommandArgumentadalah kelas pembantu, yang menyimpan argumen perintah. Ini dapat ditulis ulang untuk menyederhanakan tugas meneruskan sejumlah besar atau variabel argumen jenis apa pun.

  • CommandReceiver mengimplementasikan semua metode pemrosesan perintah dan diimplementasikan sebagai pola Singleton.

  • CommandManageradalah invoker dan Switchsetara dengan contoh sebelumnya. Ini menyimpan TransactionCommandobjek generik dalam myCommandvariabel pribadinya . Ketika runCommands( )dipanggil, itu memanggil execute( )dari TransactionCommandobjek yang sesuai .

Di Jawa, dimungkinkan untuk mencari definisi kelas dengan string yang berisi namanya. Dalam execute ( )pengoperasian TransactionCommandkelas, saya menghitung nama kelas dan secara dinamis menghubungkannya ke sistem yang sedang berjalan - yaitu, kelas dimuat dengan cepat sesuai kebutuhan. Saya menggunakan konvensi penamaan, nama perintah yang digabungkan dengan string "Command" sebagai nama subkelas perintah transaksi, sehingga dapat dimuat secara dinamis.

Perhatikan bahwa Classobjek yang dikembalikan oleh newInstance( )harus dilemparkan ke tipe yang sesuai. Ini berarti kelas baru harus mengimplementasikan antarmuka atau subkelas kelas yang sudah ada yang dikenal oleh program pada waktu kompilasi. Dalam kasus ini, karena kami mengimplementasikan Commandantarmuka, ini bukan masalah.