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.
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:
Sing Egwyddor Cyfrifoldeb. Gall modiwl fod ag un rheswm yn unig dros newid.
Mae adroddiadau Open/Egwyddor Caeedig (egwyddor agored/caeedig). Dylai dosbarthiadau ac elfennau eraill fod yn agored i'w hymestyn, ond ar gau i'w haddasu.
Mae adroddiadau Liskov Egwyddor Amnewid (Egwyddor amnewid Liskov). Dylai swyddogaethau sy'n defnyddio math sylfaen allu defnyddio isdeipiau o'r math sylfaen heb yn wybod hynny.
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.