Menulis Kode yang Fleksibel Menggunakan SOLID



Dari seorang penerjemah: Severin Peres menerbitkan sebuah artikel tentang penggunaan prinsip-prinsip SOLID dalam pemrograman untuk Anda. Informasi dari artikel ini akan berguna bagi pemula dan programmer yang berpengalaman.

Jika Anda seorang pengembang, Anda mungkin pernah mendengar tentang prinsip-prinsip SOLID. Mereka memungkinkan programmer untuk menulis kode yang bersih, terstruktur dengan baik dan mudah dirawat. Perlu dicatat bahwa dalam pemrograman ada beberapa pendekatan untuk bagaimana melakukan pekerjaan ini atau itu dengan benar. Spesialis yang berbeda memiliki ide dan pemahaman yang berbeda tentang "cara yang benar", semuanya tergantung pada pengalaman masing-masing orang. Namun demikian, gagasan yang diproklamirkan dalam SOLID diterima oleh hampir semua perwakilan komunitas TI. Mereka menjadi titik awal untuk munculnya dan pengembangan banyak praktik manajemen pembangunan yang baik.

Mari kita lihat apa prinsip-prinsip SOLID dan bagaimana mereka membantu kita.

Skillbox merekomendasikan: Kursus praktis "Pengembang Mobile PRO" .

Kami mengingatkan Anda: untuk semua pembaca "Habr" - diskon 10.000 rubel saat mendaftar untuk kursus Skillbox apa pun menggunakan kode promo "Habr".

Apa itu SOLID?


Istilah ini adalah singkatan, setiap huruf dari istilah ini adalah awal dari nama prinsip tertentu:
  • Prinsip Tanggung Jawab Tunggal. Modul dapat memiliki satu dan hanya satu alasan untuk perubahan.
  • O pena / Prinsip Tertutup . Kelas dan elemen lainnya harus terbuka untuk ekspansi, tetapi ditutup untuk modifikasi.
  • Prinsip Substitusi L iskov . Fungsi yang menggunakan tipe dasar harus dapat menggunakan subtipe dari tipe dasar tanpa menyadarinya.
  • Prinsip Segregasi Iterface . Entitas perangkat lunak tidak boleh bergantung pada metode yang tidak mereka gunakan.
  • Prinsip Pembalikan Ketergantungan . Modul tingkat atas tidak harus bergantung pada modul tingkat bawah.

Prinsip tanggung jawab tunggal


Prinsip tanggung jawab tunggal (SRP) menyatakan bahwa setiap kelas atau modul dalam suatu program harus bertanggung jawab hanya untuk satu bagian dari fungsionalitas program ini. Selain itu, elemen tanggung jawab ini harus ditugaskan ke kelas mereka, dan tidak didistribusikan di antara kelas yang tidak terkait. Pengembang dan pemimpin penginjil SRP, Robert S. Martin, menggambarkan tanggung jawab sebagai penyebab perubahan. Awalnya, ia mengusulkan istilah ini sebagai salah satu elemen dari karyanya, "Prinsip Desain Berorientasi Objek". Konsep tersebut mencakup banyak pola konektivitas yang sebelumnya ditentukan oleh Tom Demarco.

Konsep tersebut juga memasukkan beberapa konsep yang dirumuskan oleh David Parnassus. Dua yang utama adalah enkapsulasi dan penyembunyian informasi. Parnassus berpendapat bahwa membagi sistem ke dalam modul yang terpisah tidak harus didasarkan pada analisis diagram alur atau aliran eksekusi. Setiap modul harus berisi solusi spesifik yang memberikan informasi minimum kepada pelanggan.

Omong-omong, Martin memberikan contoh yang menarik dengan manajer senior perusahaan (COO, CTO, CFO), yang masing-masing menggunakan perangkat lunak khusus untuk bisnis dengan tujuan yang berbeda. Akibatnya, salah satu dari mereka dapat menerapkan perubahan dalam perangkat lunak tanpa memengaruhi minat manajer lain.

Objek ilahi


Seperti biasa, cara terbaik untuk mempelajari SRP adalah dengan melihat segala sesuatu beraksi. Mari kita lihat bagian dari program yang TIDAK mematuhi prinsip tanggung jawab bersama. Ini adalah kode Ruby yang menjelaskan perilaku dan atribut stasiun ruang angkasa.

