Belajar Factory Pattern dalam PHP

Ahmad Oriza 14 April 2020

Belajar Factory Pattern dalam PHP

Konsep

Selamat datang kembali pada pembahasan design pattern. Pada tulisan kali ini kita akan mengupas pattern Factory. Mungkin kamu sering dengan istilah Factory. Factory ini jika kita terjemahkan ke dalam bahasa Indonesia adalah Pabrik.

Ya, memang pattern ini bersifat seperti pabrik. Tugasnya adalah membuat suatu object/benda. Pabrik sepatu menghasilkan sepatu, Recheese Factory memproduksi ayam goreng keju, pabrik tekstil memproduksi kain. Nah, dalam dunia OOP adalah menghasilkan object konkrit (instansiasi).

Mirip seperti pattern Singleton, tujuan dari pattern ini secara teknis adalah menyelesaikan masalah instansiasi. Namun lebih konseptual lagi adalah membuat kode kita menjadi lebih maintainable. Pattern Factory memungkinkan kita untuk hanya menyentuh sebuah interface saja untuk melakukan berbagai bisnis proses. Dibanding kita harus membuat objek konkrit, kita dapat membuat sebuah pabrik untuk menciptakan berbagai kebutuhan kita.

Bonus dari penggunaan pattern ini adalah menjadikan kode kita lebih extensible. Secara langsung menerapkan prinsip Open/Closed dari prinsipal SOLID. Dengan artian kita dapat menginjeksi sebuah proses bisnis baru pada kode kita, tanpa harus memodifikasi source code yang sudah berjalan.

Belum terbayang maksud dari konsep ini? bagaimana kalau kita praktek?

Praktek Tanpa Factory

Ceritanya kamu saat ini sedang membuat modul payment untuk StartUp E-Commerce yang sedang naik daun. Pihak management perusahaan menginginkan platform tersebut menerima berbagai gerbang pambayaran seperti kartu kredit, GoPay, dan Dana. Lalu kamu eksekusi dengan membuat berbagai class/library untuk menangani masing-masing proses bisnis pembayaran.

Kurang lebih seperti ini :

