Prinsip SOLID dan struktur kode

150

Pada wawancara kerja baru-baru ini, saya tidak bisa menjawab pertanyaan tentang SOLID - selain memberikan makna dasar dari berbagai prinsip. Itu benar-benar menggangguku. Saya telah melakukan beberapa hari untuk menggali dan belum membuat ringkasan yang memuaskan.

Pertanyaan wawancara adalah:

Jika Anda melihat proyek .Net yang saya katakan benar-benar mengikuti prinsip-prinsip SOLID, apa yang akan Anda lihat dalam hal proyek dan struktur kode?

Saya sedikit bingung, tidak benar-benar menjawab pertanyaan, dan kemudian dibom.

Bagaimana saya bisa menangani pertanyaan ini dengan lebih baik?

S-Unit
sumber
3
Saya bertanya-tanya apa yang tidak jelas di halaman wiki untuk SOLID
BЈовић
Blok Bangunan Abstrak Extensible.
rwong
Dengan mengikuti Prinsip-prinsip SOLID dari Desain Berorientasi Objek, kelas-kelas Anda secara alami akan cenderung kecil, diperhitungkan dengan baik, dan mudah diuji. Sumber: docs.asp.net/en/latest/fundamentals/…
WhileTrueSleep

Jawaban:

188

S = Prinsip Tanggung Jawab Tunggal

Jadi saya berharap melihat struktur folder / file yang terorganisir dengan baik & Object Hierarchy. Setiap kelas / bagian dari fungsionalitas harus dinamai bahwa fungsinya sangat jelas, dan seharusnya hanya berisi logika untuk melakukan tugas itu.

Jika Anda melihat kelas manajer besar dengan ribuan baris kode, itu akan menjadi pertanda bahwa tanggung jawab tunggal tidak diikuti.

O = Prinsip Terbuka / tertutup

Ini pada dasarnya adalah gagasan bahwa fungsionalitas baru harus ditambahkan melalui kelas-kelas baru yang memiliki dampak minimal pada / memerlukan modifikasi fungsionalitas yang ada.

Saya berharap dapat melihat banyak penggunaan objek warisan, sub-mengetik, antarmuka dan kelas abstrak untuk memisahkan desain sepotong fungsi dari implementasi aktual, memungkinkan orang lain untuk datang dan mengimplementasikan versi lain di samping tanpa mempengaruhi asli.

L = Prinsip substitusi Liskov

Ini berkaitan dengan kemampuan untuk memperlakukan sub-tipe sebagai tipe induknya. Ini keluar dari kotak di C # jika Anda menerapkan hierarki objek yang diwariskan yang tepat.

Saya berharap melihat kode memperlakukan objek umum sebagai tipe dasar dan metode panggilan pada kelas dasar / abstrak daripada membuat instance dan bekerja pada sub-tipe itu sendiri.

I = Prinsip Segregasi Antarmuka

Ini mirip dengan SRP. Pada dasarnya, Anda mendefinisikan himpunan bagian kecil dari fungsi sebagai antarmuka dan bekerja dengan mereka untuk menjaga sistem Anda dipisahkan (misalnya FileManagermungkin memiliki tanggung jawab tunggal berurusan dengan File I / O, tetapi yang dapat mengimplementasikan IFileReaderdan IFileWriteryang berisi definisi metode khusus untuk membaca) dan penulisan file).

D = Prinsip Pembalikan Ketergantungan.

Sekali lagi ini berkaitan dengan menjaga sistem dipisahkan. Mungkin Anda akan mencari penggunaan perpustakaan .NET Dependency Injection, yang digunakan dalam solusi seperti Unityatau Ninjectatau sistem ServiceLocator seperti AutoFacServiceLocator.

Eoin Campbell
sumber
36
Saya telah melihat banyak pelanggaran LSP dalam C #, setiap kali seseorang memutuskan subtipe khusus mereka adalah khusus dan oleh karena itu tidak perlu mengimplementasikan sepotong antarmuka dan hanya melemparkan pengecualian pada bagian itu sebagai gantinya ... Ini adalah pendekatan umum yunior untuk memecahkan masalah implementasi dan desain antarmuka yang salah paham
Jimmy Hoffa
2
@JimmyHoffa Itulah salah satu alasan utama saya berkeras menggunakan Kontrak Kode; melalui proses pemikiran merancang kontrak membantu banyak orang keluar dari kebiasaan buruk itu.
Andy
12
Saya tidak suka "LSP keluar dari kotak di C #" dan menyamakan DIP dengan praktik injeksi Ketergantungan.
Euforia
3
+1 tetapi Pembalikan Ketergantungan <> Injeksi Ketergantungan. Mereka bermain dengan baik bersama-sama, tetapi inversi ketergantungan lebih dari sekadar injeksi ketergantungan. Referensi: DIP di alam liar
Marjan Venema
3
@Andy: Apa yang membantu juga adalah unit test didefinisikan pada interface yang diuji semua implementer (kelas apa pun yang dapat / dipakai).
Marjan Venema
17

