Pola Perintah di Java

1. Ikhtisar

Pola perintah adalah pola desain perilaku dan merupakan bagian dari daftar pola desain formal Pemerintah Indonesia. Sederhananya, pola bermaksud untuk merangkum dalam objek semua data yang diperlukan untuk melakukan tindakan tertentu (perintah), termasuk metode apa yang akan dipanggil, argumen metode, dan objek tempat metode tersebut berada.

Model ini memungkinkan kita untuk memisahkan objek yang menghasilkan perintah dari konsumennya , oleh karena itu pola tersebut umumnya dikenal sebagai pola produsen-konsumen.

Dalam tutorial ini, kita akan belajar bagaimana mengimplementasikan pola perintah di Java dengan menggunakan kedua pendekatan berorientasi objek dan fungsi objek, dan kita akan melihat dalam kasus penggunaan apa itu bisa berguna.

2. Implementasi Berorientasi Objek

Dalam implementasi klasik, pola perintah membutuhkan implementasi empat komponen: Perintah, Penerima, Invoker, dan Klien .

Untuk memahami cara kerja pola dan peran yang dimainkan setiap komponen, mari buat contoh dasar.

Misalkan kita ingin mengembangkan aplikasi file teks. Dalam kasus seperti itu, kita harus menerapkan semua fungsionalitas yang diperlukan untuk melakukan beberapa operasi terkait file teks, seperti membuka, menulis, menyimpan file teks, dan sebagainya.

Jadi, kita harus memecah aplikasi menjadi empat komponen yang disebutkan di atas.

2.1. Kelas Perintah

Perintah adalah objek yang perannya menyimpan semua informasi yang diperlukan untuk menjalankan suatu tindakan , termasuk metode yang akan dipanggil, argumen metode, dan objek (dikenal sebagai penerima) yang mengimplementasikan metode tersebut.

Untuk mendapatkan gambaran yang lebih akurat tentang cara kerja objek perintah, mari mulai mengembangkan lapisan perintah sederhana yang hanya mencakup satu antarmuka dan dua implementasi:

