Belajar Prinsip Pemrograman SOLID

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

Dadan Dahman W.
Howdy Sysinfo
11 min readJun 9, 2021

--

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. 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 di modul-modul sebelumnya.

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:

  • Toleran terhadap perubahan [2].
  • Mudah dipahami [2].
  • Komponen dasar dapat digunakan kembali dalam bentuk software system lainnya [2].

Istilah mid-level yang merujuk pada prinsip SOLID ini diterapkan oleh engineer yang bekerja pada level module [2]. 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) [2].

Single Responsibility Principle (SRP)

SRP merupakan sebuah principle yang relatif mudah diterapkan dalam pengembangan perangkat lunak. Sederhananya, principle ini digunakan untuk mengatur tanggung jawab dari sebuah entitas yang berada di dalam sebuah proyek dalam hal ini adalah sebuah module/class untuk memenuhi kebutuhan dari actor. Actor merupakan kumpulan “user” atau “stakeholder” yang menginginkan perubahan pada perangkat lunak kita.

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.

Single Responsibility Principle (SRP) merupakan cara yang baik untuk mengidentifikasi class selama fase desain aplikasi, dan mengingatkan Anda untuk memikirkan semua cara agar class dapat dikembangkan tanpa adanya masalah berarti.

Pemisahan tanggung jawab yang baik dilakukan hanya ketika sebuah gambaran lengkap aplikasi secara keseluruhan tentang bagaimana aplikasi itu dapat bekerja, telah dibuat dan dipahami dengan baik. Sehingga kita dapat memisahkannya dengan rinci.

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.

Open/Close Principle (OCP)

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.

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.

Saat menerapkan open/close principle ke dalam project, kita bisa membatasi kebutuhan untuk mengubah kode yang telah ditulis, diuji dan di-debug. Tujuannya untuk menghindari resiko atau kelemahan sistem yang bisa saja terjadi. Selain itu, kita bisa menghindari ketergantungan dan meningkatkan fleksibilitas sistem. Tentunya ini akan meringankan proses skalabilitas dari sisi pengembangan perangkat lunak.

Liskov Substitution Principle (LSP)

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.

Contravariant dan Covariant

Aturan pertama, SubClass harus memiliki sifat contravariant dan covariant. Contravariant adalah kondisi di mana parameter dari sebuah fungsi yang berada pada SubClass harus memiliki tipe dan jumlah yang sama dengan fungsi yang berada pada SuperClass-nya. Sedangkan Covariant adalah kondisi pengembalian nilai dari fungsi yang berada pada SubClass dan SuperClass.

Preconditions dan Postconditions

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 invariant yang sama dengan SuperClass-nya. Invariant 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.

Liskov’s Substitution principle merupakan prinsip yang dapat meningkatkan design dari sistem yang kita kembangkan. Sehingga ketergantungan antar klien dapat disubstitusikan tanpa klien tahu perubahan yang ada.

Interface Segregation Principle (ISP)

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).

Dengan menerapkan Interface Segregation Principle dapat membantu kita untuk mengembangkan sistem yang kukuh dan mudah dipelihara. Kita dapat mencegah pembuatan interface yang memiliki banyak fungsi untuk kepentingan yang berbeda-beda. Apa akibatnya jika tidak demikian? Tentu kita akan kesusahan karena interface yang memiliki banyak tanggung jawab sangat mudah berganti-ganti. Proses pemeliharaan sistem pun menjadi tidak efisien.

Dependency Inversion Principle (DIP)

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.

Hirarki di atas adalah gambaran fitur sebuah transaksi yang digunakan untuk berinteraksi 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.

Dengan menerapkan prinsip Dependency Inversion, kita dapat menyelesaikan permasalahan-permasalahan ini dengan menghapus ketergantungan langsung antar class. Bagaimana caranya? Kita dapat mengubah ketergantungan antar class dengan membuat class-class tersebut bergantung pada abstraksi, seperti interface atau abstract class. Pada lower-level, class-class yang ada dapat mengimplementasikan interfaces, atau mewariskan fungsi-fungsi dari abstract class. Dengan begitu, perubahan yang ada pada class-class di lower-level tidak akan mempengaruhi hirarki di atasnya, dengan syarat kita tidak mengubah abstraksi yang dibutuhkan.

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.

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.

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.

Rangkuman

Berikut adalah rangkuman dari beberapa materi yang telah dibahas pada module ini:

  • SOLID adalah kumpulan dari beberapa principle yang diwujudkan oleh para engineer yang ahli di bidangnya dan membantu mengembangkan sebuah perangkat lunak dengan tingkat kekukuhan yang tinggi.
  • Ada beberapa tujuan dari prinsip SOLID, yaitu toleran terhadap perubahan, mudah dipahami dan komponen dasar dapat digunakan kembali dalam bentuk software system lainnya.
  • SOLID adalah sebuah singkatan. Yang setiap hurufnya dapat dipecah lagi agar bisa memahami isinya. Masing-masing pecahannya adalah
  • Single Responsibility Principle (SRP), merupakan sebuah principle yang digunakan untuk mengatur tanggung jawab dari sebuah entitas yang berada di dalam sebuah proyek. Entitas dalam hal ini adalah sebuah module/class untuk memenuhi kebutuhan actor. Sedangkan Actor sendiri merupakan kumpulan “user” atau “stakeholder” yang menginginkan perubahan pada perangkat lunak.
  • Open / Close Principle, sebuah principle yang mengatur di mana artefak perangkat lunak harus terbuka untuk ditambahkan tetapi tertutup untuk dimodifikasi.
  • Liskov Subtitution Principle, sebuah principle yang mengatur desain hirarki pewarisan. Aturan — aturan tersebut antara lain
  • Contravariant & Covariant,
    Contravariant adalah kondisi di mana parameter dari sebuah fungsi yang berada pada SubClass harus memiliki tipe dan jumlah yang sama dengan fungsi yang berada pada SuperClass-nya. Sedangkan Covariant, adalah kondisi pengembalian nilai dari fungsi yang berada pada SubClass dan SuperClass.
  • Precondition & PostCondition,
    Precondition adalah tindakan yang harus ada sebelum sebuah proses dijalankan. Sedangkan postcondition sebaliknya, yaitu tindakan yang harus ada ketika sebuah proses selesai dijalankan.
  • Invariant, adalah penjelasan dari kondisi suatu proses yang benar sebelum proses tersebut dimulai dan tetap benar setelahnya.
  • Constraint, adalah pembatasan yang ditentukan oleh SuperClass terhadap perubahan keadaan sebuah obyek.
  • Interface Segregation Principle, sebuah principle yang bertujuan untuk mengurangi jumlah ketergantungan sebuah class terhadap interface class yang tidak dibutuhkan.
  • Dependency Inversion Principle, sebuah principle yang mengatur ketergantungan antar module. Terdapat 2 aturan dalam dependency inversion principle, yaitu
  • High-level module tidak diperbolehkan untuk bergantung pada low-level module. Keduanya harus bergantung pada abstraction.
  • Abstraksi tidak diperbolehkan untuk bergantung pada detail. Detail harus bergantung pada abstraksi.

Referensi : dicoding.com

Github : github.com/dadandw

Linkedin : Dadan Dahman W

--

--