Banyak kelas kecil dan antarmuka dengan injeksi ketergantungan di semua tempat. Mungkin dalam proyek besar Anda juga akan menggunakan kerangka kerja IoC untuk membantu Anda membangun dan mengelola masa hidup semua benda kecil itu. Lihat https://stackoverflow.com/questions/21288/which-net-dependency-injection-frameworks-are-worth-looking-into

Perhatikan bahwa proyek .NET besar yang KERAS mengikuti prinsip-prinsip SOLID tidak selalu berarti basis kode yang baik untuk bekerja dengan semua orang. Tergantung pada siapa pewawancara itu, dia mungkin ingin Anda menunjukkan bahwa Anda memahami apa arti SOLID dan / atau memeriksa seberapa dogmatisnya Anda mengikuti prinsip-prinsip desain.

Anda lihat, untuk menjadi SOLID, Anda harus mengikuti:

S prinsip tanggung jawab perapian di tungku, sehingga Anda akan memiliki banyak kelas kecil masing-masing melakukan satu hal saja

Prinsip O pen-closed, yang dalam. NET biasanya diimplementasikan dengan injeksi dependensi, yang juga membutuhkan I dan D di bawah ...

Prinsip substitusi L iskov mungkin tidak mungkin dijelaskan dalam c # dengan one-liner. Untungnya ada pertanyaan lain yang mengatasinya, misalnya https://stackoverflow.com/questions/4428725/can-you-explain-liskov-substitusi-principle-with-a-good-c-sharp-example

Saya menerapkan Prinsip Segregasi bekerja bersama dengan prinsip Terbuka-Tertutup. Jika diikuti secara harfiah itu berarti lebih memilih sejumlah besar antarmuka yang sangat kecil daripada sedikit antarmuka "besar"

D ependency inversi prinsip kelas tingkat tinggi tidak harus bergantung pada kelas tingkat rendah, keduanya harus bergantung pada abstraksi.

Paolo Falabella
sumber
SRP tidak berarti "melakukan satu hal saja."
Robert Harvey
13

