Nulis kode fleksibel nggunakake SOLID

Nulis kode fleksibel nggunakake SOLID

Saka penerjemah: diterbitake kanggo sampeyan artikel dening Severin Perez babagan nggunakake prinsip SOLID ing pemrograman. Informasi saka artikel bakal migunani kanggo pamula lan programer sing berpengalaman.

Yen sampeyan ana ing pembangunan, mesthine sampeyan wis krungu babagan prinsip SOLID. Dheweke ngaktifake programer kanggo nulis kode sing resik, terstruktur lan gampang dijaga. Wigati dicathet yen ing program ana sawetara cara kanggo nindakake tugas tartamtu kanthi bener. Spesialis sing beda-beda duwe gagasan lan pangerten sing beda babagan "dalan sing bener"; kabeh gumantung saka pengalaman saben wong. Nanging, gagasan sing diproklamasiake ing SOLID ditampa dening meh kabeh perwakilan komunitas IT. Dheweke dadi titik wiwitan kanggo muncule lan pangembangan akeh praktik manajemen pangembangan sing apik.

Ayo ngerti apa prinsip SOLID lan carane mbantu kita.

Skillbox nyaranake: Kursus praktis "Mobile Developer PRO".

Kita ngelingake: kanggo kabeh sing maca "Habr" - diskon 10 rubel nalika ndhaptar kursus Skillbox nggunakake kode promosi "Habr".

Apa iku SOLID?

Istilah iki minangka singkatan, saben huruf saka istilah kasebut minangka wiwitan jeneng prinsip tartamtu:

  • SPrinsip Tanggung Jawab. A modul bisa duwe siji lan mung siji alesan kanggo owah-owahan.
  • The Open / Prinsip Tertutup (prinsip mbukak/tertutup). Kelas lan unsur liyane kudu mbukak kanggo extension, nanging ditutup kanggo modifikasi.
  • β€ŠThe LPrinsip Substitusi iskov (prinsip substitusi Liskov). Fungsi sing nggunakake jinis dhasar kudu bisa nggunakake subtipe saka jinis dhasar tanpa ngerti.
  • The IPrinsip Segregasi Antarmukaβ€Š (prinsip pamisahan antarmuka). Entitas piranti lunak ora kudu gumantung marang cara sing ora digunakake.
  • The DPrinsip Inversi ependency (prinsip inversi dependensi). Modul ing tingkat sing luwih dhuwur ngirim ora gumantung ing modul ing tingkat ngisor.

Prinsip Tanggung Jawab Tunggal

β€Š
Prinsip Tanggung Jawab Tunggal (SRP) nyatakake yen saben kelas utawa modul ing program kudu tanggung jawab mung siji bagean saka fungsi program kasebut. Kajaba iku, unsur tanggung jawab iki kudu ditugasake ing kelas dhewe, tinimbang kasebar ing kelas sing ora ana hubungane. Pangembang lan pangareping penginjil SRP, Robert S. Martin, nggambarake tanggung jawab minangka alesan kanggo owah-owahan. Dheweke wiwitane ngusulake istilah iki minangka salah sawijining unsur karyane "Principles of Object-Oriented Design". Konsep kasebut kalebu akeh pola konektivitas sing sadurunge ditetepake dening Tom DeMarco.

Konsep kasebut uga kalebu sawetara konsep sing dirumusake dening David Parnas. Loro sing utama yaiku enkapsulasi lan ndhelikake informasi. Parnas ujar manawa mbagi sistem dadi modul sing kapisah ora kudu adhedhasar analisis diagram blok utawa aliran eksekusi. Sembarang modul kudu ngemot solusi tartamtu sing nyedhiyakake informasi minimal kanggo klien.

Miturut cara, Martin menehi conto sing menarik karo manajer senior perusahaan (COO, CTO, CFO), sing saben-saben nggunakake piranti lunak bisnis tartamtu kanggo macem-macem tujuan. AkibatΓ©, salah siji saka wong-wong mau bisa nindakake owah-owahan ing piranti lunak tanpa mengaruhi kapentingan manajer liyane.

obyek gaib

Kaya biasane, cara paling apik kanggo sinau SRP yaiku ndeleng tumindak. Ayo goleki bagean saka program sing ora netepi Prinsip Tanggung Jawab Tunggal. Iki minangka kode Ruby sing nggambarake prilaku lan atribut stasiun ruang angkasa.

Deleng conto kasebut lan coba nemtokake ing ngisor iki:
Tanggung jawab obyek kasebut sing diumumake ing kelas SpaceStation.
Sing bisa uga kasengsem ing operasi stasiun ruang 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

Bener, stasiun ruang angkasa kita ora bisa digunakake (aku ora mikir yen aku bakal ditelpon saka NASA kapan wae), nanging ana sing kudu dianalisis ing kene.

Dadi, kelas SpaceStation duwe sawetara tanggung jawab (utawa tugas). Kabeh mau bisa dipΓ©rang dadi jinis:

  • sensor;
  • panyedhiya (consumable);
  • bahan bakar;
  • akselerator.