Lihat contoh dan coba tentukan yang berikut:
Tanggung jawab objek-objek yang dideklarasikan di kelas SpaceStation.
Mereka yang mungkin tertarik dengan pekerjaan stasiun luar angkasa.

class SpaceStation def initialize @supplies = {} @fuel = 0 end def run_sensors puts "----- Sensor Action -----" puts "Running sensors!" end def load_supplies(type, quantity) puts "----- Supply Action -----" puts "Loading #{quantity} units of #{type} in the supply hold." if @supplies[type] @supplies[type] += quantity else @supplies[type] = quantity end end def use_supplies(type, quantity) puts "----- Supply Action -----" if @supplies[type] != nil && @supplies[type] > quantity puts "Using #{quantity} of #{type} from the supply hold." @supplies[type] -= quantity else puts "Supply Error: Insufficient #{type} in the supply hold." end end def report_supplies puts "----- Supply Report -----" if @supplies.keys.length > 0 @supplies.each do |type, quantity| puts "#{type} avalilable: #{quantity} units" end else puts "Supply hold is empty." end end def load_fuel(quantity) puts "----- Fuel Action -----" puts "Loading #{quantity} units of fuel in the tank." @fuel += quantity end def report_fuel puts "----- Fuel Report -----" puts "#{@fuel} units of fuel available." end def activate_thrusters puts "----- Thruster Action -----" if @fuel >= 10 puts "Thrusting action successful." @fuel -= 10 else puts "Thruster Error: Insufficient fuel available." end end end 

Sebenarnya, stasiun ruang angkasa kami tidak berfungsi (saya pikir saya tidak akan menerima panggilan dari NASA dalam waktu dekat yang dapat diperkirakan), tetapi ada sesuatu untuk dianalisis.

Jadi, kelas SpaceStation memiliki beberapa tanggung jawab (atau tugas) yang berbeda. Semuanya dapat dibagi menjadi beberapa tipe:
  • Sensor
  • pasokan (barang habis pakai);
  • bahan bakar;
  • akselerator.

Terlepas dari kenyataan bahwa tidak ada karyawan stasiun didefinisikan di kelas, kita dapat dengan mudah membayangkan siapa yang bertanggung jawab untuk apa. Kemungkinan besar, ilmuwan mengendalikan sensor, ahli logistik bertanggung jawab atas pasokan sumber daya, insinyur bertanggung jawab atas pasokan bahan bakar, dan pilot mengendalikan akselerator.

Bisakah kita mengatakan bahwa program ini tidak sesuai dengan SRP? Ya tentu saja Tetapi kelas SpaceStation adalah "objek ilahi" yang khas yang tahu segalanya dan melakukan segalanya. Ini adalah anti-pola utama dalam pemrograman berorientasi objek. Bagi seorang pemula, benda-benda seperti itu sangat sulit dipertahankan. Sejauh ini, program ini sangat sederhana, ya, tetapi bayangkan apa yang akan terjadi jika kita menambahkan fitur baru. Mungkin stasiun ruang angkasa kita akan membutuhkan pusat medis atau ruang pertemuan. Dan semakin banyak fitur yang ada, semakin banyak SpaceStation akan tumbuh. Nah, karena objek ini akan terhubung dengan yang lain, pemeliharaan seluruh kompleks akan menjadi lebih rumit. Akibatnya, kita dapat mengganggu pekerjaan, misalnya, akselerator. Jika seorang peneliti meminta perubahan dalam bekerja dengan sensor, maka ini dapat mempengaruhi sistem komunikasi stasiun.

Pelanggaran terhadap prinsip SRP dapat memberikan kemenangan taktis jangka pendek, tetapi pada akhirnya kita akan "kalah perang", melayani monster seperti itu di masa depan akan sangat sulit. Yang terbaik adalah membagi program menjadi bagian-bagian kode yang terpisah, yang masing-masing bertanggung jawab untuk melakukan operasi tertentu. Dengan mengingat hal ini, mari kita ubah kelas SpaceStation.

Bagikan tanggung jawab

