Petua Java 68: Pelajari cara menerapkan pola Perintah di Java

Corak reka bentuk tidak hanya mempercepat fasa reka bentuk projek berorientasikan objek (OO) tetapi juga meningkatkan produktiviti pasukan pembangunan dan kualiti perisian. A corak Command merupakan pola tingkah laku objek yang membolehkan kita untuk mencapai nyahgandingan lengkap antara penghantar dan penerima. ( Pengirim adalah objek yang meminta operasi, dan penerima adalah objek yang menerima permintaan untuk melakukan operasi tertentu. Dengan pemutusan, pengirim tidak memiliki pengetahuan tentang Receiverantarmuka.) Permintaan istilahdi sini merujuk kepada arahan yang akan dilaksanakan. Corak Perintah juga memungkinkan kita untuk mengubah kapan dan bagaimana permintaan dipenuhi. Oleh itu, corak Perintah memberi kita fleksibiliti dan kepanjangan.

Dalam bahasa pengaturcaraan seperti C, penunjuk fungsi digunakan untuk menghilangkan pernyataan pertukaran gergasi. (Lihat "Tip Jawa 30: Polimorfisme dan Java" untuk keterangan yang lebih terperinci.) Oleh kerana Java tidak memiliki penunjuk fungsi, kita dapat menggunakan pola Perintah untuk menerapkan panggilan balik. Anda akan melihat tindakan ini dalam contoh kod pertama di bawah, yang dipanggil TestCommand.java.

