Rydym yn ysgrifennu cod hyblyg gan ddefnyddio SOLID

Rydym yn ysgrifennu cod hyblyg gan ddefnyddio SOLID

Gan y cyfieithydd: cyhoeddi i chi erthygl gan Severin Perez am ddefnyddio egwyddorion SOLID mewn rhaglennu. Bydd y wybodaeth o'r erthygl yn ddefnyddiol i ddechreuwyr a rhaglenwyr profiadol.

Os ydych chi mewn datblygiad, mae'n debyg eich bod wedi clywed am yr egwyddorion SOLID. Maent yn galluogi'r rhaglennydd i ysgrifennu cod glân, wedi'i strwythuro'n dda ac yn hawdd ei gynnal. Mae'n werth nodi bod nifer o ddulliau mewn rhaglennu ar sut i gyflawni swydd benodol yn gywir. Mae gan wahanol arbenigwyr syniadau a dealltwriaeth wahanol o’r “llwybr cywir”; mae’r cyfan yn dibynnu ar brofiad pob person. Fodd bynnag, mae bron pob cynrychiolydd o'r gymuned TG yn derbyn y syniadau a gyhoeddir yn SOLID. Daethant yn fan cychwyn ar gyfer ymddangosiad a datblygiad llawer o arferion rheoli datblygiad da.

Gadewch i ni ddeall beth yw egwyddorion SOLID a sut maen nhw'n ein helpu ni.

Mae Skillsbox yn argymell: Cwrs ymarferol "Datblygwr Symudol PRO".

Rydym yn atgoffa: i holl ddarllenwyr "Habr" - gostyngiad o 10 rubles wrth gofrestru ar unrhyw gwrs Skillbox gan ddefnyddio'r cod hyrwyddo "Habr".

Beth yw SOLID?

Talfyriad yw'r term hwn, mae pob llythyren o'r term yn ddechrau enw egwyddor benodol:

Egwyddor Cyfrifoldeb Sengl


Mae'r Egwyddor Cyfrifoldeb Sengl (SRP) yn nodi y dylai pob dosbarth neu fodiwl mewn rhaglen fod yn gyfrifol am un rhan yn unig o ymarferoldeb y rhaglen honno. Yn ogystal, dylid neilltuo elfennau o'r cyfrifoldeb hwn i'w dosbarth eu hunain, yn hytrach na'u gwasgaru ar draws dosbarthiadau digyswllt. Mae datblygwr SRP a phrif efengylwr, Robert S. Martin, yn disgrifio atebolrwydd fel y rheswm dros newid. Yn wreiddiol cynigiodd y term hwn fel un o elfennau ei waith "Egwyddorion Dylunio Gwrthrychol". Mae'r cysyniad yn ymgorffori llawer o'r patrwm cysylltedd a ddiffiniwyd yn flaenorol gan Tom DeMarco.

Roedd y cysyniad hefyd yn cynnwys sawl cysyniad a luniwyd gan David Parnas. Y ddau brif beth yw amgáu a chuddio gwybodaeth. Dadleuodd Parnas na ddylai rhannu system yn fodiwlau ar wahân fod yn seiliedig ar ddadansoddiad o ddiagramau bloc neu lifau gweithredu. Rhaid i unrhyw fodiwlau gynnwys datrysiad penodol sy'n darparu lleiafswm o wybodaeth i gleientiaid.

Gyda llaw, rhoddodd Martin enghraifft ddiddorol gydag uwch reolwyr cwmni (COO, CTO, CFO), y mae pob un ohonynt yn defnyddio meddalwedd busnes penodol at wahanol ddibenion. O ganlyniad, gall unrhyw un ohonynt weithredu newidiadau yn y meddalwedd heb effeithio ar fuddiannau rheolwyr eraill.

Gwrthrych dwyfol

Fel bob amser, y ffordd orau o ddysgu SRP yw ei weld ar waith. Gadewch i ni edrych ar adran o'r rhaglen NAD yw'n dilyn yr Egwyddor Cyfrifoldeb Sengl. Dyma god Ruby sy'n disgrifio ymddygiad a phriodoleddau'r orsaf ofod.