Di atas, kami mengidentifikasi empat jenis operasi yang dikendalikan oleh kelas SpaceStation. Saat refactoring, kami akan mengingatnya. Kode yang diperbarui lebih cocok dengan SRP.

 class SpaceStation attr_reader :sensors, :supply_hold, :fuel_tank, :thrusters def initialize @supply_hold = SupplyHold.new @sensors = Sensors.new @fuel_tank = FuelTank.new @thrusters = Thrusters.new(@fuel_tank) end end class Sensors def run_sensors puts "----- Sensor Action -----" puts "Running sensors!" end end class SupplyHold attr_accessor :supplies def initialize @supplies = {} end def load_supplies(type, quantity) puts "----- Supply Action -----" puts "Loading #{quantity} units of #{type} in the supply hold." if @supplies[type] @supplies[type] += quantity else @supplies[type] = quantity end end def use_supplies(type, quantity) puts "----- Supply Action -----" if @supplies[type] != nil && @supplies[type] > quantity puts "Using #{quantity} of #{type} from the supply hold." @supplies[type] -= quantity else puts "Supply Error: Insufficient #{type} in the supply hold." end end def report_supplies puts "----- Supply Report -----" if @supplies.keys.length > 0 @supplies.each do |type, quantity| puts "#{type} avalilable: #{quantity} units" end else puts "Supply hold is empty." end end end class FuelTank attr_accessor :fuel def initialize @fuel = 0 end def get_fuel_levels @fuel end def load_fuel(quantity) puts "----- Fuel Action -----" puts "Loading #{quantity} units of fuel in the tank." @fuel += quantity end def use_fuel(quantity) puts "----- Fuel Action -----" puts "Using #{quantity} units of fuel from the tank." @fuel -= quantity end def report_fuel puts "----- Fuel Report -----" puts "#{@fuel} units of fuel available." end end class Thrusters def initialize(fuel_tank) @linked_fuel_tank = fuel_tank end def activate_thrusters puts "----- Thruster Action -----" if @linked_fuel_tank.get_fuel_levels >= 10 puts "Thrusting action successful." @linked_fuel_tank.use_fuel(10) else puts "Thruster Error: Insufficient fuel available." end end end 

Ada banyak perubahan, program sekarang terlihat lebih baik. Sekarang kelas SpaceStation kami telah menjadi, lebih tepatnya, sebuah wadah di mana operasi untuk bagian yang bergantung dimulai, termasuk seperangkat sensor, sistem pasokan untuk bahan habis pakai, tangki bahan bakar, dan penguat.

Untuk salah satu variabel sekarang ada kelas yang sesuai: Sensor; SupplyHold; FuelTank Pendorong.

Ada beberapa perubahan penting pada versi kode ini. Faktanya adalah bahwa fungsi individu tidak hanya dienkapsulasi di kelas mereka sendiri, mereka diatur sedemikian rupa sehingga menjadi dapat diprediksi dan konsisten. Kami mengelompokkan elemen yang serupa dalam fungsionalitas untuk mengikuti prinsip konektivitas. Sekarang, jika kita perlu mengubah prinsip sistem dengan beralih dari struktur hash ke array, cukup gunakan kelas SupplyHold, kita tidak perlu menyentuh modul lain. Dengan demikian, jika petugas yang bertanggung jawab atas logistik mengubah sesuatu di bagiannya, elemen stasiun yang tersisa akan tetap tidak tersentuh. Pada saat yang sama, kelas SpaceStation bahkan tidak akan menyadari perubahan.

