Indonesian (Bahasa Indonesia) translation by Andy Nur (you can also view the original English article)
Single Responsibility (SRP), Open/Close, Liskov's Substitution, Interface Segregation, dan Dependency Inversion. Lima prinsip-prinsip agile yang sebaiknya memandu anda setiap kali anda menulis kode.
Definisi
Sebuah class sebaiknya hanya memiliki satu alasan untuk perubahan.
Didefinisikan oleh Robert C. Martin dalam bukunya Agile Software Development, Principles, Patterns, and Practices dan kemudian diterbitkan ulang dalam versi C# dalam buku Agile Principles, Patterns, and Practices in C#, ini adalah salah satu dari lima prinsip SOLID agile. Apa yang ia nyatakan sangat sederhana, namun mencapai kesederhanaan itu bisa sangat rumit. Sebuah class sebaiknya hanya memiliki satu alasan untuk perubahan.
Tetapi mengapa? Mengapa itu sangat penting untuk hanya memiliki satu alasan untuk perubahan?
Dalam tipe statis dan bahasa yang dikompilasi, beberapa alasan dapat menyebabkan beberapa, pemindahan yang tidak diinginkan. Jika ada dua alasan yang berbeda untuk perubahan, bisa dibayangkan bahwa dua tim yang berbeda mungkin bekerja pada kode yang sama untuk dua alasan yang berbeda. Masing-masing harus memaparkan solusinya, yang dalam kasus ini suatu bahasa yang dikompilasi (seperti C++, C# atau Java), dapat mengakibatkan tidak kompatibelnya modul dengan tim lain atau bagian lain dari aplikasi.
Meskipun anda mungkin tidak menggunakan bahasa yang dikompilasi, anda mungkin perlu untuk mengetes ulang class yang sama atau modul untuk alasan yang berbeda. Ini berarti lebih banyak perkerjaan tanya jawab, waktu, dan usaha.
Peserta
Menentukan satu single responsibility suatu class atau modul seharusnya lebih kompleks daripada hanya melihat daftar periksa. Misalnya, satu petunjuk untuk menemukan alasan kita melakukan perubahan adalah menganalisis peserta untuk class kita. Pengguna aplikasi atau sistem yang kita kembangkan yang dilayani oleh modul tertentu akan menjadi orang yang meminta perubahan untuk itu. Mereka yang bertugas akan meminta perubahan. Berikut adalah beberapa modul dan kemungkinan pesertanya.
- Modul Persistent - Peserta termasuk DBA dan arsitek perangkat lunak.
- Modul Pelaporan - Peserta termasuk juru rulis, akuntan, dan operasi.
- Modul Perhitungan Pembayaran untuk Sistem Penggajian - Peserta mungkin termasuk pengacara, manajer, dan akuntan.
- Modul Pencarian Buku untuk Sistem Manajemen Perpustakaan - Peserta dapat mencakup pustakawan dan/atau klien itu sendiri.
Peran dan Aktor
Mengaitkan orang yang konkrit dengan semua peran ini mungkin sulit dilakukan. Di sebuah perusahaan kecil, satu orang mungkin perlu untuk memenuhi beberapa peran sementara di sebuah perusahaan besar mungkin ada beberapa orang yang dialokasikan untuk satu peran tunggal. Jadi nampaknya jauh lebih masuk akal untuk memikirkan perannya. Tetapi perannya sendiri cukup sulit untuk didefinisikan. Apa perannya? Bagaimana kita menemukannya? Jauh lebih mudah membayangkan aktor melakukan peran tersebut dan mengaitkan peserta kita dengan para aktor tersebut.
Jadi, jika peserta kita mendefinisikan alasan perubahan, para aktor menentukan peserta. Ini sangat membantu kita untuk mengurangi konsep orang yang konkrit seperti "John the architect" untuk Arsitektur, atau "Mary the referent" untuk Pengerjaan.
Jadi responsibility adalah sebuah keluarga fungsi yang melayani satu aktor tertentu. (Robert C. Martin)
Sumber Perubahan
Dalam arti dari pemikiran ini, aktor menjadi sumber perubahan untuk keluarga fungsi yang melayani mereka. Ketika kebutuhan mereka berubah, keluarga fungsi yang spesifik juga harus berubah untuk mengakomodasi kebutuhan mereka.
Aktor untuk responsibility adalah satu-satunya sumber perubahan untuk responsibility itu. (Robert C. Martin)
Contoh Klasik
Objek Yang Dapat "Mencetak" Diri Sendiri
Mari katakan bahwa kita memiliki class Book
yang mengenkapsulasi konsep buku dan fungsinya.
class Book { function getTitle() { return "A Great Book"; } function getAuthor() { return "John Doe"; } function turnPage() { // pointer to next page } function printCurrentPage() { echo "current page content"; } }
Ini mungkin tampak seperti sebuah class yang wajar. Kita memiliki buku, ini dapat menyediakan judul, penulis dan dapat mengubah halaman. Akhirnya, hal ini juga mampu mencetak halaman aktif pada layar. Tetapi ada sedikit masalah. Jika kita berpikir tentang aktor yang terlibat dalam operasi objek Buku
, siapa mereka? Kita dapat dengan mudah memikirkan dua aktor yang berbeda di sini: Buku Manajemen (seperti pustakawan) dan Mekanisme Presentasi Data (seperti cara kita ingin menyampaikan konten ke pengguna - layar, grafis UI, hanya-teks UI, mungkin pencetakan). Berikut adalah dua aktor yang sangat berbeda.
Pencampuran logika bisnis dengan presentasi adalah buruk karena ini melawan Single Responsibility Principle (SRP). Lihatlah kode berikut:
class Book { function getTitle() { return "A Great Book"; } function getAuthor() { return "John Doe"; } function turnPage() { // pointer to next page } function getCurrentPage() { return "current page content"; } } interface Printer { function printPage($page); } class PlainTextPrinter implements Printer { function printPage($page) { echo $page; } } class HtmlPrinter implements Printer { function printPage($page) { echo '<div style="single-page">' . $page . '</div>'; } }
Bahkan contoh ini sangat mendasar yang menunjukkan bagaimana memisahkan presentasi dari logika bisnis, dan mematuhi SRP, memberikan keuntungan besar dalam fleksibilitas desain kita.
Objek Yang Dapat "Menyimpan" Dirinya Sendiri
Contoh serupa dengan yang di atas adalah ketika sebuah objek dapat menyimpan dan mengambil dirinya sendiri dari presentasi.
class Book { function getTitle() { return "A Great Book"; } function getAuthor() { return "John Doe"; } function turnPage() { // pointer to next page } function getCurrentPage() { return "current page content"; } function save() { $filename = '/documents/'. $this->getTitle(). ' - ' . $this->getAuthor(); file_put_contents($filename, serialize($this)); } }
Kita bisa, sekali lagi mengidentifikasi beberapa aktor seperti Sistem Manajemen Buku dan Persistent. Setiap kali kita ingin mengubah ketekunan, kita perlu mengubah class ini. Setiap kali kita ingin mengubah bagaimana kita mendapatkan dari satu halaman ke halaman berikutnya, kita harus memodifikasi class ini. Ada beberapa sumbu perubahan di sini.
class Book { function getTitle() { return "A Great Book"; } function getAuthor() { return "John Doe"; } function turnPage() { // pointer to next page } function getCurrentPage() { return "current page content"; } } class SimpleFilePersistence { function save(Book $book) { $filename = '/documents/' . $book->getTitle() . ' - ' . $book->getAuthor(); file_put_contents($filename, serialize($book)); } }
Yang memindahkan operasi persistent untuk class lain jelas akan memisahkan responsibiliti dan kita akan bebas untuk menukar metode persistent tanpa mempengaruhi class Book
kita. Misalnya mengimplementasi suatu class DatabasePersistence
akan biasa saja dan logika bisnis kita dibangun di sekitar operasi dengan buku yang tidak akan berubah.
Pandangan Tingkat Tinggi
Dalam artikel saya sebelumnya sering saya singgung dan disajikan dengan skema arsitektur tingkat tinggi yang dapat dilihat di bawah ini.



Jika kita menganalisis skema ini, Anda dapat melihat bagaimana Prinsip Single Responsibility dipatuhi. Pembuatan objek dipisahkan di sebelah kanan di Factory dan pintu masuk utama aplikasi kita, satu aktor satu tanggung jawab. Persistent juga diurus di bagian bawah. Modul terpisah untuk responsibiliti terpisah. Akhirnya, di sebelah kiri, kita memiliki presentasi atau mekanisme pengiriman jika Anda mau, dalam bentuk MVC atau tipe UI lainnya. SRP kembali dipatuhi. Yang tersisa hanyalah mencari tahu apa yang harus dilakukan dalam logika bisnis kita.
Pertimbangan Desain Perangkat Lunak
Bila kita memikirkan perangkat lunak yang perlu kita tulis, kita bisa menganalisa banyak aspek yang berbeda. Misalnya, beberapa persyaratan yang mempengaruhi class yang sama dapat mewakili sumbu perubahan. Sumbu perubahan ini mungkin merupakan petunjuk untuk single responsibility. Ada kemungkinan tinggi bahwa kelompok persyaratan yang mempengaruhi kelompok fungsi yang sama akan memiliki alasan untuk berubah atau ditentukan di tempat pertama.
Nilai utama perangkat lunak adalah kemudahan perubahan. Yang sekunder adalah fungsionalitas, dalam arti memuaskan sebanyak mungkin persyaratan, memenuhi kebutuhan pengguna. Namun, untuk mencapai nilai sekunder yang tinggi, nilai utama adalah wajib. Agar nilai utama tetap tinggi, kita harus memiliki desain yang mudah diubah, diperluas, untuk mengakomodasi fungsionalitas baru dan untuk memastikan bahwa SRP dipatuhi.
Kita bisa beralasan dengan langkah demi langkah:
- Nilai utama yang tinggi mengarah pada waktu ke nilai sekunder yang tinggi.
- Nilai sekunder berarti kebutuhan pengguna.
- Kebutuhan pengguna berarti kebutuhan para aktor.
- Kebutuhan para aktor menentukan kebutuhan perubahan aktor tersebut.
- Kebutuhan perubahan aktor mendefinisikan responsibiliti kita.
Jadi ketika kita merancang perangkat lunak kita, kita harus:
- Temukan dan definisikan para aktor.
- Identifikasi responsibiliti yang melayani aktor tersebut.
- Kelompokkan fungsi dan class kita sehingga masing-masing hanya memiliki satu tanggung jawab yang dialokasikan.
Contoh yang Kurang Jelas
class Book { function getTitle() { return "A Great Book"; } function getAuthor() { return "John Doe"; } function turnPage() { // pointer to next page } function getCurrentPage() { return "current page content"; } function getLocation() { // returns the position in the library // ie. shelf number & room number } }
Sekarang ini mungkin tampak masuk akal. Kami tidak memiliki metode yang berhubungan dengan persistence, atau presentasi. Kita memiliki fungsionalitas turnPage()
dan beberapa metode untuk memberikan informasi yang berbeda tentang buku ini. Namun, kita mungkin punya masalah. Untuk mengetahuinya, kita mungkin ingin menganalisis aplikasi kita. Fungsi getLocation()
mungkin menjadi masalah.
Semua metode class Book
adalah tentang logika bisnis. Jadi perspektif kita harus dari sudut pandang bisnis. Jika aplikasi kita ditulis untuk digunakan oleh pustakawan nyata yang sedang mencari buku dan memberi kita buku fisik, maka SRP mungkin dilanggar.
Kita dapat beralasan bahwa operasi aktor adalah yang tertarik dengan metode getTitle()
, getAuthor()
dan getLocation()
. Klien mungkin juga memiliki akses ke aplikasi untuk memilih buku dan membaca beberapa halaman pertama untuk mendapatkan ide tentang buku tersebut dan memutuskan apakah mereka menginginkannya atau tidak. Jadi pembaca aktor mungkin tertarik pada semua metode kecuali getLocations()
. Klien biasa tidak peduli di mana buku itu disimpan di perpustakaan. Buku ini akan diserahkan ke klien oleh pustakawan. Jadi, kita memang memiliki pelanggaran terhadap SRP.
class Book { function getTitle() { return "A Great Book"; } function getAuthor() { return "John Doe"; } function turnPage() { // pointer to next page } function getCurrentPage() { return "current page content"; } } class BookLocator { function locate(Book $book) { // returns the position in the library // ie. shelf number & room number $libraryMap->findBookBy($book->getTitle(), $book->getAuthor()); } }
Memperkenalkan BookLocator
, pustakawan akan tertarik dengan BookLocator
. Klien hanya akan tertarik pada Book
. Tentu saja, ada beberapa cara untuk menerapkan BookLocator
. Ini bisa menggunakan penulis dan judul atau objek buku dan mendapatkan informasi yang dibutuhkan dari Book
. Ini selalu tergantung pada bisnis kita. Yang penting adalah jika perpustakaan diubah, dan pustakawan harus menemukan buku di perpustakaan yang berbeda, objek Book
tidak akan terpengaruh. Dengan cara yang sama, jika kita memutuskan untuk memberikan ringkasan yang telah disusun sebelumnya kepada pembaca dan bukan membiarkan mereka menelusuri halaman, itu tidak akan mempengaruhi pustakawan maupun proses menemukan rak buku-buku itu.
Namun, jika bisnis kita adalah untuk menghilangkan pustakawan dan menciptakan mekanisme ambil sendiri di perpustakaan kita, maka kita dapat mempertimbangkan bahwa SRP dipatuhi dalam contoh pertama kita. Pembaca adalah pustakawan kita juga, mereka harus pergi dan menemukan buku itu sendiri dan kemudian memeriksanya di sistem otomatis. Ini juga kemungkinan. Yang penting untuk diingat di sini adalah Anda harus selalu mempertimbangkan bisnis Anda dengan saksama.
Pemikiran Akhir
Prinsip Single Responsibility harus selalu dipertimbangkan saat kita menulis kode. Desain class dan modul sangat dipengaruhi olehnya dan ini mengarah pada desain low couple dengan dependensi yang lebih sedikit dan lebih ringan. Tetapi seperti koin, ada dua wajah. Sangat menggoda untuk merancang dari awal aplikasi kita dengan SRP. Hal ini juga menggoda untuk mengidentifikasi sebanyak mungkin aktor yang kita inginkan atau butuhkan. Tapi ini sebenarnya berbahaya - dari sudut pandang desain - untuk mencoba dan memikirkan semua pihak sejak awal. Pertimbangan SRP yang berlebihan dapat dengan mudah mengarah pada optimasi prematur dan bukannya desain yang lebih baik, hal itu dapat mengarah pada masalah yang tersebar dimana responsibiliti yang jelas dari class atau modul mungkin akan sulit dimengerti.
Jadi, setiap kali Anda mengamati bahwa class atau modul mulai berubah karena alasan yang berbeda, jangan ragu-ragu, ambillah langkah-langkah yang diperlukan untuk mematuhi SRP, namun jangan terlambat karena optimasi prematur dapat dengan mudah menipu Anda.
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weekly