Adolygwch yr enghraifft a cheisiwch benderfynu ar y canlynol:
Cyfrifoldebau'r gwrthrychau hynny sy'n cael eu datgan yn y dosbarth SpaceStation.
Y rhai a allai fod â diddordeb yng ngweithrediad yr orsaf ofod.

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

A dweud y gwir, mae ein gorsaf ofod yn gamweithredol (nid wyf yn meddwl y byddaf yn cael galwad gan NASA unrhyw bryd yn fuan), ond mae rhywbeth i'w ddadansoddi yma.

Felly, mae gan y dosbarth SpaceStation nifer o wahanol gyfrifoldebau (neu dasgau). Gellir rhannu pob un ohonynt yn fathau:

  • synwyr;
  • cyflenwadau (nwyddau traul);
  • tanwydd;
  • cyflymyddion.

Er nad oes dosbarth yn cael ei neilltuo i unrhyw un o weithwyr yr orsaf, gallwn yn hawdd ddychmygu pwy sy'n gyfrifol am beth. Yn fwyaf tebygol, mae'r gwyddonydd yn rheoli'r synwyryddion, y logistegydd sy'n gyfrifol am gyflenwi adnoddau, y peiriannydd sy'n gyfrifol am gyflenwadau tanwydd, ac mae'r peilot yn rheoli'r cyfnerthwyr.

A allwn ddweud nad yw'r rhaglen hon yn cydymffurfio â SRP? Iawn siwr. Ond mae'r dosbarth SpaceStation yn "wrthrych duw" nodweddiadol sy'n gwybod popeth ac yn gwneud popeth. Mae hwn yn wrth-batrwm mawr mewn rhaglennu gwrthrych-ganolog. I ddechreuwr, mae gwrthrychau o'r fath yn hynod o anodd i'w cynnal. Hyd yn hyn mae'r rhaglen yn syml iawn, ydy, ond dychmygwch beth fydd yn digwydd os byddwn yn ychwanegu nodweddion newydd. Efallai y bydd angen gorsaf feddygol neu ystafell gyfarfod ar ein gorsaf ofod. A pho fwyaf o swyddogaethau sydd, y mwyaf y bydd SpaceStation yn tyfu. Wel, gan y bydd y cyfleuster hwn yn cael ei gysylltu ag eraill, bydd gwasanaethu'r cyfadeilad cyfan yn dod yn anoddach fyth. O ganlyniad, gallwn amharu ar weithrediad, er enghraifft, cyflymyddion. Os bydd ymchwilydd yn gofyn am newidiadau i'r synwyryddion, gallai hyn effeithio'n dda iawn ar systemau cyfathrebu'r orsaf.

Efallai y bydd torri egwyddor SRP yn rhoi buddugoliaeth dactegol tymor byr, ond yn y diwedd byddwn yn “colli’r rhyfel”, a bydd yn dod yn anodd iawn cynnal anghenfil o’r fath yn y dyfodol. Mae'n well rhannu'r rhaglen yn adrannau ar wahân o'r cod, y mae pob un ohonynt yn gyfrifol am gyflawni gweithrediad penodol. Gan ddeall hyn, gadewch i ni newid y dosbarth SpaceStation.

Gadewch i ni ddosbarthu cyfrifoldeb

Uchod fe wnaethom ddiffinio pedwar math o weithrediadau sy'n cael eu rheoli gan y dosbarth SpaceStation. Byddwn yn eu cadw mewn cof wrth ailffactorio. Mae'r cod wedi'i ddiweddaru yn cyfateb yn well i'r 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

Mae yna lawer o newidiadau, mae'r rhaglen yn bendant yn edrych yn well nawr. Nawr mae ein dosbarth SpaceStation wedi dod yn fwy o gynhwysydd lle mae gweithrediadau'n cael eu cychwyn ar gyfer rhannau dibynnol, gan gynnwys set o synwyryddion, system gyflenwi traul, tanc tanwydd, a chyfnerthwyr.