Beberapa hal dasar yang saya harapkan untuk dilihat di basis kode toko yang mendukung SOLID dalam pekerjaan sehari-hari mereka:

  • Banyak file kode kecil - dengan satu kelas per file sebagai praktik terbaik dalam .NET, dan Prinsip Tanggung Jawab Tunggal mendorong struktur kelas modular kecil, saya berharap melihat banyak file masing-masing berisi satu kelas kecil yang fokus.
  • Banyak pola Adaptor dan Komposit - Saya berharap penggunaan banyak pola Adaptor (kelas yang mengimplementasikan satu antarmuka dengan "melewati" ke fungsi antarmuka yang berbeda) untuk menyederhanakan penyumbatan dalam ketergantungan yang dikembangkan untuk satu tujuan menjadi sedikit tempat berbeda di mana fungsinya juga diperlukan. Pembaruan sesederhana mengganti logger konsol dengan file logger akan melanggar LSP / ISP / DIP jika antarmuka diperbarui untuk mengekspos cara untuk menentukan nama file yang akan digunakan; sebagai gantinya, kelas file logger akan mengekspos anggota tambahan, dan kemudian Adaptor akan membuat file logger terlihat seperti konsol logger dengan menyembunyikan barang baru, jadi hanya objek yang memecah semua ini bersama-sama yang harus mengetahui perbedaannya.

    Demikian pula, ketika sebuah kelas perlu menambahkan ketergantungan dari antarmuka yang sama seperti yang sudah ada, untuk menghindari mengubah objek (OCP), jawaban yang biasa adalah untuk menerapkan pola Komposit / Strategi (kelas yang mengimplementasikan antarmuka ketergantungan dan mengkonsumsi beberapa lainnya. implementasi dari antarmuka itu, dengan berbagai jumlah logika yang memungkinkan kelas untuk meneruskan panggilan ke satu, beberapa, atau semua implementasi).

  • Banyak antarmuka dan ABC - DIP harus mensyaratkan bahwa abstraksi ada, dan ISP mendorong ini untuk dibatasi secara sempit. Oleh karena itu, antarmuka dan kelas dasar abstrak adalah aturannya, dan Anda akan membutuhkan banyak dari mereka untuk membahas fungsionalitas dependensi bersama dari basis kode Anda. Sementara SOLID ketat akan mengharuskan menyuntikkan segalanya , jelas Anda harus membuat suatu tempat, dan jadi jika bentuk GUI hanya pernah dibuat sebagai anak dari satu bentuk orang tua dengan melakukan beberapa tindakan pada orangtua tersebut, saya tidak punya keraguan untuk memulai bentuk anak dari kode langsung di dalam induknya. Saya biasanya hanya membuat kode itu metode sendiri, jadi jika dua tindakan dari bentuk yang sama pernah membuka jendela, saya hanya memanggil metode itu.
  • Banyak proyek - Inti dari semua ini adalah membatasi ruang lingkup perubahan. Perubahan mencakup perlu mengkompilasi ulang (latihan yang relatif sepele lagi, tetapi masih penting dalam banyak operasi penting prosesor dan bandwidth, seperti menyebarkan pembaruan ke lingkungan seluler). Jika satu file dalam proyek harus dibangun kembali, semua file melakukannya. Itu berarti jika Anda menempatkan antarmuka di perpustakaan yang sama dengan implementasinya, Anda kehilangan intinya; Anda harus mengkompilasi ulang semua penggunaan jika Anda mengubah implementasi antarmuka karena Anda juga akan mengkompilasi ulang definisi antarmuka itu sendiri, mengharuskan penggunaan untuk menunjuk ke lokasi baru di biner yang dihasilkan. Oleh karena itu, menjaga antarmuka terpisah dari penggunaan dan implementasi, sementara tambahan memisahkan mereka berdasarkan area penggunaan umum, adalah praktik terbaik yang khas.
  • Banyak perhatian diberikan kepada terminologi "Geng Empat" - Pola desain yang diidentifikasi dalam buku Pola Desain tahun 1994 menekankan pada ukuran kecil, desain kode modular yang ingin diciptakan SOLID. Prinsip Ketergantungan Inversi dan Prinsip Terbuka / Tertutup, misalnya, adalah jantung dari sebagian besar pola yang diidentifikasi dalam buku itu. Karena itu, saya berharap sebuah toko yang sangat mematuhi prinsip-prinsip SOLID juga merangkul terminologi dalam buku Gang of Four, dan menamai kelas-kelas sesuai dengan fungsinya di sepanjang baris tersebut, seperti "AbcFactory", "XyzRepository", "DefToXyzAdapter "," Perintah A1C "dll.
  • Repositori generik - Sesuai dengan ISP, DIP dan SRP seperti yang dipahami secara umum, Repositori hampir ada di mana-mana dalam desain SOLID, karena memungkinkan pemakai kode untuk meminta kelas data secara abstrak tanpa perlu pengetahuan khusus tentang mekanisme pengambilan / ketekunan, dan itu menempatkan kode yang melakukan ini di satu tempat yang bertentangan dengan pola DAO (di mana jika Anda memiliki, misalnya, kelas data Faktur, Anda juga akan memiliki InvoiceDAO yang menghasilkan objek terhidrasi dari jenis itu, dan seterusnya untuk semua objek data / tabel dalam basis kode / skema).
  • Kontainer IoC - Saya ragu untuk menambahkan ini, karena saya sebenarnya tidak menggunakan kerangka kerja IoC untuk melakukan sebagian besar injeksi ketergantungan saya. Itu dengan cepat menjadi anti-pola Objek Allah dari membuang segala sesuatu ke dalam wadah, mengocoknya dan menuangkan ketergantungan terhidrasi secara vertikal yang Anda butuhkan melalui metode pabrik yang disuntikkan. Kedengarannya hebat, sampai Anda menyadari bahwa struktur menjadi sangat monolitik, dan proyek dengan info pendaftaran, jika "lancar", sekarang harus tahu segalanya tentang segala sesuatu dalam solusi Anda. Itu banyak alasan untuk berubah. Jika tidak lancar (pendaftaran akhir menggunakan file config), maka bagian penting dari program Anda bergantung pada "string ajaib", yang sama sekali berbeda dapat berupa worm.
KeithS
sumber
1
mengapa downvotes?
KeithS
Saya pikir ini adalah jawaban yang bagus. Alih-alih mirip dengan banyak posting blog tentang istilah-istilah ini, Anda telah mencantumkan contoh dan penjelasan yang menunjukkan penggunaan dan nilainya
Crowie
10

Mengalihkan perhatian mereka dengan diskusi Jon Skeet tentang bagaimana 'O' di SOLID adalah "tidak membantu dan kurang dipahami" dan membuat mereka berbicara tentang "variasi yang dilindungi" Alistair Cockburn dan "desain warisan" milik Josh Bloch "atau melarangnya".