<?php /** * Bayangkan class class ini berada pada file terpisah. * Class real world tidak akan sesimple ini! * Saya gabungkan disini hanya untuk gambaran saja. */ class CreditCard { public function getStatus() { // Mengecek status koneksi ke server. echo "Checking Credit Card server .. \n"; } public function sendIssue() { // Mengirim request untuk pembayaran. echo "Issue sent! \n"; } } class Dana { public function getStatus() { // Mengecek status koneksi ke server. echo "Checking Dana server .. \n"; } public function sendIssue() { // Mengirim request untuk pembayaran. echo "Issue sent! \n"; } } class GoPay { public function getStatus() { // Mengecek status koneksi ke server. echo "Checking GoPay server .. \n"; } public function sendIssue() { // Mengirim request untuk pembayaran. echo "Issue sent! \n"; } } // Ceritanya disini mengambil data pilihan pembayaran dari user. // Dalam kasus yang lebih real bisa jadi datang dari POST/GET Request // Dalam suatu file. $option = 'Dana'; $payment = NULL; if ($option == 'GoPay') { $payment = new GoPay; $payment->getStatus(); $payment->sendIssue(); } else if ($option == 'Dana') { $payment = new Dana; $payment->getStatus(); $payment->sendIssue(); } else { // Kode lanjutan. } ?>

Bisa dilihat, masing-masing class memiliki fungsi menangani pembayaran sesuai dengan Brand-nya. Setiap class mempunyai method getStatus() dan sendIssue(). Ini hanyalah method khayalan saja, hanya untuk contoh. Method getStatus berfungsi Mengecek koneksi ke server pembayaran, sedangkan sendIssue berguna untuk mengirimkan request pembayaran.

Setelah pembuatan class, kita instansiasi objek nya sesuai dengan kebutuhan user. Pada kode tersebut misalnya saja yang dipilih adalah Dana. Terakhir, kita panggil masing-masing method untuk mengoperasikan pembayaran.

Output kode tersebut adalah :

Checking Dana server .. Issue sent!

Kode tersebut nampak baik-baik saja. Namun akan menjadi masalah ketika kita menginstansiasi objek konkrit tersebut di berbagai class. Suatu class akan tergabung sangat erat dengan objek tersebut (tightly coupled), sehingga sulit untuk di test.

Kemudian, jika ada perubahan perilaku pada masing-masing class tersebut, misalnya saja merubah nama method, maka kita harus merubah semua kode pada codebase.

Lebih parah lagi jika class/library ini sudah tersebar dan digunakan rekan satu tim di proyek besar, tentunya refactor besar-besaran kan terjadi jika ada perubahan. Mereka harus menyunting semua instansiasi yang sudah terlanjur digunakan.

Praktek Dengan Factory

Untuk menyelesaikan masalah ini, mari kita implementasi Factory. Pertama-tama kita harus membuat sebuah interface untuk kesepahaman antar class :

interface PaymentInterface { public function getStatus(); public function sendIssue(); }

Disini setiap class wajib implementasi dua method pada interface tersebut. Pertama, getStatus dan yang kedua sendIssue. Method khayalan ini berguna untuk mengecek status dan mengirimkan request/issue pembayaran.

Kemudian, kita harus rombak masing-masing class, untuk menerapkan PaymentInterface :

class CreditCard implements PaymentInterface { public function getStatus() { // Mengecek status koneksi ke server. echo "Checking Credit Card server .. \n"; } public function sendIssue() { // Mengirim request untuk pembayaran. echo "Issue sent! \n"; } } class Dana implements PaymentInterface { public function getStatus() { // Mengecek status koneksi ke server. echo "Checking Dana server .. \n"; } public function sendIssue() { // Mengirim request untuk pembayaran. echo "Issue sent! \n"; } } class GoPay implements PaymentInterface { public function getStatus() { // Mengecek status koneksi ke server. echo "Checking GoPay server .. \n"; } public function sendIssue() { // Mengirim request untuk pembayaran. echo "Issue sent! \n"; } }

Bisa dilihat, sekarang masing-masing class menerapkan dua buah method pada interface. Mari kita lanjutkan, sekarang buat sebuah class baru yang akan berperilaku sebagai pabrik :

class PaymentFactory { // Di method ini kita bikin konkrit class nya. public function getInstance($class) { return new $class; } }

Namingnya bebas sebenarnya. Namun sesuaikan saja dengan proses bisnis yang kita sedang kerjakan. Saya menggunakan nama PaymentFactory agar lebih natural dan mudah dipahami. Pada class tersebut hanya ada sebuah method bernama getInstance yang tugasnya membuat instansiasi sesuai dengan parameter yang dipassing.

Oke, mari kita lihat implementasinya pada kode client (kode dimana proses bisnis dijalankan) :

// Ceritanya disini mengambil data pilihan pembayaran dari user. // Dalam kasus yang lebih real bisa jadi datang dari POST/GET Request // Dalam suatu file. $option = 'GoPay'; // Instansiasi Pabrik beserta parameter class yang mau diproduksi. $PaymentFactory = new PaymentFactory; $payment = $PaymentFactory->getInstance($option); $payment->getStatus(); $payment->sendIssue();

Pada kode tersebut, kita simulasikan pilihan user adalah GoPay. Kemudian, kita buat objek konkrit melalui PaymentFactory. Yang bertugas adalah method getInstance. Lalu kita jalankan method getStatus dan sendIssue untuk mengeksekusi proses bisnis real.

Jika kita gabungkan semuanya, maka kode lengkap terlihat seperti ini :

<?php /** * Bayangkan class class ini berada pada file terpisah. * Class real world tidak akan sesimple ini! * Saya gabungkan disini hanya untuk gambaran saja. */ interface PaymentInterface { public function getStatus(); public function sendIssue(); } class CreditCard implements PaymentInterface { public function getStatus() { // Mengecek status koneksi ke server. echo "Checking Credit Card server .. \n"; } public function sendIssue() { // Mengirim request untuk pembayaran. echo "Issue sent! \n"; } } class Dana implements PaymentInterface { public function getStatus() { // Mengecek status koneksi ke server. echo "Checking Dana server .. \n"; } public function sendIssue() { // Mengirim request untuk pembayaran. echo "Issue sent! \n"; } } class GoPay implements PaymentInterface { public function getStatus() { // Mengecek status koneksi ke server. echo "Checking GoPay server .. \n"; } public function sendIssue() { // Mengirim request untuk pembayaran. echo "Issue sent! \n"; } } /** * Pabrik yang akan membuat berbagai class. */ class PaymentFactory { // Di konstruktor kita bikin konkrit class nya. public function getInstance($class) { return new $class; } } // Ceritanya disini mengambil data pilihan pembayaran dari user. // Dalam kasus yang lebih real bisa jadi datang dari POST/GET Request // Dalam suatu file. $option = 'GoPay'; // Instansiasi Pabrik beserta parameter class yang mau diproduksi. $PaymentFactory = new PaymentFactory; $payment = $PaymentFactory->getInstance($option); $payment->getStatus(); $payment->sendIssue(); ?>

Output kode tersebut adalah :

Checking GoPay server .. Issue sent!

Sekarang, jika kita memerlukan berbagai Payment method kita cukup berhubungan dengan PaymentFactory. Bisa dilihat pada method getInstance kita membuat instansiasi class sesuai dengan class yang diinginkan. Class PaymentFactory dapat menyembunyikan kerumitan dibalik pabrikasi kelas konkrit.

Class ini lah yang akan disebar pada berbagai lini kode yang memerlukan instansiasi payment. Dengan cara ini kita bisa menjaga backward compability, ketika kita ingin patch library kita dengan fitur/class baru.

Referensi :