Pembangun yang terbiasa menggunakan penunjuk fungsi dalam bahasa lain mungkin tergoda untuk menggunakan Methodobjek API Refleksi dengan cara yang sama. Sebagai contoh, dalam artikelnya "Java Reflection," Paul Tremblett menunjukkan kepada anda bagaimana menggunakan Reflection untuk melaksanakan transaksi tanpa menggunakan pernyataan beralih. Saya telah menolak godaan ini, kerana Sun menyarankan agar tidak menggunakan Reflection API ketika alat lain yang lebih semula jadi untuk bahasa pengaturcaraan Java akan mencukupi. (Lihat Sumber untuk pautan ke artikel Tremblett dan halaman tutorial Sun's Reflection.) Program anda akan lebih mudah untuk debug dan dikendalikan jika anda tidak menggunakan Methodobjek. Sebaliknya, anda harus menentukan antara muka dan menerapkannya di kelas yang melakukan tindakan yang diperlukan.

Oleh itu, saya cadangkan anda menggunakan corak Perintah yang digabungkan dengan mekanisme pemuatan dan pengikatan dinamis Java untuk melaksanakan penunjuk fungsi. (Untuk perincian mengenai mekanisme pemuatan dan pengikatan dinamis Java, lihat karya James Gosling dan Henry McGilton "The Java Language Environment - A White Paper," yang disenaraikan dalam Sumber.)

Dengan mengikuti cadangan di atas, kami memanfaatkan polimorfisme yang diberikan oleh penerapan pola Perintah untuk menghilangkan pernyataan beralih gergasi, yang menghasilkan sistem yang dapat diperluas. Kami juga mengeksploitasi mekanisme pemuatan dan pengikatan dinamik Java yang unik untuk membina sistem yang dinamik dan dapat diperluas secara dinamik. Ini digambarkan dalam contoh contoh kod kedua di bawah, yang disebut TestTransactionCommand.java.

Corak Perintah mengubah permintaan itu sendiri menjadi objek. Objek ini boleh disimpan dan dilalui seperti objek lain. Kunci untuk corak ini adalah Commandantara muka, yang menyatakan antara muka untuk melaksanakan operasi. Dalam bentuk paling mudah, antara muka ini merangkumi executeoperasi abstrak . Setiap Commandkelas konkrit menentukan pasangan tindakan-penerima dengan menyimpan Receiversebagai pemboleh ubah contoh. Ini menyediakan pelaksanaan execute()kaedah yang berbeza untuk meminta permintaan. Yang Receivermempunyai pengetahuan yang diperlukan untuk melaksanakan permintaan itu.

Gambar 1 di bawah menunjukkan Switch- gabungan Commandobjek. Ia mempunyai flipUp()dan flipDown()beroperasi di antara muka. Switchdipanggil invoker kerana memanggil operasi eksekusi di antara muka arahan.

Perintah konkrit,, LightOnCommandmelaksanakan executeoperasi antara muka perintah. Ia mempunyai pengetahuan untuk memanggil Receiveroperasi objek yang sesuai . Ia berfungsi sebagai penyesuai dalam kes ini. Dengan istilah penyesuai, maksud saya bahawa Commandobjek konkrit adalah penyambung mudah, menghubungkan Invokerdan Receiverdengan antara muka yang berbeza.

Pelanggan instantiates yang Invoker, yang Receiver, dan perintah konkrit objek.

Rajah 2, rajah turutan, menunjukkan interaksi antara objek. Ini menggambarkan bagaimana Commandmemisahkan Invokerdari Receiver(dan permintaan yang dilakukannya). Pelanggan membuat arahan konkrit dengan memusatkan konstruktornya dengan yang sesuai Receiver. Kemudian menyimpannya Commanddi Invoker. The Invokerpanggilan menyokong arahan konkrit, yang mempunyai pengetahuan untuk melakukan yang dikehendaki Action()operasi.

Pelanggan (program utama dalam senarai) membuat Commandobjek konkrit dan menetapkannya Receiver. Sebagai Invokerobjek, Switchmenyimpan Commandobjek konkrit . Yang Invokermengeluarkan permintaan dengan memanggil executekepada Commandobjek. CommandObjek konkrit memanggil operasi Receiveruntuk melaksanakan permintaan tersebut.

Idea utama di sini adalah bahawa perintah konkrit mendaftarkan dirinya dengan Invokerdan Invokermemanggilnya kembali, melaksanakan perintah pada Receiver.

Kod contoh corak arahan

Mari kita lihat contoh mudah yang menggambarkan mekanisme panggilan balik yang dicapai melalui corak Perintah.

Contohnya menunjukkan a Fandan a Light. Objektif kami adalah untuk mengembangkan Switchobjek yang dapat menghidupkan atau mematikan objek. Kami melihat bahawa Fandan Lightmemiliki antaramuka yang berbeza, yang bermaksud Switchharus bebas dari Receiverantara muka atau tidak mempunyai pengetahuan tentang kod> Antaramuka penerima. Untuk menyelesaikan masalah ini, kita perlu membuat parameter setiap Switchs dengan perintah yang sesuai. Jelas, yang Switchdisambungkan ke Lightkehendak mempunyai perintah yang berbeza daripada yang Switchdihubungkan ke Fan. The Commandkelas mempunyai sebagai abstrak atau antara muka untuk ini untuk bekerja.

Apabila konstruktor untuk a Switchdipanggil, ia di parameter dengan set perintah yang sesuai. Perintah akan disimpan sebagai pemboleh ubah peribadi dari Switch.

Apabila operasi flipUp()dan flipDown()operasi dipanggil, mereka hanya akan membuat perintah yang sesuai untuk execute( ). Wasiat Switchitu tidak tahu apa yang berlaku akibat execute( )dipanggil.

Kipas kelas TestCommand.java {public void startRotate () {System.out.println ("Fan is rotating"); } public void stopRotate () {System.out.println ("Kipas tidak berputar"); }} lampu kelas {public void turnOn () {System.out.println ("Light on on"); } public void turnOff () {System.out.println ("Lampu mati"); }} kelas Switch {perintah peribadi UpCommand, DownCommand; suis awam (Command Up, Command Down) {UpCommand = Up; // Perintah konkrit mendaftarkan dirinya dengan penyerang DownCommand = Down; } void flipUp () {// invoker memanggil kembali Command konkrit, yang melaksanakan Command pada penerima UpCommand. laksanakan (); } batalkan flipDown () {DownCommand. laksanakan (); }} kelas LightOnCommand melaksanakan Perintah {private Light myLight; LightOnCommand awam (Light L) {myLight = L;} kekosongan awam melaksanakan () {myLight. hidupkan( ); }} kelas LightOffCommand melaksanakan Perintah {private Light myLight; LightOffCommand awam (Light L) {myLight = L; } kekosongan awam melaksanakan () {myLight. turnOff (); }} FanOnCommand kelas melaksanakan Command {private Fan myFan; FanOnCommand awam (Fan F) {myFan = F; } kekosongan awam melaksanakan () {myFan. startRotate (); }} FanOffCommand kelas melaksanakan Command {private Fan myFan; FanOffCommand awam (Fan F) {myFan = F; } kekosongan awam melaksanakan () {myFan. stopRotate (); }} kelas awam TestCommand {public static void main (String [] args) {Light testLight = cahaya baru (); LightOnCommand testLOC = LightOnCommand baru (testLight); LightOffCommand testLFC = LightOffCommand baru (testLight); Switch testSwitch = Suis baru (testLOC, testLFC); testSwitch.flipUp (); testSwitch.flipDown ();Fan testFan = Kipas baru (); FanOnCommand foc = FanOnCommand baru (testFan); FanOffCommand ffc = FanOffCommand baru (testFan); Tukar ts = Suis baru (foc, ffc); ts.flipUp (); ts.flipDown (); }} Perintah antara muka awam Command.java {public abstract void execute (); }

Perhatikan dalam contoh kod di atas bahawa corak Perintah melepaskan sepenuhnya objek yang memanggil operasi - (Switch )- dari yang mempunyai pengetahuan untuk melaksanakannya - Lightdan Fan. Ini memberi kita banyak kelonggaran: objek yang mengeluarkan permintaan mesti tahu hanya cara mengeluarkannya; ia tidak perlu mengetahui bagaimana permintaan itu akan dilaksanakan.

Corak arahan untuk melaksanakan transaksi

Corak Perintah juga dikenali sebagai corak tindakan atau transaksi. Mari kita pertimbangkan pelayan yang menerima dan memproses transaksi yang dihantar oleh pelanggan melalui sambungan soket TCP / IP. Transaksi ini terdiri daripada perintah, diikuti dengan sifar atau lebih banyak argumen.

Pembangun mungkin menggunakan pernyataan beralih dengan huruf besar untuk setiap perintah. Penggunaan Switchpernyataan semasa pengekodan adalah tanda reka bentuk yang tidak baik semasa fasa reka bentuk projek berorientasikan objek. Perintah mewakili cara berorientasi objek untuk menyokong transaksi dan dapat digunakan untuk menyelesaikan masalah reka bentuk ini.

Dalam kod klien program TestTransactionCommand.java, semua permintaan dimasukkan ke dalam TransactionCommandobjek generik . The TransactionCommandpembina dicipta oleh pelanggan dan ia berdaftar dengan CommandManager. Permintaan beratur dapat dilaksanakan pada waktu yang berlainan dengan memanggil runCommands(), yang memberi kita banyak fleksibilitas. Ini juga memberi kita kemampuan untuk mengumpulkan perintah menjadi perintah gabungan. Saya juga mempunyai CommandArgument, CommandReceiverdan CommandManagerkelas dan subkelas TransactionCommand- iaitu AddCommanddan SubtractCommand. Berikut adalah penerangan setiap kelas ini:

  • CommandArgumentadalah kelas penolong, yang menyimpan argumen perintah. Ia dapat ditulis semula untuk mempermudah tugas menyampaikan sejumlah besar argumen dari jenis apa pun.

  • CommandReceiver melaksanakan semua kaedah pemprosesan perintah dan dilaksanakan sebagai corak Singleton.

  • CommandManageradalah penyerang dan Switchsetara dengan contoh sebelumnya. Ia menyimpan TransactionCommandobjek generik dalam myCommandpemboleh ubah peribadinya . Apabila runCommands( )dipanggil, ia memanggil objek yang execute( )sesuai TransactionCommand.

Di Jawa, adalah mungkin untuk mencari definisi kelas yang diberi rentetan yang mengandungi namanya. Dalam execute ( )operasi TransactionCommandkelas, saya mengira nama kelas dan menghubungkannya secara dinamik ke dalam sistem berjalan - iaitu, kelas dimuat dengan cepat seperti yang diperlukan. Saya menggunakan konvensyen penamaan, nama perintah digabungkan dengan rentetan "Command" sebagai nama subkelas perintah transaksi, sehingga dapat dimuat secara dinamis.

Perhatikan bahawa Classobjek yang dikembalikan oleh newInstance( )harus dilemparkan ke jenis yang sesuai. Ini bermakna kelas baru mesti melaksanakan antara muka atau subkelas kelas yang ada yang diketahui oleh program pada waktu kompilasi. Dalam kes ini, kerana kami menerapkan Commandantara muka, ini tidak menjadi masalah.