Prinsip SOLID - Penjelasan Lengkap Beserta Contohnya
Setelah mempelajari tentang pemrograman berorientasi objek, sekarang saatnya kita sama-sama memahami lima buah prinsip yang akan membantu kita membuat design yang lebih baik dalam mengembangkan software.
Lima buah prinsip yang akan kita pelajari disingkat menjadi satu buah kata yaitu S.O.L.I.D. Prinsip ini dipopulerkan oleh Robert C. Martin atau yang biasa dipanggil dengan nama Uncle Bob. Jika kita mengaplikasikan masing-masing prinsip dengan tepat, maka kelima prinsip ini dapat membuat baris kode yang kita tulis semakin extendable dan easier to read.
Sebaliknya jika kita mengembangan software dengan menggunakan design yang buruk, maka baris kode yang kita tulis akan menjadi sangat kaku dan rapuh, sedikit saja kita melakukan perubahan dapat menghasilkan banyak bug. Maka dari itu, sangat penting bagi kita untuk mempelajari dan menerapkan kelima prinsip yang disebut SOLID ini.
Oke, sebelumnya kita semua harus percaya bahwa :
Disiplin itu memang terkesan mengekang, tetapi sebenarnya dia membebaskan.
Menerapkan prinsip SOLID tentunya memerlukan disiplin yang kuat, kita yang mungkin sering berpikir bahwa disiplin itu mengekang, harus mulai merubah pola pikir kita. Karena ketika kita disiplin saat ini, itu akan membebaskan banyak masalah di masa yang akan datang.
Langsung saja aku akan mencoba untuk menjelaskan kelima prinsip SOLID dengan sesederhana mungkin sehingga mudah dimengerti.
#1 Single Responsibility Principle
Sebuah class harus memiliki satu, dan hanya satu alasan untuk berubah.
Satu class hanya boleh mengerjakan satu tujuan. Anti multitasking-multitasking club. Hindari menciptakan GOD Class yang mampu mengerjakan banyak hal. Semua methods dan properties harus bekerja untuk mengerjakan tujuan yang sama.
Ketika sebuah class sudah mulai mengerjakan beberapa tujuan, maka class tersebut harus dipecah menjadi class baru. Atau ketika sebuah class memiliki lebih dari satu alasan untuk berubah, maka class tersebut harus dipecah menjadi class baru.
Contoh :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package galihlprakoso.com.solidprinciple; | |
import java.text.NumberFormat; | |
import java.util.ArrayList; | |
import java.util.Locale; | |
/** | |
* | |
* @author galihlarasprakoso | |
*/ | |
class GODClass { | |
private ArrayList<Integer> getListDataHarga(){ | |
ArrayList<Integer> listDataHarga = new ArrayList<>(); | |
listDataHarga.add(5000); | |
listDataHarga.add(6000); | |
listDataHarga.add(7000); | |
listDataHarga.add(8000); | |
listDataHarga.add(9000); | |
listDataHarga.add(10000); | |
return listDataHarga; | |
} | |
public ArrayList<String> ambilListHarga(){ | |
ArrayList<Integer> listDataHarga = getListDataHarga(); | |
ArrayList<String> listDataHargaFormatted = new ArrayList<>(); | |
for (int i = 0; i < listDataHarga.size(); i++) { | |
String hargaFormatted = formatMataUang(listDataHarga.get(i)); | |
listDataHargaFormatted.add(hargaFormatted); | |
} | |
return listDataHargaFormatted; | |
} | |
public String formatMataUang(Integer angka){ | |
Locale localeID = new Locale("in", "ID"); | |
NumberFormat format = NumberFormat.getCurrencyInstance(localeID); | |
return format.format(angka); | |
} | |
} |
Misalnya suatu saat kita ingin merubah listDataHarga, cara menyajikan data dan format harga. Maka GODClass akan memiliki 3 alasan untuk berubah dan itu melanggar prinsip SOLID yang pertama karena sebuah class harus memiliki satu, dan hanya satu alasan untuk berubah.
#2 Open/Closed Principle
“Classes should be open for extension but closed for modification”
Sengaja ga saya translate karena saya susah bahasa indonesiain :D jadi aneh. Maksud dari prinsip kedua ini adalah misalnya ada sebuah tim programmer yang sedang mengerjakan sebuah aplikasi, suatu hari ada sebuah class yang ditulis oleh programmer yang bernama Paijo, nah setelah Paijo selesai menulis class-nya ada programmer lain yang bernama Suparman yang ingin menggunakan class yang dibuat oleh Paijo namun dia menginginkan ada beberapa perubahan pada class tersebut. Nah, seharusnya Suparman dapat dengan mudah meng-extend class yang dibuat oleh Paijo. Tetapi dengan catatan bahwa Suparman tidak perlu merubah class yang ditulis oleh paijo.
Contoh :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package galihlprakoso.com.solidprinciple; | |
/** | |
* | |
* @author galihlarasprakoso | |
*/ | |
//Contoh yang tidak menggunakan | |
//Open/Closed Principle | |
class WarungMakanBuLastri{ | |
public void sajikanMakananBerdasarkanPesanan(String pesanan){ | |
if(pesanan.equalsIgnoreCase("Nasi Goreng")){ | |
System.out.println("Menyajikan Nasi Goreng"); | |
}else if(pesanan.equalsIgnoreCase("Mie Goreng")){ | |
System.out.println("Menyajikan Mie Goreng"); | |
} | |
//Jika kita ingin menambahkan menu pesanan baru, | |
//Kita harus menambahkan kondisi (if) lagi disini | |
//dengan merubah class Warung Makan | |
//Tentunya ini melanggar prinsip Open/Closed Principle | |
} | |
} | |
//Contoh yang menggunakan | |
//Open/Closed Principle | |
class WarungMakanBuSri{ | |
public void sajikanMakananBerdasarkanPesanan(Pesanan pesanan){ | |
pesanan.sajikanPesanan(); | |
} | |
} | |
interface Pesanan{ | |
void sajikanPesanan(); | |
} | |
class NasiGoreng implements Pesanan{ | |
@Override | |
public void sajikanPesanan() { | |
System.out.println("Menyajikan Nasi Goreng"); | |
} | |
} | |
class MieGoreng implements Pesanan{ | |
@Override | |
public void sajikanPesanan() { | |
System.out.println("Menyajikan Mie Goreng"); | |
} | |
} | |
//Untuk menambah menu, kita tidak perlu mengubah kode yang ada | |
//di class WarungMakanBuSri | |
//Misalnya kita ingin menambah menu makanan SateAyam | |
//Kita cukup membuat class baru bernama SateAyam | |
class SateAyam implements Pesanan{ | |
@Override | |
public void sajikanPesanan() { | |
System.out.println("Menyajikan Sate Ayam"); | |
} | |
} |
“Parent classes should be easily substituted with their child classes without blowing up the application”.
Class induk harus dapat dengan mudah digantikan dengan class turunannya tanpa menghancurkan keseluruhan aplikasi. Langsung saja kita ambil sebagai contoh misalnya :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package galihlprakoso.com.solidprinciple; | |
/** | |
* | |
* @author galihlarasprakoso | |
*/ | |
class AlatMusik { | |
public void berbunyi(){ | |
System.out.println("Bunyi alat musik..."); | |
} | |
} | |
class Gitar extends AlatMusik{ | |
@Override | |
public void berbunyi(){ | |
System.out.println("Jrengg..."); | |
} | |
} | |
class Biola extends AlatMusik{ | |
@Override | |
public void berbunyi(){ | |
System.out.println("Ngek ngok ngek ngik..."); | |
} | |
} |
#4 Interface Segregation Principle
“A client should not be forced to use an interface, if it doesn’t need it.”
Jika kalian sudah mulai belajar PBO, pastinya kamu tidak asing dengan yang namanya interface. Yak, maksud dari prinsip yang ke empat ini sangatlah sederhana yaitu banyak interface yang memiliki tujuan spesifik lebih baik daripada hanya satu interface namun memiliki banyak tujuan. Langsung aja lihat contoh di bawah ini :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package galihlprakoso.com.solidprinciple; | |
/** | |
* | |
* @author galihlarasprakoso | |
*/ | |
//Implementasi yang tidak memenuhi prinsip | |
interface InteraksiPenggunaDenganLayar { | |
void menekan(); | |
void menggeser(); | |
} | |
//Implementasi yang memenuhi prinsip | |
interface InteraksiMenekan{ | |
void menekan(); | |
} | |
interface InteraksiMenggeser{ | |
void menggeser(); | |
} |
Misalnya suatu saat pengguna hanya ingin mengimplementasikan fungsi untuk menangani ketika ada interaksi sentuhan. Masa iya dia harus mengimplementasikan fungsi lain yang tidak dia butuhkan? kan tidak efisien.
#5 Dependency Inversion
“High level modules should not depend on low-level modules, but should depend on abstraction.”
Yak, sampailah kita pada prinsip yang terakhir. Pengertian dari prinsip ini adalah bahwa class yang dikategorikan sebagai high-level class tidak boleh bergantung pada low-level class tetapi harus bergantung pada abstraksi.
Waduh, gimana sih maksudnya? maksudnya adalah setiap class tidak boleh berinteraksi secara langsung. Kenapa? karena itu akan menyebabkan terjadinya tight coupling atau keterikatan yang kuat antara kedua class tersebut. Lalu bagaimana class berkomunikasi? Menggunakan Interface.
Dengan begitu, high-level class tidak perlu khawatir aplikasi akan rusak jika terjadi perubahan pada low-level class. Langsung saja deh lihat contohnya :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package galihlprakoso.com.solidprinciple; | |
import java.util.ArrayList; | |
import java.util.List; | |
/** | |
* | |
* @author galihlarasprakoso | |
*/ | |
interface PekerjaBangunan { | |
void kerja(); | |
} | |
class TukangBatu implements PekerjaBangunan{ | |
@Override | |
public void kerja() { | |
buatPondasi(); | |
memasangBatuBata(); | |
mengecor(); | |
} | |
private void buatPondasi(){ | |
System.out.println("Membuat pondasi..."); | |
} | |
private void memasangBatuBata(){ | |
System.out.println("Memasang batu bata..."); | |
} | |
private void mengecor(){ | |
System.out.println("Mengecor..."); | |
} | |
} | |
class TukangCat implements PekerjaBangunan{ | |
@Override | |
public void kerja() { | |
mengecat(); | |
} | |
private void mengecat(){ | |
System.out.println("Mengecat..."); | |
} | |
} | |
class ProyekBangunan{ | |
List<PekerjaBangunan> pekerja; | |
public ProyekBangunan(List<PekerjaBangunan> pekerja) { | |
this.pekerja = pekerja; | |
} | |
public void mulaiProyek(){ | |
for (int i = 0; i < pekerja.size(); i++) { | |
pekerja.get(i).kerja(); | |
} | |
} | |
} |
Perhatikan class ProyekBangunan yang ada pada contoh di atas. Kita bisa melihat bahwa method mulaiProyek() yang ada pada ProyekBangunan memanggil method kerja() pada setiap class yang mengimplementasikan interface PekerjaBangunan tanpa mengkhawatirkan bagaimana masing-masing pekerja melakukan pekerjaannya yang dia tahu hanya PekerjaBangunan pasti bisa kerja. Dengan begini class ProyekBangunan tidak terikat secara langsung dengan setiap class pekerja dan jika pekerja suatu saat bekerja dengan cara yang berbeda, high-level class seperti ProyekBangunan tidak perlu khawatir dengan perubahan tersebut.
Yak, kira-kira begitulah penjelasan yang bisa kusampaikan kepada teman-teman semua. Jika mungkin aku salah dalam menjelaskan tolong dikoreksi dan jika kalian ada pertanyaan jangan sungkan - sungkan untuk memulai diskusi dengan berkomentar pada kolom komentar yang berada di bawah. Semoga bermanfaat!
dalam dependency principle modul yg bertanggung jawab dengan fungsi yg sangat detail berada pada tingkat ?.....
BalasHapus