Software Design Principle merupakan sebuah pedoman yang dapat kita gunakan untuk
menghindari design yang buruk saat mengembangkan sebuah perangkat lunak. Menurut
Robert C. Martin, terdapat 3 (tiga) karakteristik penting dari design yang buruk yang perlu
kita perhatikan dan sebaiknya dihindari.
Rigidity
Di mulai dari rigidity atau kekakuan. Rigidity adalah kondisi suatu sistem yang sulit diubah,
bahkan untuk perubahan yang paling sederhana. Saat kita ingin melakukan perubahan,
perubahan tersebut menyebabkan ketergantungan untuk mengubah item lain di dalam suatu
modul. Alhasil, perubahan yang seharusnya dapat dilakukan dalam waktu yang singkat,
malah sebaliknya. Belum lagi dampaknya pada modul-modul lain yang saling berkaitan.
Fragility
Immobility
Terakhir yang harus kita perhatikan dan hindari adalah Imobilitas. Yaitu sebuah
ketidakmampuan untuk menggunakan kembali perangkat lunak dari proyek lain atau bagian-
bagian dari proyek yang sama. Seorang engineer tentu akan mengalami kondisi di mana ia
membutuhkan modul atau sistem yang sebelumnya sudah pernah ditulis atau dibuat oleh
engineer lain. Namun, sering juga terjadi bahwa modul yang dibutuhkan tersebut memiliki
terlalu banyak bobot yang bergantung padanya. Setelah mencoba untuk memisahkan, para
engineer menemukan bahwa pekerjaan dan risiko yang diperlukan untuk memisahkan bagian
yang diinginkan (dari bagian yang tidak diinginkan), terlalu besar untuk ditoleransi. Sehingga
alih-alih menulis ulang (re-write), sang engineer akan menggunakannya kembali untuk
project lain.
Dari penjelasan beberapa pedoman di atas, kita dapat mengetahui lebih dalam mengenai
perbedaan antara architecture dan design pada perangkat lunak. Selain itu, telah dijelaskan
pula tentang bagaimana kita mendefinisikan sebuah design yang buruk dan perlu kita
dihindari.
Pada modul selanjutnya kita akan mempelajari tentang prinsip SOLID, baik itu definisi,
penjelasan dan contoh studi kasus kapan kita membutuhkannya. Yuk lanjut!
Pada beberapa modul sebelumnya kita telah belajar memahami Object Oriented
Programming, dari definisi hingga pilar-pilarnya. Kini kita akan masuk ke pembahasan
tentang SOLID ya.
SOLID merupakan kumpulan dari beberapa principle yang diwujudkan oleh engineer-
engineer yang ahli dibidangnya. SOLID membantu kita mengembangkan sebuah perangkat
lunak dengan tingkat kekukuhan yang tinggi. Itu goal kita!
Lantas apakah seseorang yang sudah menguasai dan menggunakan OOP perlu mempelajari
SOLID? Tentu saja YA, karena pada dasarnya OOP dan SOLID merupakan 2 (dua) hal yang
berbeda. OOP adalah sebuah paradigma untuk menuliskan program yang sudah diadaptasi
oleh beberapa bahasa pemrograman, sedangkan SOLID merupakan sebuah principle yang
sudah disebutkan sebelumnya. Sampai di sini kita pasti sudah bisa membedakannya ya.
Berbicara soal paradigma lebih dalam, paradigma sendiri bukanlah sebuah principle yang
mengajarkan tentang bagaimana sebuah tanggung jawab suatu entitas yang berada di dalam
sebuah perangkat lunak. Saat kita sudah berhasil menulis kode dengan mengikuti paradigma
OOP, bukan berarti kita sudah mengikuti design principle yang sudah kita pelajari bersama
dimodul-modul sebelumnya.
Ilustrasi di atas menggambarkan bagaimana sekumpulan bola yang disusun sedemikian rupa
dapat menciptakan bentuk yang kukuh. Padahal seperti yang kita ketahui, bola memiliki
bentuk yang sangat mudah untuk bergerak. Analogi serupa dapat kita terapkan saat mengetik
kode. Dalam mengembangkan sebuah perangkat lunak, jika kita bisa menuliskan kode
dengan bentuk dan ukuran yang sama seperti halnya kumpulan bola di atas, kita pun dapat
menciptakan sebuah sistem yang kukuh. Denga terciptanya sistem yang kukuh, kita dapat
lebih mudah dan leluasa mengganti komponen dan memperluas sistem tanpa adanya
gangguan.
Tujuan SOLID
Sudah paham kan penjelasan dari ilustrasi di atas? Kita jadi paham bahwa dengan mengikuti
prinsip SOLID, kode yang kita buat dapat dengan mudah diekstensi (extended) dan
dipertahankan (maintained).
Prinsip SOLID bukanlah suatu hukum atau aturan tertentu yang wajib kita patuhi, melainkan
sebuah prinsip yang dimaksudkan untuk membantu kita dalam menuliskan kode yang rapi.
Bagaimana hal itu dapat diwujudkan? Berikut adalah tujuan dari prinsip SOLID dalam
pembuatan struktur mid-level perangkat lunak:
Istilah mid-level yang merujuk pada prinsip SOLID ini diterapkan oleh engineer yang bekerja
pada level module. Prinsip ini diterapkan tepat di atas level kode. Manfaatnya, ia dapat
membantu menentukan jenis struktur perangkat lunak yang digunakan dalam modul dan
komponen. Setelah komponen tersebut dapat kita desain dengan baik menggunakan prinsip
SOLID, maka selanjutnya kita dapat melanjutkan ke dalam prinsip-prinsip arsitektur tingkat
tinggi (high-level architecture).
Di modul berikutnya kita akan sama-sama belajar lebih dalam lagi tentang masing-masing
principle yang menjadi bagian dari SOLID itu sendiri.
Kita sudah belajar memahami Object Orientation Programming dan Software Design
Principle. Akhirnya kita sampai juga di modul yang membahas masing-masing prinsip bagian
dari SOLID, lebih dalam.
Kita mulai dari Single Responsibility Principle. Ia merupakan sebuah principle yang relatif
mudah diterapkan dalam pengembangan perangkat lunak. Sederhannya, principle ini
digunakan untuk mengatur tanggung jawab dari sebuah entitas yang berada di dalam sebuah
proyek dalam hal ini adalah sebuah class.
Tanggung jawab (responsibility) berarti bahwa jika suatu class punya 2 (dua) fungsionalitas
yang tak miliki keterkaitan untuk melakukan suatu perubahan, maka kita harus membagi
fungsionalitas yang berbeda tersebut dengan cara memisahkannya menjadi dua class yang
berbeda.
Maka dari itu, setiap class yang sudah dipisahkan berdasarkan fungsionalitasnya hanya akan
menangani satu tanggung jawab. Lebih lanjut, jika kita melakukan perubahan tanggung
jawab, kita tinggal fokus pada masing-masing class yang sudah dipisahkan tersebut.
Apa tujuan menerapkan Single Responsibility? Ketika kita ingin melakukan perubahan pada
sebuah class yang memiliki tanggung jawab yang banyak, perubahan yang akan dilakukan
berpotensi untuk mempengaruhi fungsionalitas dan tanggung jawab lain yang saling
berkaitan di dalam class tersebut.
Single Responsibility Principle adalah prinsip yang sederhana dan intuitif, tetapi dalam
praktiknya terkadang sulit untuk memperbaikinya. Untuk itu, mari kita lanjut ke modul
selanjutnya untuk mengetahui seperti apa penerapannya pada sebuah contoh kasus. Ayo!
Setelah selesai dengan Single Responsibility Principle, mari kita lanjut ke aturan berikutnya
yaitu sebuah entitas perangkat lunak seperti class, property, dan function. Mereka adalah
entitas untuk ditambahkan tetapi tidak untuk dimodifikasi yaitu Open/Close Principle.
Seperti apa detail aturannya?
Pada Tahun 1988, seorang profesor asal Perancis, Bertrand Meyer menulis sebuah buku yang
berjudul Object Oriented Software Construction. Di dalamnya terdapat sebuah aturan yang
mengatur di mana sebuah artefak perangkat lunak harus terbuka untuk ditambahkan tetapi
tertutup untuk dimodifikasi. Aturan tersebut kemudian ditulis lagi pada sebuah artikel yang
berjudul The Open-Closed Principle oleh Robert C. Martin pada tahun 1996.
Lantas apa yang dimaksud dengan terbuka untuk ditambahkan dan tertutup untuk
dimodifikasi? Jangan bingung. Terbuka untuk ditambahkan adalah keadaan ketika sebuah
sistem dapat ditambahkan dengan spesifikasi baru yang dibutuhkan. Sedangkan tertutup
untuk dimodifikasi adalah agar ketika ingin menambahkan spesifikasi baru, kita tidak perlu
mengubah atau memodifikasi sistem yang telah ada.
Aturan ini sekilas terlihat bertentangan satu sama lain, yah? Namun tak usah khawatir, karena
saat kita bisa mengatur dependensi sistem dengan baik dan benar, dengan mudahnya aturan
tersebut dapat kita capai. Secara umum, penggunaan aturan open/close diterapkan dengan
memanfaatkan interface dan abstraksi kelas daripada menggunakan sebuah kelas konkret.
Penggunaan interface dan abstraksi kelas bertujuan agar dapat mudah diperbaiki setelah
pengembangan tanpa harus mengganggu kelas yang mewarisi dan ketika ingin membuat
fungsionalitas baru, cukup dengan membuat kelas baru dan mewarisi interface atau abstraksi
tersebut.
Oke, kita lanjut ke aturan berikutnya yaitu Liskov Substitution Principle. Aturan ini
disampaikan pada pembukaan sebuah acara oleh Barbara Liskov. Beliau menyampaikan
pernyataan sebagai berikut “if for each object o1 of type S there is an object o2 of type T such
that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is
substituted for o2 then S is a subtype of T”.
Sederhanannya, Liskov’s substitution adalah aturan yang berlaku untuk hirarki pewarisan.
Hal ini mengharuskan kita untuk mendesain kelas-kelas yang kita miliki sehingga
ketergantungan antar klien dapat disubstitusikan tanpa klien mengetahui tentang perubahan
yang ada. Oleh karena itu, seluruh SubClass setidaknya dapat berjalan dengan cara yang
sama seperti SuperClass-nya.
Untuk menjadikan sebuah kelas benar-benar menjadi SubClass, kelas tersebut tidak hanya
wajib untuk menerapkan fungsi dan properti dari SuperClass, melainkan juga harus memiliki
perilaku yang sama dengan SuperClass-nya. Untuk mencapainya, terdapat beberapa aturan
yang harus dipatuhi. Mari kita bahas satu per satu.
Selanjutnya adalah aturan preconditions dan postconditions. Ini merupakan tindakan yang
harus ada sebelum atau sesudah sebuah proses dijalankan. Contohnya, ketika kita ingin
memanggil sebuah fungsi yang digunakan untuk membaca data dari database, terlebih dahulu
kita harus memastikan database tersebut dalam keadaan terbuka agar proses dapat dijalankan.
Ini disebut sebagai precondition. Sedangkan postcondition, contohnya saat proses baca tulis
di dalam database telah selesai, kita harus memastikan database tersebut sudah tertutup.
Invariant
Berikutnya adalah invariant. Dalam pembuatan sebuah SubClass, SubClass tersebut harus
memiliki invarian yang sama dengan SuperClass-nya. Invarian sendiri adalah penjelasan
dari kondisi suatu proses yang benar sebelum proses tersebut dimulai dan tetap benar
setelahnya.
Constraint
Terakhir, aturan tentang constraint dari sebuah SubClass. Secara default, SubClass dapat
memiliki fungsi dan properti dari SuperClass-nya. Selain itu, kita juga dapat menambahkan
member baru di dalamnya. Constraint di sini adalah pembatasan yang ditentukan oleh
SuperClass terhadap perubahan keadaan sebuah obyek. Sebagai contoh misal SuperClass
memiliki obyek yang memiliki nilai tetap, maka SubClass tidak diijinkan untuk mengubah
keadaan dari nilai obyek tersebut.
Nah, setelah tahu beberapa aturan di atas, lantas seperti apa penerapannya? Ayo kita pelajari
di modul berikutnya.
Setelah selesai dengan 2 (dua) aturan sebelumnya, apakah kini Anda sudah punya gambaran
untuk diterapkan di dalam proyek garapan Anda? Pastinya iya, agar kode yang sudah kita
tulis bisa dikembangkan lebih lanjut tanpa halangan berarti.
Oke, kita lanjut ke aturan berikutnya yaitu Liskov Substitution Principle. Aturan ini
disampaikan pada pembukaan sebuah acara oleh Barbara Liskov. Beliau menyampaikan
pernyataan sebagai berikut “if for each object o1 of type S there is an object o2 of type T such
that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is
substituted for o2 then S is a subtype of T”.
Sederhanannya, Liskov’s substitution adalah aturan yang berlaku untuk hirarki pewarisan.
Hal ini mengharuskan kita untuk mendesain kelas-kelas yang kita miliki sehingga
ketergantungan antar klien dapat disubstitusikan tanpa klien mengetahui tentang perubahan
yang ada. Oleh karena itu, seluruh SubClass setidaknya dapat berjalan dengan cara yang
sama seperti SuperClass-nya.
Untuk menjadikan sebuah kelas benar-benar menjadi SubClass, kelas tersebut tidak hanya
wajib untuk menerapkan fungsi dan properti dari SuperClass, melainkan juga harus memiliki
perilaku yang sama dengan SuperClass-nya. Untuk mencapainya, terdapat beberapa aturan
yang harus dipatuhi. Mari kita bahas satu per satu.
Selanjutnya adalah aturan preconditions dan postconditions. Ini merupakan tindakan yang
harus ada sebelum atau sesudah sebuah proses dijalankan. Contohnya, ketika kita ingin
memanggil sebuah fungsi yang digunakan untuk membaca data dari database, terlebih dahulu
kita harus memastikan database tersebut dalam keadaan terbuka agar proses dapat dijalankan.
Ini disebut sebagai precondition. Sedangkan postcondition, contohnya saat proses baca tulis
di dalam database telah selesai, kita harus memastikan database tersebut sudah tertutup.
Invariant
Berikutnya adalah invariant. Dalam pembuatan sebuah SubClass, SubClass tersebut harus
memiliki invarian yang sama dengan SuperClass-nya. Invarian sendiri adalah penjelasan
dari kondisi suatu proses yang benar sebelum proses tersebut dimulai dan tetap benar
setelahnya.
Constraint
Terakhir, aturan tentang constraint dari sebuah SubClass. Secara default, SubClass dapat
memiliki fungsi dan properti dari SuperClass-nya. Selain itu, kita juga dapat menambahkan
member baru di dalamnya. Constraint di sini adalah pembatasan yang ditentukan oleh
SuperClass terhadap perubahan keadaan sebuah obyek. Sebagai contoh misal SuperClass
memiliki obyek yang memiliki nilai tetap, maka SubClass tidak diijinkan untuk mengubah
keadaan dari nilai obyek tersebut.
Nah, setelah tahu beberapa aturan di atas, lantas seperti apa penerapannya? Ayo kita pelajari
di modul berikutnya.
Setelah Liskov Substitution, prinsip selanjutnya yang akan kita pelajari adalah Interface
segregation. Prinsip Interface Segregation adalah salah satu dari prinsip yang dikemukakan
oleh Robert C.Martin dalam bukunya yang berjudul Design Principle. Meskipun prinsip-
prinsip tersebut sudah beberapa tahun usianya, prinsip-prinsip ini tetap penting seperti saat
pertama kali ia rilis.
Prinsip ini sendiri bertujuan untuk mengurangi jumlah ketergantungan sebuah class terhadap
interface class yang tidak dibutuhkan. Faktanya, class memiliki ketergantungan terhadap
class lainnya. Jumlah ketergantungan dari fungsi pada sebuah interface class yang dapat
diakses oleh class tersebut harus dioptimalkan atau dikurangi. Mengapa penting? Terkadang
ketika kita membuat sebuah class dengan jumlah fungsi dan properti yang banyak, class lain
yang bergantung pada class tersebut hanya membutuhkan satu atau dua fungsi dari class
tersebut. Ketergantungan antar class akan semakin bertambah seiring bertambahnya jumlah
fungsi dan properti dari class yang dibutuhkan. Lalu bagaimana cara mengatasinya?
Pada saat kita membuat sebuah sistem, pasti kita pernah membuat sebuah class yang
memiliki atau mengimplementasikan beberapa public interface dan interface-interface
tersebut juga digunakan dan di implementasi oleh class lainnya dalam sistem kita. class-class
yang kita buat ini terkadang hanya membutuhkan beberapa fungsi yang ada pada interface
tersebut sehingga menurut aturan prinsip interface segregation hal ini kurang baik. Tapi
tenang, ketika prinsip interface segregation diterapkan, setiap class-class akan
mengimplementasi beberapa interface class yang lebih kecil sesuai dengan fungsi-fungsi
yang dibutuhkan class-class tersebut.
Hal ini berarti bahwa class-class yang saling bergantung dapat berkomunikasi dengan
menggunakan interface yang lebih kecil, mengurangi ketergantungan pada fungsi-fungsi yang
tidak digunakan dan mengurangi coupling. Dengan menggunakan interface yang lebih kecil
akan memudahkan dalam implementasi, meningkatkan fleksibilitas dan juga kemungkinan
untuk digunakan kembali (reuse).
Menarik sekali bukan jika kita berhasil menerapkan prinsip interface segregation dalam
sistem kita? Yuk kita lanjutkan belajarnya ke studi kasus pada modul selanjutnya.
Setelah kita membahas mengenai empat prinsip sebelumnya, kini kita akan membahas
mengenai prinsip terakhir dari S.O.L.I.D, yaitu Prinsip Dependency Inversion. Pada prinsip
Dependency Inversion terdapat dua pernyataan atau aturan yang perlu kita ketahui, yang
pertama adalah high-level module tidak diperbolehkan untuk bergantung pada low-level
module. Keduanya harus bergantung pada abstraction. Pernyataan yang kedua, abstraksi
tidak diperbolehkan untuk bergantung pada detail. Detail harus bergantung pada abstraksi.
Prinsip Dependency Inversion hampir sama dengan konsep layering dalam aplikasi, di mana
low-level modules bertanggung jawab dengan fungsi yang sangat detail dan high-level
modules menggunakan low-level classes untuk mencapai tugas yang lebih besar. Hal ini bisa
dicapai dengan bergantung pada sebuah abstraksi, ketika ada ketergantungan antar kelas
seperti interface, daripada referensi langsung ke kelas lainnya.
Apa yang dimaksud dengan high-level modules dan low-level modules? Agar lebih mudah
memahaminya, kita dapat mengkategorikan kelas-kelas menjadi sebuah hirarki. High-level
modules adalah kelas-kelas yang berurusan dengan kumpulan-kumpulan fungsionalitas. Pada
hirarki tertinggi terdapat kelas-kelas yang mengimplementasikan aturan bisnis sesuai dengan
desain yang telah ditentukan. Low-level modules bertanggung jawab pada operasi yang lebih
detail. Pada level terendah memungkinkan modul ini untuk bertanggung jawab dalam
menulis informasi ke database atau menyampaikan pesan ke sistem operasi.
Berbeda dengan modul-modul sebelumnya, kita akan memulai contoh penerapan dari
Dependency Inversion dengan penjelasan singkat dari hierarki kelas di bawah ini.
Hirarki di atas adalah gambaran fitur sebuah transaksi yang digunakan untuk berinterasi
dengan database. Jika kita perhatikan, di dalam hirarki di atas terdapat class PaymentService
yang digunakan untuk melakukan pembayaran dan class MySQLDatabase yang
bertanggung jawab menyimpan data tersebut ke dalam database. Pada sistem ini juga akan
terdapat fungsi-fungsi untuk menambah atau menghapus data pembayaran. Untuk melakukan
pembayaran kita akan membutuhkan class yang merupakan high-level yaitu class
PaymentService.
Jika kita melihat penerapan pada class tersebut, permasalahan yang ada adalah class tersebut
bergantung pada class database dan memiliki referensi langsung pada class tersebut sebagai
propertinya. Akibatnya, mustahil kita mengganti tipe data dari class tersebut atau ketika kita
ingin menambahkan database baru. Kecuali, class-class yang akan kita tambahkan merupakan
SubClass dari class MySQLDatabase.
Namun, dengan menambahkan class baru tersebut, kita dapat menyalahi prinsip Liskov
Substitution. Kenapa? Sebabnya, kita membutuhkan perubahan lagi pada class
PaymentService yang berarti kita menyalahi aturan lainnya yaitu Open/Close Principle.
Masalah lainnya yang akan timbul adalah ketika kita membutuhkan perubahan pada class
MySQLDatabase, di mana perubahan yang ada pada class tersebut dapat mempengaruhi class
di atasnya yaitu PaymentService. Hal ini juga memungkinkan kita untuk mengubah class-
class lainnya yang berada pada hierarki di atasnya, ketika sistem kita terus berkembang,
permasalahan ini akan tetap terus ada dan semakin membuat kita kesusahan dalam
mengembangkan sistem yang kukuh.
Manfaat lainnya dari penggunaan atau penerapan prinsip ini dapat meningkatkan kekukuhan
dan fleksibilitas dari sistem yang kita kembangkan. Tanpa penerapan prinsip
Dependency,Inversion, hanya class-class lower-level saja yang mudah digunakan kembali.
Agar lebih jelas memahami contoh di atas, mari kita coba mengubah contoh kode yang
menyalahi aturan tersebut dan menjelaskan perubahannya agar sesuai dengan prinsip
Dependency Inversion. Contoh di bawah ini merupakan kode dari hierarki pada class di atas:
Kotlin
Java
Swift
JavaScript
Dart
1. class PaymentService {
2.
4.
5. fun paymentIsValid() {
7. }
8.
9. fun openDatabase() {
11. }
12.
15. }
16.
19. }
20.
23. }
24. }
25.
27.
30. }
31.
34. }
35.
38. }
39. }
Untuk memperbaiki contoh kode di atas agar sesuai dengan prinsip Dependency Inversion,
kita dapat menghapus ketergantungan langsung class PaymentService terhadap class
MySQLDatabase. Kita akan menambahkan abstract class baru sehingga nantinya ketika kita
menambahkan implementasi baru untuk database, kita hanya akan mewariskan dari class
Database. Sehingga hierarki dari kode yang akan kita perbaiki menjadi seperti berikut.
Class abstract yang akan kita tambahkan, yaitu class Database, akan berada pada high-level
dari hierarki class. Sedangkan class MySQLDatabase dan MongoDatabase akan menjadi
SubClass dari class tersebut sehingga tidak ada ketergantungan langsung pada class yang
menjadi implementasi database. Hal ini akan memudahkan kita untuk menambahkan atau
mengganti kode pada class di bawahnya tanpa mempengaruhi class pada hirarki di atasnya.
Untuk lebih jelasnya kita dapat melihat implementasinya pada potongan kode berikut:
Kotlin
Java
Swift
JavaScript
Dart
2.
3. fun paymentIsValid() {
5. }
6.
7. fun openDatabase() {
9. }
10.
13. }
14.
17. }
18.
22. }
23.
28. }
29.
31.
34. }
35.
38. }
39.
42. }
43. }
44.
46.
50.
53. }
54.
57. }
58. }
Dependency Inversion Principle merupakan prinsip ke-5 dan terakhir dari S.O.L.I.D. Dalam
prinsip ini dikenalkan abstraksi sebagai antarmuka antara komponen yang memilik hierarki
tinggi (higher-level) dan komponen yang memiliki hierarki rendah (lower-level) untuk
menghilangkan ketergantungan antara kedua hierarki tersebut.
Setelah mempelajari semua modul hingga tahap ini, Anda dapat banyak pengetahuan mulai
dari pendalam Object Orientation Programming sampai pengertian dan studi kasus dari
beberapa prinsip-prinsip SOLID. Modul selanjutnya adalah modul paling seru! Anda akan
menyelesaikan exam untuk menguji seberapa paham materi-materi yang sudah dipelajari.
Sudah siap? Baca kembali materi, jika belum.