Ringkasan singkat dari artikel Skeet (meskipun saya tidak akan merekomendasikan menjatuhkan namanya tanpa membaca posting blog asli!):

  • Kebanyakan orang tidak tahu apa arti 'prinsip terbuka' dan 'tertutup' dalam 'prinsip terbuka-tertutup', walaupun mereka mengira begitu.
  • Interpretasi umum meliputi:
    • bahwa modul harus selalu diperluas melalui warisan implementasi, atau
    • bahwa kode sumber modul asli tidak pernah dapat diubah.
  • Maksud yang mendasari OCP, dan rumusan asli Bertrand Meyer tentang hal itu, baik-baik saja:
    • bahwa modul harus memiliki antarmuka yang terdefinisi dengan baik (tidak harus dalam arti teknis 'antarmuka') yang dapat diandalkan oleh klien mereka, tetapi
    • seharusnya bisa memperluas apa yang dapat mereka lakukan tanpa merusak antarmuka tersebut.
  • Tetapi kata-kata "terbuka" dan "tertutup" hanya membingungkan masalah, bahkan jika mereka membuat singkatan yang dapat diucapkan.

OP bertanya, "Bagaimana saya bisa menangani pertanyaan ini dengan lebih baik?" Sebagai seorang insinyur senior yang melakukan wawancara, saya akan jauh lebih tertarik pada seorang kandidat yang dapat berbicara secara cerdas tentang pro dan kontra dari gaya desain kode yang berbeda daripada seseorang yang dapat mengeluarkan daftar poin-poin.

Jawaban lain yang baik adalah, "Yah, itu tergantung pada seberapa baik mereka memahaminya. Jika yang mereka tahu adalah kata-kata SOLID, saya akan berharap penyalahgunaan warisan, terlalu banyak menggunakan kerangka injeksi ketergantungan, satu juta antarmuka kecil tidak ada yang mencerminkan kosakata domain yang digunakan untuk berkomunikasi dengan manajemen produk .... "

David Moles
sumber
6

Mungkin ada sejumlah cara yang dapat dijawab dengan jumlah waktu yang bervariasi. Namun, saya pikir ini lebih sesuai dengan kalimat "Apakah Anda tahu apa artinya SOLID?" Jadi menjawab pertanyaan ini mungkin hanya bermula untuk mencapai poin dan menjelaskannya dalam hal proyek.

Jadi, Anda berharap melihat yang berikut:

  • Kelas memiliki Tanggung jawab tunggal (mis. Kelas akses data untuk pelanggan hanya akan mendapatkan data pelanggan dari basis data pelanggan).
  • Kelas mudah diperpanjang tanpa mempengaruhi perilaku yang ada. Saya tidak perlu mengubah properti atau metode lain untuk menambah fungsionalitas tambahan.
  • Kelas turunan dapat diganti dengan kelas dasar dan fungsi yang menggunakan kelas dasar itu tidak harus membuka kelas dasar ke tipe yang lebih spesifik untuk menanganinya.
  • Antarmuka kecil dan mudah dimengerti. Jika sebuah kelas menggunakan antarmuka, itu tidak perlu bergantung pada beberapa metode untuk menyelesaikan tugas.
  • Kode cukup abstrak sehingga implementasi tingkat tinggi tidak secara konkret bergantung pada implementasi tingkat rendah tertentu. Saya harus bisa mengganti implementasi level rendah tanpa memengaruhi kode level tinggi. Sebagai contoh, saya bisa mengganti lapisan akses data SQL saya dengan layanan berbasis Web tanpa memengaruhi aplikasi saya yang lain.
villecoder
sumber
4

Ini adalah pertanyaan yang sangat bagus, meskipun saya pikir ini adalah pertanyaan wawancara yang sulit.

Prinsip-prinsip SOLID benar-benar mengatur kelas dan antarmuka dan bagaimana mereka terkait satu sama lain.

Pertanyaan ini benar-benar salah satu yang lebih berkaitan dengan file dan belum tentu kelas.

Pengamatan atau jawaban singkat yang akan saya berikan adalah bahwa secara umum Anda akan melihat file yang hanya memiliki antarmuka, dan sering konvensi adalah bahwa mereka mulai dengan huruf kapital-I. Di luar itu, saya akan menyebutkan bahwa file tidak akan memiliki kode duplikat (terutama di dalam modul, aplikasi, atau perpustakaan), dan kode itu akan dibagikan secara hati-hati melintasi batas-batas tertentu antara modul, aplikasi, atau perpustakaan.

Robert Martin membahas topik ini dalam ranah C ++ dalam Merancang Aplikasi C ++ Berorientasi Objek Menggunakan Metode Booch (lihat bagian tentang Kohesi, Penutupan, dan Dapat Digunakan Kembali) dan dalam Kode Bersih .

J. Polfer
sumber
.NET coders IME biasanya mengikuti aturan "1 class per file", dan juga struktur mirror folder / namespace; Visual Studio IDE mendorong kedua praktik, dan berbagai plugin seperti ReSharper dapat menegakkannya. Jadi, saya akan mengharapkan untuk melihat struktur proyek / file yang mencerminkan struktur kelas / antarmuka.
KeithS