Yang udah pernah menggunakan Clean Architecture mungkin udah familiar dengan design pattern ini, terutama ketika declare input boundary. Selain itu, ketika kita melakukan pembungkusan logic ke dalam runnable juga termasuk Command Pattern. Design pattern yang satu ini memang cukup populer di kalangan software engineer. Termasuk salah satu design pattern yang gampang dipahami.
Command Design Pattern adalah Behavioral Design Pattern yang membungkus request menjadi objek tunggal yang menyediakan informasi tentang request tersebut untuk ditransformasikan menjadi method arguments, eksekusi request, antrian proses, dan undoable operations.
Design Pattern
Use Case
Kita akan membuat system delete operation, yang mencakup delete user dan delete order.
- Pertama akan dilakukan pengecekan apakah ada order yang dilakukan oleh user id dari request;
- Jika ada maka akan dilakukan penghapusan order berdasarakan user id tersebut;
- Lalu, akan dilakukan pengecekan apakah user id tersebut ada di user repository;
- Jika ada maka akan dilakukan penghapusan user berdasarakan user id tersebut;
Contoh Code
UserRepository Interface
public interface UserRepository{
void deleteUserById(int id);
boolean check(int userId);
}
OrderRepository Interface
public interface OrderRepository{
void deleteOrderByUserId(int id);
boolean check(int userId);
}
UserRepositoryImpl Class
public class UserRepositoryImpl implements UserRepository{
@Override
public void deleteUserById(int id){
System.out.println("success deleted by user id: " + id);
}
@Override
public boolean check(int userId){
return true;
}
}
OrderRepositoryImpl Class
public class OrderRepositoryImpl implements OrderRepository{
@Override
public void deleteOrderByUserId(int id){
System.out.println("success deleted order by user id: " + id);
}
@Override
public boolean check(int userId){
return false;
}
}
Contoh penggunaan
public static void main(String[] args){
OrderRepository orderRepository = new OrderRepositoryImpl();
UserRepository userRepository = new UserRepositoryImpl();
int userId = 1;
//delete order
if(orderRepository.check(userId)){
orderRepository.deleteOrderByUserId(userId);
}
//delete user
if(userRepository.check(userId)){
userRepository.deleteUserById(userId);
}
}
Code di atas cukup simple dan cukup menjelaskan requirement yang dibutuhkan.
Masalah
Terdapat dua buah action yang identik, yaitu delete user dan delete order. Disini client lah yang melakukan configurasi request. Misalkan ada penambahan action lagi seperti delete bank account by user id, maka user harus melakukan penambahan configurasi lagi di client code.
Solusi
Sekarang kita coba membuat Command Design Pattern😎.
DeleteCommand Interface
public interface DeleteCommand{
void execute(int userId);
}
UserDeleteCommand Class
public class UserDeleteCommand implements DeleteCommand{
private final UserRepository repository;
public UserDeleteCommand(UserRepository repository){
this.repository = repository;
}
@Override
public void execute(int userId){
if(repository.check(userId)){
repository.deleteUserById(userId);
}
}
}
OrderDeleteCommand Class
public class OrderDeleteCommand implements DeleteCommand{
private final OrderRepository repository;
public OrderDeleteCommand(OrderRepository repository){
this.repository = repository;
}
@Override
public void execute(int userId){
if(repository.check(userId)){
repository.deleteOrderByUserId(userId);
}
}
}
Contoh penggunaan
public static void main(String[] args){
int userId = 1;
//delete order
invokeDelete(new OrderDeleteCommand(new OrderRepositoryImpl()), userId);
//delete user
invokeDelete(new UserDeleteCommand(new UserRepositoryImpl()), userId);
}
private static void invokeDelete(DeleteCommand deleteCommand, int userId){
deleteCommand.execute(userId);
}
Kita hanya menambahkan Command Interface dan implementasinya. Kali ini kita membungkus operasi delete user dan delete order ke dalam sebuah objek. Untuk menggunakannya kita cukup membuat objek dari class tersebut dan melakukan eksekusi. Pada contoh di atas, method invokeDelete() berperan sebagai invoker dari command yang diinput. Ketika ada penambahan delete action baru, tinggal bikin class baru yang mengimplementasi DeleteCommand.
Kenapa menggunakan Command Design Pattern?
Command Design Pattern digunakan ketika ingin menjadikan sebuah action sebagai parameter. Oleh karena itu kita membungkusnya menjadi sebuah objek. Dengan begitu kita bisa mengirim objek yang berisi action yang identik dan fleksibel. Selain itu, command pattern juga bisa dimanfaatkan untuk antrian eksekusi, karena trigger proses eksekusinya bisa dilakukan belakangan seperti pada Runnable di Java.
Verdict
Contoh Command Pattern yang paling sering dilakukan adalah ketika melakukan multi-threading menggunakan Runnable, di sana kita membungkus sebuah action ke dalam objek runnable sebelum dieksekusi nanti secara concurrent. Ini juga diimplementasi pada Clean Architecture untuk membungkus logic pada class use case. Sekilas memang terlihat seperti Strategy Design Pattern, tapi sebenarnya beda tujuan. Yang membedakan adalah, Command Design Pattern lebih spesifik tentang membungkus algoritma ke dalam sebuah objek yang memiliki sebuah method berupa perintah untuk mentrigger eksekusi algoritma tersebut. Sedangkan Strategy Design Pattern lebih general tentang penggunaan varian dari beberapa algoritma agar fleksibel dan maintainable. Dengan Command Pattern kita telah menerapkan Single Responsibility dan Open/Close Principle.