
Tõlkijalt: olenud teie jaoks SOLID-põhimõtete rakendamisest programmeerimises. Artikli teave on kasulik nii algajatele kui ka kogenud programmeerijatele.
Kui tegelete arendusega, olete tõenäoliselt kuulnud SOLID-põhimõtetest. Need võimaldavad programmeerijal kirjutada puhtat, hästi struktureeritud ja lihtsasti hooldatavat koodi. Tuleb märkida, et programmeerimises on mitu lähenemist sellele, kuidas õigesti täita mingit ülesannet. Erinevatel spetsialistidel on erinevad ideed ja arusaamad "õigest teest", kõik oleneb igaühe kogemusest. Siiski, SOLIDis väljendatud ideed on vastuvõetavad peaaegu kõigi IT-kommuuni esindajate poolt. Need on saanud alguseks mitmete heade arendusjuhtimise meetodite tekkimisele ja arengule.
Vaatame, mis on SOLID-põhimõtted ja kuidas need meid aitavad.
Skillbox soovitab: Praktiline kursus .
Tuletame meelde: kõigile «Habr» lugejatele – 10 000 rubla allahindlus igale Skillboxi kursusele, kasutades sooduskoodi «Habr».
Mis on SOLID?
See termin on akronüüm, kus iga tähe algus on teatud põhimõtte nimi:
- SSingle Responsibility Principle (ühe vastutuse põhimõte). Moodulil võib olla vaid üks põhjus muutuda.
- (avatus/kinni püsimise põhimõte). Klassid ja muud üksused peavad olema avatud laiendamiseks, kuid suletud modifitseerimiseks.
- (Liskovi asenduspõhimõte). Funktsioonid, mis kasutavad baastype, peavad olema suutelised kasutama baastype alamtype, teadmata sellest.
- (liidese jagamise põhimõte). Tarkvarakujud ei tohi sõltuda meetoditest, mida nad ei kasuta.
- (sõltuvuse pööramise põhimõte). Ülemise taseme moodulid ei tohi sõltuda alumise taseme moodulitest.
Ühe vastutuse põhimõte
Ühe vastutuse põhimõte (SRP) ütleb, et iga klass või moodul programmis peab vastutama ainult ühte osa selle programmist. Ühtlasi peavad selle vastutuse elemendid olema kinnitatud oma klassi, mitte jaotatud omavahel mitteseotud klasside vahel. Arendaja ja SRP peamine evangelist, Robert C. Martin, kirjeldab vastutust kui muutuste põhjust. Alguses ettepanek, et see termin on üks elementi tema töös "Objekti-orienteeritud disaini põhimõtted". Kontseptsioonis on palju sisse viidud seotud lahenduse seadusi, mis on varem määratlenud Tom DeMarco.
Lisaks on kontseptsiooni tulnud mitu mõistet, mille on sõnastanud David Parnas. Kaks peamist — kapseldamine ja teabe varjamine. Parnas väitis, et süsteemi jagamine eraldi mooduliteks ei tohiks põhineda vooskeemide või täitmisvoogude analüüsil. Iga moodul peaks sisaldama konkreetset lahendust, mis pakub klientidele minimaalset teavet.
Muide, Martin tõi huvitava näite ettevõtte kõrgema juhtkonna (COO, CTO, CFO) kaudu, igaühel neist on konkreetne äritarkvara, mida nad kasutavad erinevatel eesmärkidel. Seetõttu võib igaüks neist tarkvara muuta, mõjutamata teiste juhtide huve.
Jumallik objekt
Nagu tavaliselt, on parim viis SRP-d õppida, näha seda kõike töös. Vaadakem programmi lõiku, mis ei vasta ühe vastutuse põhimõttele. See on Ruby-kood, mis kirjeldab kosmosesüsteemi käitumist ja atribuute.
Vaadake näidet ja proovige kindlaks teha järgmist:
Need objektid, mille kohustused on määratletud klassis SpaceStation.
Need, kes võivad olla huvitatud kosmosejaama tööst.
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} available: #{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
endTõepoolest, meie kosmosejaam ei tööta (arvan, et ei saa NASA-d peagi kõnetama), kuid siin on palju, mida analüüsida.
Nii et klassil SpaceStation on mitu erinevat vastutust (või ülesannet). Kõik need saab jagada tüüpideks:
- andurid;
- varustus (tarvikud);
- kütus;
- thrustid.
Kuigi jaama töötajad ei ole klassi määratud, saame kergesti ette kujutada, kes millise ülesande täidab. Tõenäoliselt jälgib teadlane andureid, logistika vastutab varude eest, insener vastutab kütusepeamiste eest, ja piloot kontrollib thrustide tööd.
Kas me saame öelda, et see programm ei vasta SRP-le? Jah, kindlasti. Kuid klass SpaceStation on tüüpiline „jumalik objekt“, mis teab kõigest ja teeb kõike. See on peamine antipattern objektorienteeritud programmeerimises. Algajatele on sellised objektid äärmiselt keerulised hooldada. Praegu on programm väga lihtne, jah, aga kujutage ette, mis juhtub, kui lisame uusi funktsioone. Võib-olla vajab meie kosmosejaam meditsiinipunkti või koosolekuruumi. Ja mida rohkem funktsioone lisatakse, seda keerulisemaks muutub SpaceStation. Kuna see objekt on seotud teiste objektidega, muutub kogu kompleksi hooldamine veelgi keerulisemaks. Lõpuks võime rikku hakata, näiteks, kiirendites. Kui teadlane küsib muudatusi anduritelt töötamisel, võib see kindlasti mõjutada jaama side süsteeme.
SRP-printsiibi rikkumine võib anda lühiajalise taktikalise võidu, kuid lõpuks „kaotame sõja“, kuna sellise koletise hooldamine tulevikus muutub üsna keeruliseks. Parim on jagada programm eraldi koodilõikudeks, millest igaüks vastutab konkreetse operatsiooni täitmise eest. Seda mõistes muudame klassi SpaceStation.
Jagame vastutust
Ülal oleme määratlenud neli tüüpi operatsiooni, mida kontrollib klass SpaceStation. Refaktoorimise käigus võtame need arvesse. Uuendatud kood vastab paremini SRP-le.
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} available: #{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
endMuutusi on palju, programm näeb nüüd tõeliselt parem välja. Meie SpaceStation klass on nüüd pigem konteiner, kus algatatakse tegevusi sõltuvate osade jaoks, sealhulgas andurite komplekt, tarnehalduse süsteem, kütusepaak ja thrusterid.
Iga muutuja jaoks on nüüd vastav klass: Sensors; SupplyHold; FuelTank; Thrusters.
Selles koodiversioonis on mitmeid olulisi muudatusi. Esiteks on eraldi funktsioonid mitte ainult kapseldatud oma klassidesse, vaid need on ka korraldatud viisil, mis muudab need etteennustatavaks ja järjepidevaks. Me grupeerime funktsionaalselt sarnased elemendid, et järgida seotuse põhimõtet. Nüüd, kui me peame süsteemi toimimise printsiipi muutma, liikudes hash-struktuurilt massiivi, saame lihtsalt kasutada klassi SupplyHold, puudutamata teisi mooduleid. Nii et kui logistikaülem muudab midagi oma sektsioonis, jääb ülejäänud jaam puutumata. Klass SpaceStation ei saa sellest muutusest isegi teadlikuks.
Meie ohvitserid, kes töötavad kosmosejaamas, on tõenäoliselt muudatustest rõõmsad, kuna nad saavad taotleda just neid, mis neile vajalikud. Pange tähele, et koodis on sellised meetodid nagu report_supplies ja report_fuel, mis sisalduvad klassides SupplyHold ja FuelTank. Mis juhtub, kui Maa palub muuta aruannete koostamise viisi? Mõlemat klassi, SupplyHold ja FuelTank, tuleb muuta. Aga mis siis, kui tuleb muuta kütuse ja tarvikute tarnimise viisi? Ilmselt tuleb siis jälle muuta kõiki neid samu klasse. See rikub juba SRP-printsiipi. Parandame selle.
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 0
@linked_item.get_supplies.each do |type, quantity|
puts "#{type} available: #{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 available: 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.Selles, viimases versioonis on ülesanded jagatud kaheks uueks klassiks, FuelReporter ja SupplyReporter. Mõlemad on tütreklassid Reporter klassi suhtes. Lisaks oleme lisanud eksemplarimuutujad SpaceStation klassi, et vajadusel vajalik allklass initsialiseerida. Nüüd, kui Maa otsustab midagi veel muuta, teeme parandused allklassidesse, mitte põhiklassi.
Loomulikult sõltuvad mõned klassid endiselt üksteisest. Näiteks sõltub SupplyReporter SupplyHold objektist ja FuelReporter sõltub FuelTank objektist. Muidugi peavad kiirendid olema seotud kütusepaagiga. Kuid nüüd paistab kõik loogilisena ning muudatuste tegemine ei ole eriti keeruline — ühe objekti koodi redigeerimine ei mõjuta teist liiga palju.
Seega oleme loonud modulaarse koodi, kus iga objekti/kliendi kohustused on täpselt määratletud. Sellise koodiga töötamine ei ole probleem ning selle hooldamine on lihtne ülesanne. Kogu "jumalik objekt" oleme muutnud SRP-ks.
Skillbox soovitab:
- Kaks aastat praktilist kursust .
- Veebikursus .
- Praktiline aastakursus .
Allikas: habr.com
