No tulka: publicÄts jums Severina Peresa raksts par SOLID principu izmantoÅ”anu programmÄÅ”anÄ. RakstÄ sniegtÄ informÄcija bÅ«s noderÄ«ga gan iesÄcÄjiem, gan pieredzÄjuÅ”iem programmÄtÄjiem.
Ja jÅ«s interesÄ attÄ«stÄ«ba, jÅ«s, visticamÄk, esat dzirdÄjuÅ”i par SOLID principiem. Tie ļauj programmÄtÄjam rakstÄ«t tÄ«ru, labi strukturÄtu un viegli uzturÄjamu kodu. Ir vÄrts atzÄ«mÄt, ka programmÄÅ”anÄ ir vairÄkas pieejas, kÄ pareizi veikt konkrÄtu darbu. DažÄdiem speciÄlistiem ir dažÄdas idejas un izpratne par āpareizo ceļuā, tas viss ir atkarÄ«gs no katra cilvÄka pieredzes. TaÄu SOLID sludinÄtÄs idejas pieÅem gandrÄ«z visi IT kopienas pÄrstÄvji. Tie kļuva par sÄkumpunktu daudzu labu attÄ«stÄ«bas pÄrvaldÄ«bas prakÅ”u raÅ”anÄs un attÄ«stÄ«bai.
SapratÄ«sim, kas ir SOLID principi un kÄ tie mums palÄ«dz.
ā
VienotÄs atbildÄ«bas princips (Single Responsibility Principle ā SRP) nosaka, ka katrai programmas klasei vai modulim ir jÄatbild tikai par vienu Ŕīs programmas funkcionalitÄtes daļu. TurklÄt Ŕīs atbildÄ«bas elementi ir jÄpieŔķir savai klasei, nevis jÄizkaisa pa nesaistÄ«tÄm klasÄm. SRP izstrÄdÄtÄjs un galvenais evaÅÄ£Älists Roberts S. MÄrtins apraksta atbildÄ«bu kÄ pÄrmaiÅu iemeslu. SÄkotnÄji viÅÅ” ierosinÄja Å”o terminu kÄ vienu no sava darba "ObjektorientÄtÄ dizaina principi" elementiem. Koncepcija ietver lielu daļu savienojamÄ«bas modeļa, ko iepriekÅ” definÄja Toms Demarko.
KoncepcijÄ bija iekļauti arÄ« vairÄki Deivida Parnasa formulÄti jÄdzieni. Divas galvenÄs ir iekapsulÄÅ”ana un informÄcijas slÄpÅ”ana. Parnas apgalvoja, ka sistÄmas sadalÄ«Å”anu atseviŔķos moduļos nevajadzÄtu balstÄ«t uz blokshÄmu vai izpildes plÅ«smu analÄ«zi. JebkurÄ no moduļiem ir jÄbÅ«t konkrÄtam risinÄjumam, kas klientiem sniedz minimÄlu informÄciju.
Starp citu, Martins sniedza interesantu piemÄru ar uzÅÄmuma augstÄkajiem vadÄ«tÄjiem (COO, CTO, CFO), no kuriem katrs izmanto specifisku biznesa programmatÅ«ru dažÄdiem mÄrÄ·iem. RezultÄtÄ jebkurÅ” no tiem var ieviest izmaiÅas programmatÅ«rÄ, neietekmÄjot citu vadÄ«tÄju intereses.
DieviŔķs objekts
KÄ vienmÄr, labÄkais veids, kÄ apgÅ«t SRP, ir redzÄt to darbÄ«bÄ. ApskatÄ«sim programmas sadaļu, kurÄ NAV ievÄrots vienotas atbildÄ«bas princips. Å is ir RubÄ«na kods, kas apraksta kosmosa stacijas uzvedÄ«bu un atribÅ«tus.
PÄrskatiet piemÄru un mÄÄ£iniet noteikt tÄlÄk norÄdÄ«to.
To objektu pienÄkumi, kuri ir deklarÄti SpaceStation klasÄ.
Tie, kurus varÄtu interesÄt kosmosa stacijas darbÄ«ba.
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
PatiesÄ«bÄ mÅ«su kosmosa stacija nedarbojas (es nedomÄju, ka tuvÄkajÄ laikÄ saÅemÅ”u zvanu no NASA), taÄu Å”eit ir ko analizÄt.
TÄdÄjÄdi SpaceStation klasei ir vairÄki dažÄdi pienÄkumi (vai uzdevumi). Tos visus var iedalÄ«t tipos:
sensori;
izejmateriÄli (palÄ«gmateriÄli);
degviela;
paÄtrinÄtÄji.
Lai arÄ« nevienam no stacijas darbiniekiem nav iedalÄ«ta klase, mÄs varam viegli iedomÄties, kurÅ” par ko ir atbildÄ«gs. VisticamÄk, zinÄtnieks kontrolÄ sensorus, loÄ£istikas pienÄkums ir nodroÅ”inÄt resursus, inženieris ir atbildÄ«gs par degvielas piegÄdÄm, bet pilots kontrolÄ pastiprinÄtÄjus.
Vai mÄs varam teikt, ka Ŕī programma nav saderÄ«ga ar SRP? JÄ, protams. Bet SpaceStation klase ir tipisks "dieva objekts", kas zina visu un dara visu. Å is ir galvenais objektorientÄtÄs programmÄÅ”anas pretmodelis. IesÄcÄjam Å”Ädus objektus ir ÄrkÄrtÄ«gi grÅ«ti uzturÄt. PagaidÄm programma ir ļoti vienkÄrÅ”a, jÄ, taÄu iedomÄjieties, kas notiks, ja pievienosim jaunas funkcijas. VarbÅ«t mÅ«su kosmosa stacijai bÅ«s nepiecieÅ”ama medicÄ«nas stacija vai sanÄksmju telpa. Un jo vairÄk bÅ«s funkciju, jo vairÄk SpaceStation pieaugs. TÄ kÄ Å”Ä« iekÄrta bÅ«s savienota ar citÄm, visa kompleksa apkalpoÅ”ana kļūs vÄl grÅ«tÄka. RezultÄtÄ varam traucÄt, piemÄram, akseleratoru darbÄ«bu. Ja pÄtnieks pieprasa izmaiÅas sensoros, tas var ļoti labi ietekmÄt stacijas sakaru sistÄmas.
SRP principa pÄrkÄpÅ”ana var dot Ä«stermiÅa taktisku uzvaru, taÄu galu galÄ mÄs āzaudÄsim karuā, un turpmÄk Å”Ädu briesmoni uzturÄt bÅ«s ļoti grÅ«ti. VislabÄk ir sadalÄ«t programmu atseviŔķÄs koda sadaļÄs, no kurÄm katra ir atbildÄ«ga par konkrÄtas darbÄ«bas veikÅ”anu. To saprotot, mainÄ«sim SpaceStation klasi.
Sadalīsim atbildību
IepriekÅ” mÄs definÄjÄm Äetrus operÄciju veidus, kurus kontrolÄ SpaceStation klase. MÄs tos paturÄsim prÄtÄ, veicot pÄrstrukturÄÅ”anu. AtjauninÄtais kods labÄk atbilst 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
Ir daudz izmaiÅu, programma tagad noteikti izskatÄs labÄk. Tagad mÅ«su SpaceStation klase ir kļuvusi vairÄk par konteineru, kurÄ tiek uzsÄktas darbÄ«bas ar atkarÄ«gÄm daļÄm, tostarp sensoru komplektu, patÄrÄjamo materiÄlu padeves sistÄmu, degvielas tvertni un pastiprinÄtÄjiem.
Jebkuram no mainÄ«gajiem tagad ir atbilstoÅ”a klase: Sensors; SupplyHold; Degvielas tvertne; DzinÄji.
Å ajÄ koda versijÄ ir vairÄkas svarÄ«gas izmaiÅas. Lieta ir tÄda, ka atseviŔķas funkcijas ir ne tikai iekapsulÄtas savÄs klasÄs, tÄs ir organizÄtas tÄ, lai tÄs kļūtu paredzamas un konsekventas. MÄs grupÄjam elementus ar lÄ«dzÄ«gu funkcionalitÄti, lai ievÄrotu saskaÅotÄ«bas principu. Tagad, ja mums ir jÄmaina sistÄmas darbÄ«bas veids, pÄrejot no hash struktÅ«ras uz masÄ«vu, vienkÄrÅ”i izmantojiet SupplyHold klasi; mums nav jÄpieskaras citiem moduļiem. TÄdÄ veidÄ, ja loÄ£istikas virsnieks kaut ko mainÄ«s savÄ sekcijÄ, pÄrÄjÄ stacija paliks neskarta. Å ajÄ gadÄ«jumÄ SpaceStation klase pat neuzzinÄs par izmaiÅÄm.
MÅ«su virsnieki, kas strÄdÄ kosmosa stacijÄ, droÅ”i vien ir priecÄ«gi par izmaiÅÄm, jo āāviÅi var pieprasÄ«t sev nepiecieÅ”amÄs. Å emiet vÄrÄ, ka kodam ir tÄdas metodes kÄ report_supplies un report_fuel, kas ietvertas klasÄs SupplyHold un FuelTank. Kas notiktu, ja Zeme lÅ«gtu mainÄ«t veidu, kÄ tÄ ziÅo? Abas klases, SupplyHold un FuelTank, bÅ«s jÄmaina. Ko darÄ«t, ja jÄmaina degvielas un palÄ«gmateriÄlu piegÄdes veids? VisticamÄk, jums atkal bÅ«s jÄmaina visas tÄs paÅ”as klases. Un tas jau ir SRP principa pÄrkÄpums. Labosim Å”o.
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.
Å ajÄ jaunÄkajÄ programmas versijÄ pienÄkumi ir sadalÄ«ti divÄs jaunÄs klasÄs ā FuelReporter un SupplyReporter. ViÅi abi ir Reportieru klases bÄrni. TurklÄt SpaceStation klasei pievienojÄm instanÄu mainÄ«gos, lai vajadzÄ«bas gadÄ«jumÄ varÄtu inicializÄt vajadzÄ«go apakÅ”klasi. Tagad, ja Zeme nolems mainÄ«t kaut ko citu, tad mÄs veiksim izmaiÅas apakÅ”klasÄs, nevis galvenajÄ klasÄ.
Protams, dažas no mÅ«su klasÄm joprojÄm ir atkarÄ«gas viena no otras. TÄdÄjÄdi objekts SupplyReporter ir atkarÄ«gs no SupplyHold, un FuelReporter ir atkarÄ«gs no FuelTank. Protams, pastiprinÄtÄji ir jÄpievieno degvielas tvertnei. Bet Å”eit jau viss izskatÄs loÄ£iski, un izmaiÅu veikÅ”ana nebÅ«s Ä«paÅ”i sarežģīta ā viena objekta koda rediÄ£ÄÅ”ana citu Ä«paÅ”i neietekmÄs.
TÄdÄjÄdi esam izveidojuÅ”i modulÄru kodu, kurÄ ir precÄ«zi definÄti katra objekta/klases pienÄkumi. Darbs ar Å”Ädu kodu nav problÄma, tÄ uzturÄÅ”ana bÅ«s vienkÄrÅ”s uzdevums. MÄs esam pÄrveidojuÅ”i visu "dieviŔķo objektu" par SRP.