Ar gyfer unrhyw un o'r newidynnau mae bellach ddosbarth cyfatebol: Synwyryddion; SupplyHold; Tanc tanwydd; Gwthwyr.

Mae yna nifer o newidiadau pwysig yn y fersiwn hwn o'r cod. Y pwynt yw bod swyddogaethau unigol nid yn unig yn cael eu crynhoi yn eu dosbarthiadau eu hunain, maent yn cael eu trefnu mewn ffordd sy'n dod yn rhagweladwy a chyson. Rydym yn grwpio elfennau gyda swyddogaethau tebyg i ddilyn yr egwyddor o gydlyniad. Nawr, os oes angen i ni newid y ffordd y mae'r system yn gweithio, gan symud o strwythur hash i arae, defnyddiwch y dosbarth SupplyHold; nid oes rhaid i ni gyffwrdd â modiwlau eraill. Fel hyn, os bydd y swyddog logisteg yn newid rhywbeth yn ei adran, bydd gweddill yr orsaf yn parhau'n gyfan. Yn yr achos hwn, ni fydd y dosbarth SpaceStation hyd yn oed yn ymwybodol o'r newidiadau.

Mae'n debyg bod ein swyddogion sy'n gweithio ar yr orsaf ofod yn hapus gyda'r newidiadau oherwydd gallant ofyn am y rhai sydd eu hangen arnynt. Sylwch fod gan y cod ddulliau fel report_supplies ac report_fuel sydd wedi’u cynnwys yn y dosbarthiadau SupplyHold a FuelTank. Beth fyddai'n digwydd pe bai'r Ddaear yn gofyn am newid y ffordd y mae'n adrodd? Bydd angen newid y ddau ddosbarth, SupplyHold a FuelTank. Beth os oes angen ichi newid y ffordd y caiff tanwydd a nwyddau traul eu danfon? Mae'n debyg y bydd yn rhaid i chi newid yr un dosbarthiadau eto. Ac mae hyn eisoes yn groes i'r egwyddor SRP. Gadewch i ni drwsio hyn.

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.

Yn y fersiwn ddiweddaraf hon o'r rhaglen, mae'r cyfrifoldebau wedi'u rhannu'n ddau ddosbarth newydd, sef FuelReporter a SupplyReporter. Mae'r ddau yn blant i ddosbarth yr Adroddwr. Yn ogystal, rydym wedi ychwanegu newidynnau enghreifftiol at y dosbarth SpaceStation fel y gellir cychwyn yr is-ddosbarth a ddymunir os oes angen. Nawr, os bydd y Ddaear yn penderfynu newid rhywbeth arall, yna byddwn yn gwneud newidiadau i'r is-ddosbarthiadau, ac nid i'r prif ddosbarth.

Wrth gwrs, mae rhai o'n dosbarthiadau yn dal i ddibynnu ar ei gilydd. Felly, mae gwrthrych SupplyReporter yn dibynnu ar SupplyHold, ac mae FuelReporter yn dibynnu ar FuelTank. Wrth gwrs, rhaid cysylltu'r cyfnerthwyr â'r tanc tanwydd. Ond yma mae popeth eisoes yn edrych yn rhesymegol, ac ni fydd gwneud newidiadau yn arbennig o anodd - ni fydd golygu cod un gwrthrych yn effeithio'n fawr ar un arall.

Felly, rydym wedi creu cod modiwlaidd lle mae cyfrifoldebau pob un o'r gwrthrychau/dosbarthiadau wedi'u diffinio'n fanwl gywir. Nid yw gweithio gyda chod o'r fath yn broblem, bydd ei gynnal yn dasg syml. Rydym wedi trosi'r “gwrthrych dwyfol” cyfan yn SRP.

Mae Skillsbox yn argymell:

Ffynhonnell: hab.com

Ychwanegu sylw