Beberapa tahun belakangan Observer Design Pattern kembali populer digunakan dalam mengembangkan aplikasi. Observer Design Pattern cukup berguna terutama pada distributed system, di mana dengan Observer Design Pattern kita bisa membuat dependency antar object jadi berkurang sehingga logic antar dependency object lebih sederhana. Jadi komunikasi antar dependency object tidak lagi dipanggil secara langsung, melainkan lewat komponen yang disebut Publisher. Flownya nanti setiap ada update, object akan mengirimkan request ke Publisher, lalu Publisher inilah yang meneruskan request tersebut ke dependency-dependency yang “berlangganan” event dari object tadi yang disebut Subscriber. “Berlangganan” di sini maksudnya adalah dependencynya didaftarkan pada Publisher sebagai Subscriber dan dependency itu tidak meminta request untuk diproses, melainkan dependency itu hanya menunggu publisher memberi request. Analoginya seperti kita ingin beli laptop keluaran terbaru yang akan rilis, kita tidak perlu mengecek toko setiap saat, melainkan cukup bilang ke penjualnya, “Kalau sudah rilis hubungi saya!”. Konsep inilah yang digunakan sebagai Choreography pada distributed system oleh aplikasi Message Broker di jaman sekarang.
Observer Design Pattern adalah Behavioral Design Pattern dengan mekanisme berlangganan pada suatu komponen, di mana ketika terjadi suatu event pada komponen tersebut, maka komponen tersebut akan memberitahu perubahan tersebut melalui komponen lain yang disebut Publisher kepada komponen-komponen yang berlangganan padanya yang disebut Subscribers.
Design Pattern
Use Case
Kita akan membuat flow pemesanan barang, setelah pesanan terbuat maka akan memperbarui stok produk dan lanjut membuat invoice.
Contoh Code
Ordering Class
public class Ordering{
private String orderNo;
private String product;
private int totalItem;
private BigDecimal price;
private String seller;
private String customer;
public String getOrderNo(){
return orderNo;
}
public void setOrderNo(String orderNo){
this.orderNo = orderNo;
}
public String getProduct(){
return product;
}
public void setProduct(String product){
this.product = product;
}
public int getTotalItem(){
return totalItem;
}
public void setTotalItem(int totalItem){
this.totalItem = totalItem;
}
public BigDecimal getPrice(){
return price;
}
public void setPrice(BigDecimal price){
this.price = price;
}
public String getSeller(){
return seller;
}
public void setSeller(String seller){
this.seller = seller;
}
public String getCustomer(){
return customer;
}
public void setCustomer(String customer){
this.customer = customer;
}
}
OrderCreation Class
public class OrderCreation{
private final InvoiceCreation invoiceCreation;
private final StockUpdater stockUpdater;
public OrderCreation(InvoiceCreation invoiceCreation, StockUpdater stockUpdater){
this.invoiceCreation = invoiceCreation;
this.stockUpdater = stockUpdater;
}
public void createOrder(){
Ordering ordering = new Ordering();
ordering.setCustomer("tukul");
ordering.setSeller("arwana");
ordering.setOrderNo("xyz-888");
ordering.setPrice(BigDecimal.TEN);
ordering.setProduct("susu");
ordering.setTotalItem(10);
System.out.println("successfully created order " + ordering.getOrderNo());
invoiceCreation.consume(ordering);
stockUpdater.consume(ordering);
}
}
InvoiceCreation Class
public class InvoiceCreation{
public void consume(Ordering ordering){
System.out.println("invoice from order " + ordering.getOrderNo() + " has been created");
}
}
StockUpdater Class
public class StockUpdater{
public void consume(Ordering ordering){
System.out.println("stock of product " + ordering.getProduct() + " from order " + ordering.getOrderNo() +
" has been updated " + ordering.getTotalItem() + " unit");
}
}
Contoh penggunaan
public static void main(String[] args){
OrderCreation orderCreation = new OrderCreation(new InvoiceCreation(), new StockUpdater());
orderCreation.createOrder();
}
Masalah
Sebenarnya ga ada yang salah dengan code di atas😅, karena codenya masih sangat sederhana dan sah-sah saja dibuat seperti code di atas. Masalahnya adalah ketika dependency tersebut nantinya bertambah menjadi beberapa dependency seperti email queue, push notification, delivery, dan lain-lain. Tentu jadi ribet nge-handlenya. Code di class OrderCreation akan membengkak.
Solusi
Sekarang kita coba implementasikan Observer Design Pattern😎.
Subscriber Interface
public interface Subscriber{
void consume(Ordering ordering);
}
StockUpdater Class
public class StockUpdater implements Subscriber{
@Override
public void consume(Ordering ordering){
System.out.println("stock of product " + ordering.getProduct() + " from order " + ordering.getOrderNo() +
" has been updated " + ordering.getTotalItem() + " unit");
}
}
InvoiceCreation Class
public class InvoiceCreation implements Subscriber{
@Override
public void consume(Ordering ordering){
System.out.println("invoice from order " + ordering.getOrderNo() + " has been created");
}
}
Publisher Interface
public interface Publisher{
Publisher addSubscriber(Subscriber subscriber);
Publisher removeSubscriber(Class<? extends Subscriber> subsriberClass);
void produce(Ordering ordering);
}
OrderCreationPublisher class
public class OrderCreationPublisher implements Publisher{
Map<Class<? extends Subscriber>, Subscriber> subscribers = new HashMap<>();
@Override
public Publisher addSubscriber(Subscriber subscriber){
this.subscribers.put(subscriber.getClass(), subscriber);
return this;
}
@Override
public Publisher removeSubscriber(Class<? extends Subscriber> subsriberClass){
subscribers.remove(subsriberClass);
return this;
}
@Override
public void produce(Ordering ordering){
subscribers.forEach((aClass, subscriber) -> {
subscriber.consume(ordering);
});
}
}
OrderCreation class
public class OrderCreation{
private final Publisher publisher;
OrderCreation(Publisher publisher){
this.publisher = publisher;
}
public void createOrder(){
Ordering ordering = new Ordering();
ordering.setCustomer("tukul");
ordering.setSeller("arwana");
ordering.setOrderNo("xyz-888");
ordering.setPrice(BigDecimal.TEN);
ordering.setProduct("susu");
ordering.setTotalItem(10);
System.out.println("successfully created order " + ordering.getOrderNo());
this.publisher.produce(ordering);
}
}
Contoh penggunaan
public static void main(String[] args){
Publisher publisher = new OrderCreationPublisher()
.addSubscriber(new StockUpdater())
.addSubscriber(new InvoiceCreation());
OrderCreation orderCreation = new OrderCreation(publisher);
orderCreation.createOrder();
System.out.println();
publisher.removeSubscriber(InvoiceCreation.class);
orderCreation.createOrder();
}
Sekarang kita bikin 2 interface, Publisher & Subscriber. InvoiceCreation dan StockUpdater mengimplementasi Subscriber. Kita juga butuh OrderCreationPublisher sebagai concrete class yang mengimplementasi Publisher. Terakhir, hanya Publisher yang kita inject dependencynya ke object OrderCreation. Jadi OrderCreation tidak akan berhubungan langsung dengan InvoiceCreation dan StockUpdater, melainkan lewat perantara Publisher ketika selesai melakukan tugasnya. Lalu publisher yang akan meneruskan requestnya ke masing-masing Subscriber. Publisher inilah yang menjadi alat komunikasi antar dependency. Jadi antar dependency tidak perlu repot-repot nge-handle manual dan inject dependency.
Kenapa menggunakan Observer Design Pattern?
Dengan Observer Design Pattern, ketika suatu saat ada dependency baru, kita tinggal bikin class yang mengimplementasi Subscriber dan register ke Publisher tanpa menambahkan dependency ke masing-masing object. Begitu juga saat ada perubahan atau menghapus dependency, maka kita tinggal remove dependencynya dari Publisher tanpa ada perubahan di masing-masing object dependency. Kita bisa dengan bebas mengatur Subscribernya bahkan saat runtime. Design Pattern ini juga memenuhi Single Responsibility Principle dan Open/Close Principle.
Verdict
Observer Design Pattern mengurangi dependency object secara langsung, melainkan komunikasi antar dependency lewat perantara Publisher. Strukturnya agak mirip dengan Chain of Responsibility Design Pattern, yaitu sama-sama meneruskan request antar dependency. Secara OOP, design pattern ini memang jarang terlihat. Namun, beberapa tahun belakangan design pattern ini kembali populer berkat Message Broker seperti Kafka atau RabbitMQ. Message Broker tersebut memiliki konsep yang sama dengan Observer Design Pattern, di mana antar dependency tidak berkomunikasi secara langsung, melainkan lewat perantara Message Broker. Nanti Message Broker yang meneruskan pesannya ke masing-masing dependency tanpa harus menuliskan logicnya di dalam object yang memiliki dependency. Pola seperti ini pada distributed system disebut Choreography.