
Tõlkijalt: avalikustatud teie jaoks SOLID-põhimõtete kasutamisest programmeerimises. Artiklist saadud teave on kasulik nii algajatele kui ka kogenud programmeerijatele.
Kui tegelete arendusega, olete tõenäoliselt kuulnud SOLID-põhimõtetest. Need annavad võimaluse programmeerijale kirjutada puhtaid, hästi struktureeritud ja hõlpsasti hallatavaid koode. Oluline on märkida, et programmeerimises on mitmeid lähenemisviise, kuidas õiget tööd teha. Erinevatel spetsialistidel on erinevad ideed ja arusaamad "õigest teest", kõik sõltub igaühe kogemustest. Siiski on SOLIDis väljatud ideed aktsepteeritud praktiliselt kõikide IT-ühiskonna esindajate seas. Need on saanud lähtepunktiks paljude heade arenduse juhtimise meetodite tekkimisele ja arengule.
Vaatame, mis on SOLID-põhimõtted ja kuidas need meid aitavad.
Skillbox soovitab: Praktiline kursus .
Tuletame meelde: kõigile «Habra» lugejatele — 10 000 rubla soodustus, kui registreerite end Skillboxi mis tahes kursusele promokoodi «Habr» abil.
Mis on SOLID?
See termin on lühend, kus iga tähe algusosa viitab konkreetse põhimõtte nimele:
- SSingle Responsibility Principle (ühe vastutuse põhimõte). Moodulil võib olla üks ja ainult üks põhjus muutuda.
- (avatuse/sulgemise põhimõte). Klassid ja muud elemendid peaksid olema avatud laiendamiseks, kuid suletud muutmiseks.
- (Liskovi substiutsiooni põhimõte). Funktsioonid, mis kasutavad põhityypi, peaksid saama kasutada põhitypist alam tüüpe, teadmata sellest.
- (interfaci segregatsiooni põhimõte). Tarkvaralised olendid ei tohi sõltuda meetoditest, mida nad ei kasuta.
- (sõltuvuste inversiooni põhimõte). Ülemiste tasandite moodulid ei tohiks sõltuda alumiste tasandite moodulitest.
Ühe vastutuse põhimõte
Ühe vastutuse põhimõte (SRP) ütleb, et iga klass või moodul programmis peaks vastutama ainult ühe osa selle programmi funktsionaalsusest. Samuti peab selle vastutuse elemendid olema seotud oma klassiga, mitte jagatud mittevastavate klasside vahel. Arendaja ja SRP peamine evangelist, Robert C. Martin, kirjeldab vastutust kui muutuste põhjust. Alguses pakkus ta selle termini välja ühe elemendina oma töös "Objektorienteeritud disaini põhimõtted". Kontseptsiooni kuulus palju eespool määratletud seotuse seadusest, mille määratles varem Tom DeMarco.
Kontseptsiooni kuulusid ka mitmed mõisted, mille töötas välja David Parnas. Kaks peamist neist on kapseldamine ja teabe peitmine. Parnas väitis, et süsteemi jagamine eraldi mooduliteks ei tohiks põhineda diagrammi analüüsil ega täitmisvoogudel. Iga moodul peaks sisaldama kindlat lahendust, mis annab minimaalset teavet klientidele.
Muide, Martin tõi huvitava näite ettevõtte kõrgemast juhtkonnast (COO, CTO, CFO), kellest igaühel on erinevatel põhjustel spetsiifilised äritarkvara rakendused. Seetõttu võib igaüks neist tarkvaras muudatusi teha, mõjutamata teiste juhtide huve.
Jumalik objekt
Nagu tavaliselt, on parim viis SRP-d õppida näha seda kõik toimimas. Vaadakem programmi osa, mis EI vasta üksiku vastutuse põhimõttele. See on Ruby-kood, mis kirjeldab kosmosejaama käitumist ja atribuute.
Vaadake näidet ja proovige määrata järgmist:
Need objektid, mille kohustused on määratletud SpaceStation klassis.
Need, kes võivad olla huvitatud kosmosejaamast.
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
endKuna meie kosmosejaam ei tööta (ma arvan, et NASA ei helista mulle lähitulevikus), on siin siiski midagi, mida analüüsida.
Nii et klassil SpaceStation on mitu erinevat vastutust (või ülesannet). Kõik need saab jagada tüüpideks:
- andurid;
- varustus (tarbijamaterjalid);
- kütus;
- kiirendid.
Kuigi jaama töötajaid ei ole klassi määratud, on meil lihtne ette kujutada, kes mille eest vastutab. Tõenäoliselt kontrollib teadustöötaja andureid, logistika vastutab ressurside varustamise eest, insener hooldab kütusevarusid ning piloot jälgib kiirendeid.
Kas me saame öelda, et see programm ei vasta SRP-le? Jah, loomulikult. Kuid klass SpaceStation on tüüpiline "jumalik objekt", mis teab kõike ja teeb kõike. See on peamine antipattern objektorienteeritud programmeerimises. Algajate jaoks on sellised objektid äärmiselt keerulised hooldada. Praegu on programm väga lihtne, jah, kuid kujutage ette, mis juhtub, kui lisame uusi funktsioone. Võib-olla on meie kosmosejaamal vaja meditsiinituba või koosolekuruumi. Ja mida rohkem funktsioone, seda suuremaks muutub SpaceStation. Kuna see objekt on seotud teistega, siis muutub kogu kompleksi hooldamine veelgi keerulisemaks. Lõppkokkuvõttes võime häirida näiteks kiirendite toimimist. Kui teadlane soovib muudatusi sensoritega töötamisse, võib see tõepoolest mõjutada jaama side süsteeme.
SRP-printsiibi rikkumine võib anda lühiajalise taktikalise võidu, kuid lõpuks kaotame "sõja", sellise monstri teenindamine tulevikus muutub äärmiselt keeruliseks. Parim on jagada programm eraldi koodilõikudeks, millest igaühe ülesanne on teatud tegevuse täitmine. Olles sellest aru saanud, muudame klassi SpaceStation.
Jagame vastutuse
Ülalpool määratlesime neli tüüpi toiminguid, mida kontrollib klass SpaceStation. Refaktoreerimise käigus peame neid silmas pidama. 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
endMuudatusi on palju, programm näeb nüüd tõeliselt parem välja. Meie SpaceStation klassist sai nüüd pigem konteiner, kus initsialiseeritakse sõltuvate osade operatsioonid, sealhulgas sensorite kogum, tarvikute varustussüsteem, kütusepaak ja kiirendid.
Iga muutuja jaoks on nüüd vastav klass: Sensors; SupplyHold; FuelTank; Thrusters.
Selles koodiversioonis on mitmeid olulisi muudatusi. Esiteks, eraldi funktsioonid ei ole ainult isoleeritud oma klassidesse, vaid need on organiseeritud viisil, et need muutuvad ettearvatavateks ja järjekindlateks. Me grupeerime sarnaste funktsionaalsustega elemendid, et järgida sidususe põhimõtet. Nüüd, kui me peame süsteemi tööpõhimõtet muutma, liikudes hash-struktuurilt massiivi suunas, kasutage lihtsalt klassi SupplyHold ja teisi mooduleid pole vaja puudutada. Seega, kui logistikaülem midagi oma sektsioonis muudab, jäävad ülejäänud jaamad muutmata. Samal ajal ei ole klass SpaceStation isegi teadlik muudatustest.
Meie ohvitserid, kes töötavad kosmosejaamas, on tõenäoliselt muutustest rõõmsad, kuna nad saavad taotleda neid ressursse, mis on neile vajalikud. Pange tähele, et koodis on sellised meetodid nagu report_supplies ja report_fuel, mis kuuluvad klassidele SupplyHold ja FuelTank. Mis juhtub, kui Maa palub muuta aruandekooste viisi? Tuleb muuta mõlemat klassi, SupplyHold ja FuelTank. Aga ent kui muutub kütuse ja tarvikute tarnimise viis? Tõenäoliselt tuleb jälle muuda kõik need samad klassid. See aga rikub 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 alamharud Reporter klassist. Lisaks lisasime SpaceStation klassile eksemplari muutujad, et vajadusel õiged alamklassid initsialiseerida. Nii et kui Maa otsustab midagi veel muuta, teeme me muudatused alamklassidesse, mitte põhiklassi.
Muidugi, mõned klassid sõltuvad endiselt üksteisest. Näiteks objekt SupplyReporter sõltub SupplyHold'ist, samas kui FuelReporter sõltub FuelTank'ist. Loomulikult peavad kiirendid olema seotud kütuse mahutiga. Kuid siin näeb kõik välja loogiline ning muudatuste tegemine ei ole eriti keeruline — ühe objekti koodi muutmine ei mõjuta teist oluliselt.
Nii oleme loonud modulaarse koodi, kus iga objekti/klassi ülesanded on selgelt määratletud. Sellise koodiga töötamine ei ole probleem ja selle hooldamine on lihtne ülesanne. Kogu 'jumalik objekt' on muudetud SRP-ks.
Skillbox soovitab:
- Kaheaastane praktiline kursus .
- Veebikursus .
- Praktiline aastakursus .
Allikas: habr.com