Petugas stasiun ruang angkasa kami cenderung senang dengan perubahan itu, karena mereka dapat meminta yang mereka butuhkan. Perhatikan bahwa kode memiliki metode seperti report_supplies dan report_fuel yang terkandung dalam kelas SupplyHold dan FuelTank. Apa yang terjadi jika Bumi meminta untuk mengubah cara laporan dihasilkan? Anda harus mengubah kedua kelas, SupplyHold dan FuelTank. Tetapi bagaimana jika Anda perlu mengubah cara Anda mengirimkan bahan bakar dan barang habis pakai? Anda mungkin harus mengubah semua kelas yang sama lagi. Dan ini merupakan pelanggaran prinsip SRP. Mari kita perbaiki.

 class SpaceStation attr_reader :sensors, :supply_hold, :supply_reporter, :fuel_tank, :fuel_reporter, :thrusters def initialize @sensors = Sensors.new @supply_hold = SupplyHold.new @supply_reporter = SupplyReporter.new(@supply_hold) @fuel_tank = FuelTank.new @fuel_reporter = FuelReporter.new(@fuel_tank) @thrusters = Thrusters.new(@fuel_tank) end end class Sensors def run_sensors puts "----- Sensor Action -----" puts "Running sensors!" end end class SupplyHold attr_accessor :supplies attr_reader :reporter def initialize @supplies = {} end def get_supplies @supplies end def load_supplies(type, quantity) puts "----- Supply Action -----" puts "Loading #{quantity} units of #{type} in the supply hold." if @supplies[type] @supplies[type] += quantity else @supplies[type] = quantity end end def use_supplies(type, quantity) puts "----- Supply Action -----" if @supplies[type] != nil && @supplies[type] > quantity puts "Using #{quantity} of #{type} from the supply hold." @supplies[type] -= quantity else puts "Supply Error: Insufficient #{type} in the supply hold." end end end class FuelTank attr_accessor :fuel attr_reader :reporter def initialize @fuel = 0 end def get_fuel_levels @fuel end def load_fuel(quantity) puts "----- Fuel Action -----" puts "Loading #{quantity} units of fuel in the tank." @fuel += quantity end def use_fuel(quantity) puts "----- Fuel Action -----" puts "Using #{quantity} units of fuel from the tank." @fuel -= quantity end end class Thrusters FUEL_PER_THRUST = 10 def initialize(fuel_tank) @linked_fuel_tank = fuel_tank end def activate_thrusters puts "----- Thruster Action -----" if @linked_fuel_tank.get_fuel_levels >= FUEL_PER_THRUST puts "Thrusting action successful." @linked_fuel_tank.use_fuel(FUEL_PER_THRUST) else puts "Thruster Error: Insufficient fuel available." end end end class Reporter def initialize(item, type) @linked_item = item @type = type end def report puts "----- #{@type.capitalize} Report -----" end end class FuelReporter < Reporter def initialize(item) super(item, "fuel") end def report super puts "#{@linked_item.get_fuel_levels} units of fuel available." end end class SupplyReporter < Reporter def initialize(item) super(item, "supply") end def report super if @linked_item.get_supplies.keys.length > 0 @linked_item.get_supplies.each do |type, quantity| puts "#{type} avalilable: #{quantity} units" end else puts "Supply hold is empty." end end end iss = SpaceStation.new iss.sensors.run_sensors # ----- Sensor Action ----- # Running sensors! iss.supply_hold.use_supplies("parts", 2) # ----- Supply Action ----- # Supply Error: Insufficient parts in the supply hold. iss.supply_hold.load_supplies("parts", 10) # ----- Supply Action ----- # Loading 10 units of parts in the supply hold. iss.supply_hold.use_supplies("parts", 2) # ----- Supply Action ----- # Using 2 of parts from the supply hold. iss.supply_reporter.report # ----- Supply Report ----- # parts avalilable: 8 units iss.thrusters.activate_thrusters # ----- Thruster Action ----- # Thruster Error: Insufficient fuel available. iss.fuel_tank.load_fuel(100) # ----- Fuel Action ----- # Loading 100 units of fuel in the tank. iss.thrusters.activate_thrusters # ----- Thruster Action ----- # Thrusting action successful. # ----- Fuel Action ----- # Using 10 units of fuel from the tank. iss.fuel_reporter.report # ----- Fuel Report ----- # 90 units of fuel available. 

Dalam versi terbaru dari program ini, tanggung jawab dibagi menjadi dua kelas baru, FuelReporter dan SupplyReporter. Mereka berdua adalah anak-anak dari kelas Reporter. Selain itu, kami menambahkan variabel instan ke kelas SpaceStation untuk menginisialisasi subkelas yang diperlukan jika perlu. Sekarang, jika Bumi memutuskan untuk mengubah sesuatu yang lain, maka kita akan membuat perubahan pada subclass, dan bukan ke kelas utama.

Tentu saja, beberapa kelas di sini masih saling bergantung. Jadi, objek SupplyReporter tergantung pada SupplyHold, dan FuelReporter tergantung pada FuelTank. Tentu saja, booster harus terhubung ke tangki bahan bakar. Tapi di sini semuanya terlihat logis, dan membuat perubahan tidak akan terlalu sulit - mengedit kode dari satu objek tidak akan terlalu banyak mempengaruhi yang lain.

Jadi, kami membuat kode modular di mana tanggung jawab masing-masing objek / kelas didefinisikan secara tepat. Bekerja dengan kode seperti itu bukan masalah, pemeliharaannya akan menjadi tugas yang sederhana. Seluruh "objek ilahi" yang telah kita konversi menjadi SRP.

Skillbox merekomendasikan:

Source: https://habr.com/ru/post/id442928/


All Articles