@FunctionalInterface public interface TextFileOperation { String execute(); }
public class OpenTextFileOperation implements TextFileOperation { private TextFile textFile; // constructors @Override public String execute() { return textFile.open(); } }
public class SaveTextFileOperation implements TextFileOperation { // same field and constructor as above @Override public String execute() { return textFile.save(); } } 

Dalam kasus ini, antarmuka TextFileOperation mendefinisikan API objek perintah, dan dua implementasi, OpenTextFileOperation dan SaveTextFileOperation, melakukan tindakan konkret. Yang pertama membuka file teks, sedangkan yang terakhir menyimpan file teks.

Jelas untuk melihat fungsionalitas objek perintah: perintah TextFileOperation merangkum semua informasi yang diperlukan untuk membuka dan menyimpan file teks, termasuk objek penerima, metode untuk memanggil, dan argumen (dalam hal ini, tidak ada argumen yang diperlukan, tapi mereka bisa).

Perlu ditekankan bahwa komponen yang menjalankan operasi file adalah penerima ( instance TextFile ) .

2.2. Kelas Penerima

Penerima adalah objek yang melakukan serangkaian tindakan kohesif . Ini adalah komponen yang melakukan tindakan aktual ketika metode perintah mengeksekusi () dipanggil.

Dalam hal ini, kita perlu mendefinisikan kelas penerima, yang perannya adalah untuk memodelkan objek TextFile :

public class TextFile { private String name; // constructor public String open() { return "Opening file " + name; } public String save() { return "Saving file " + name; } // additional text file methods (editing, writing, copying, pasting) } 

2.3. Kelas Invoker

Invoker adalah objek yang mengetahui cara menjalankan perintah yang diberikan, tetapi tidak mengetahui bagaimana perintah tersebut diterapkan. Ia hanya mengetahui antarmuka perintah.

Dalam beberapa kasus, invoker juga menyimpan dan mengantri perintah, selain menjalankannya. Ini berguna untuk mengimplementasikan beberapa fitur tambahan, seperti fungsi perekaman makro atau urung dan ulangi.

Dalam contoh kita, menjadi jelas bahwa harus ada komponen tambahan yang bertanggung jawab untuk memanggil objek perintah dan mengeksekusinya melalui metode perintah ' execute () . Di sinilah tepatnya kelas invoker berperan .

Mari kita lihat implementasi dasar dari invoker kami:

public class TextFileOperationExecutor { private final List textFileOperations = new ArrayList(); public String executeOperation(TextFileOperation textFileOperation) { textFileOperations.add(textFileOperation); return textFileOperation.execute(); } }

Kelas TextFileOperationExecutor hanyalah lapisan tipis abstraksi yang memisahkan objek perintah dari konsumennya dan memanggil metode yang dienkapsulasi dalam objek perintah TextFileOperation .

Dalam kasus ini, kelas juga menyimpan objek perintah dalam Daftar . Tentu saja, ini tidak wajib dalam implementasi pola, kecuali kita perlu menambahkan beberapa kontrol lebih lanjut ke proses eksekusi operasi.

2.4. Kelas Klien

Klien adalah objek yang mengontrol proses eksekusi perintah dengan menentukan perintah apa yang akan dieksekusi dan pada tahap proses apa untuk mengeksekusinya.

Jadi, jika kita ingin menjadi ortodoks dengan definisi formal pola, kita harus membuat kelas klien dengan menggunakan metode utama yang khas :

public static void main(String[] args) { TextFileOperationExecutor textFileOperationExecutor = new TextFileOperationExecutor(); textFileOperationExecutor.executeOperation( new OpenTextFileOperation(new TextFile("file1.txt")))); textFileOperationExecutor.executeOperation( new SaveTextFileOperation(new TextFile("file2.txt")))); } 

3. Implementasi Objek-Fungsional

Sejauh ini, kami telah menggunakan pendekatan berorientasi objek untuk mengimplementasikan pola perintah, yang semuanya baik dan bagus.

Dari Java 8, kita dapat menggunakan pendekatan objek-fungsional, berdasarkan ekspresi lambda dan referensi metode, untuk membuat kode sedikit lebih ringkas dan tidak terlalu bertele-tele .

3.1. Menggunakan Ekspresi Lambda

Karena antarmuka TextFileOperation adalah antarmuka fungsional, kita dapat meneruskan objek perintah dalam bentuk ekspresi lambda ke invoker , tanpa harus membuat instance TextFileOperation secara eksplisit:

TextFileOperationExecutor textFileOperationExecutor = new TextFileOperationExecutor(); textFileOperationExecutor.executeOperation(() -> "Opening file file1.txt"); textFileOperationExecutor.executeOperation(() -> "Saving file file1.txt"); 

The implementation now looks much more streamlined and concise, as we've reduced the amount of boilerplate code.

Even so, the question still stands: is this approach better, compared to the object-oriented one?

Well, that's tricky. If we assume that more compact code means better code in most cases, then indeed it is.

As a rule of thumb, we should evaluate on a per-use-case basis when to resort to lambda expressions.

3.2. Using Method References

Similarly, we can use method references for passing command objects to the invoker:

TextFileOperationExecutor textFileOperationExecutor = new TextFileOperationExecutor(); TextFile textFile = new TextFile("file1.txt"); textFileOperationExecutor.executeOperation(textFile::open); textFileOperationExecutor.executeOperation(textFile::save); 

In this case, the implementation is a little bit more verbose than the one that uses lambdas, as we still had to create the TextFile instances.

4. Conclusion

Dalam artikel ini, kita mempelajari konsep kunci pola perintah dan cara mengimplementasikan pola di Java dengan menggunakan pendekatan berorientasi objek dan kombinasi ekspresi lambda dan referensi metode.

Seperti biasa, semua contoh kode yang ditampilkan dalam tutorial ini tersedia di GitHub.