Sanajan ora ana karyawan stasiun sing diwenehi kelas, kita bisa mbayangake sapa sing tanggung jawab. Paling kamungkinan, ilmuwan ngontrol sensor, logistik tanggung jawab kanggo nyuplai sumber daya, insinyur tanggung jawab kanggo pasokan bahan bakar, lan pilot ngontrol boosters.

Apa kita bisa ujar manawa program iki ora cocog karo SRP? Iya tenanan. Nanging kelas SpaceStation minangka "obyek dewa" khas sing ngerti kabeh lan nindakake kabeh. Iki minangka anti-pola utama ing pemrograman berorientasi obyek. Kanggo pamula, obyek kasebut angel banget kanggo njaga. Nganti saiki, program kasebut gampang banget, ya, nanging bayangake apa sing bakal kedadeyan yen kita nambah fitur-fitur anyar. Mungkin stasiun ruang angkasa kita butuh stasiun medis utawa ruang rapat. Lan luwih akeh fungsi, luwih akeh SpaceStation bakal tuwuh. Ya, amarga fasilitas iki bakal disambungake karo wong liya, layanan kabeh kompleks bakal dadi luwih angel. AkibatΓ©, kita bisa ngganggu operasi, contone, akselerator. Yen peneliti njaluk owah-owahan ing sensor, iki bisa uga mengaruhi sistem komunikasi stasiun.

Nglanggar prinsip SRP bisa menehi kamenangan taktik short-term, nanging ing pungkasan kita bakal "kalah perang", lan bakal dadi angel banget kanggo njaga monster kuwi ing mangsa. Iku paling apik kanggo dibagi program menyang bagean kapisah saka kode, saben kang tanggung jawab kanggo nindakake operasi tartamtu. Ngerteni iki, ayo ngganti kelas SpaceStation.

Ayo dibagi tanggung jawab

Ndhuwur kita nemtokake papat jinis operasi sing dikontrol dening kelas SpaceStation. Kita bakal mbudidaya nalika refactoring. Kode sing dianyari luwih cocog karo 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

Ana akeh owah-owahan, program mesthi katon luwih apik saiki. Saiki kelas SpaceStation kita wis dadi luwih saka wadhah sing operasi diwiwiti kanggo bagean sing gumantung, kalebu set sensor, sistem pasokan sing bisa dikonsumsi, tangki bahan bakar, lan booster.

Kanggo samubarang variabel saiki ana kelas sing cocog: Sensor; SupplyHold; Tangki Bahan Bakar; Pendorong.

Ana sawetara owah-owahan penting ing versi kode iki. Titik iku fungsi individu ora mung encapsulated ing kelas dhewe, padha diatur ing kuwi cara dadi bisa katebak lan konsisten. Kita nglumpukake unsur kanthi fungsi sing padha kanggo ngetutake prinsip koherensi. Saiki, yen kita kudu ngganti cara kerja sistem, pindhah saka struktur hash menyang array, gunakake kelas SupplyHold; kita ora kudu ndemek modul liyane. Kanthi cara iki, yen petugas logistik ngganti apa wae ing bageane, stasiun liyane bakal tetep utuh. Ing kasus iki, kelas SpaceStation malah ora bakal weruh owah-owahan.

Petugas kita sing kerja ing stasiun ruang angkasa mbokmenawa seneng karo owah-owahan amarga bisa njaluk sing dibutuhake. Elinga yen kode kasebut nduweni cara kayata report_supplies lan report_fuel sing ana ing kelas SupplyHold lan FuelTank. Apa sing bakal kelakon yen Bumi njaluk ngganti cara laporan? Loro kelas, SupplyHold lan FuelTank, kudu diganti. Kepiye yen sampeyan kudu ngganti cara pangiriman bahan bakar lan bahan bakar? Sampeyan mbokmenawa kudu ngganti kabeh kelas sing padha maneh. Lan iki wis nglanggar prinsip SRP. Ayo didandani iki.

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.

Ing versi paling anyar saka program iki, tanggung jawab wis dipΓ©rang dadi rong kelas anyar, FuelReporter lan SupplyReporter. Loro-lorone bocah saka kelas Reporter. Kajaba iku, kita nambahake variabel conto menyang kelas SpaceStation supaya subkelas sing dikarepake bisa diwiwiti yen perlu. Saiki, yen Bumi mutusake kanggo ngganti liyane, mula kita bakal nggawe owah-owahan ing subclass, lan dudu kelas utama.

Mesthi, sawetara kelas kita isih gumantung ing saben liyane. Mangkono, obyek SupplyReporter gumantung ing SupplyHold, lan FuelReporter gumantung ing FuelTank. Mesthi, boosters kudu disambungake menyang tank bahan bakar. Nanging ing kene kabeh wis katon logis, lan owah-owahan ora bakal angel banget - nyunting kode siji obyek ora bakal mengaruhi liyane.

Mangkono, kita wis nggawe kode modular ngendi tanggung jawab saben obyek / kelas wis ditetepake sabenere. Nggarap kode kasebut ora dadi masalah, njaga bakal dadi tugas sing gampang. Kita wis ngowahi kabeh "obyek gaib" dadi SRP.

Skillbox nyaranake:

Source: www.habr.com

Add a comment