
FrÄn översÀttaren: har publicerat för dig om anvÀndningen av SOLID-principerna i programmering. Informationen i artikeln kommer att vara anvÀndbar bÄde för nybörjare och erfarna programmerare.
Om du Àr involverad i utveckling har du sannolikt hört talas om SOLID-principerna. De ger programmerare möjlighet att skriva ren, vÀldefinierad och lÀttunderhÄllen kod. Det Àr vÀrt att notera att det finns flera tillvÀgagÄngssÀtt i programmering nÀr det gÀller att utföra ett visst arbete pÄ rÀtt sÀtt. Olika specialister har olika idéer och förstÄelser för vad som utgör "den rÀtta vÀgen"; allt beror pÄ personens erfarenhet. Trots detta accepteras de idéer som proklameras i SOLID av nÀstan alla inom IT-gemenskapen. De har blivit utgÄngspunkten för utveckling och framvÀxt av mÄnga bra metoder för projektledning.
LÄt oss gÄ igenom vad SOLID-principerna Àr och hur de hjÀlper oss.
Skillbox rekommenderar: Praktisk kurs .
PĂ„minnelse: för alla lĂ€sare av "Habr" â 10 000 rubel rabatt vid registrering för vilken kurs som helst pĂ„ Skillbox med rabattkoden "Habr".
Vad Àr SOLID?
Denna term Àr en akronym, dÀr varje bokstav i termen inleder namnet pÄ en specifik princip:
- SSingle Responsibility Principle (principen om enbart ansvar). En modul kan ha en och endast en orsak till förÀndring.
- (principen om öppenhet/stÀngdhet). Klasser och andra element ska vara öppna för utvidgning men stÀngda för modifiering.
- (principen om Liskovs substitution). Funktioner som anvÀnder en bas typ ska kunna anvÀnda under typer av bas typen utan att veta om det.
- â(principen om grĂ€nssnittets uppdelning). Programvaruenheter ska inte vara beroende av metoder som de inte anvĂ€nder.
- (principen om beroendeinversion). Moduler pÄ högre nivÄer ska inte vara beroende av moduler pÄ lÀgre nivÄer.
Principen om enbart ansvar
â
Enligt principen om enhetligt ansvar (SRP) ska varje klass eller modul i programmet endast ha ansvar för en del av programmets funktionalitet. Dessutom ska elementen av detta ansvar vara knutna till sin klass och inte spridas över orelaterade klasser. Utvecklaren och huvudambassadören för SRP, Robert C. Martin, beskriver ansvar som orsaken till förÀndringar. Han föreslog ursprungligen denna term som en av elementen i sitt arbete "Principer för objektorienterad design". MÄnga aspekter av begreppet koppling, som tidigare definierades av Tom DeMarco, ingick ocksÄ.
Flera begrepp som formulerades av David Parnas ingick ocksÄ i konceptet. De tvÄ centrala Àr inkapsling och informationsdölja. Parnas hÀvdade att uppdelningen av systemet i separata moduler inte bör baseras pÄ analys av flödesscheman eller exekveringsflöden. Varje modul bör innehÄlla en viss lösning som ger minimalt med information till kunderna.
För övrigt nÀmnde Martin ett intressant exempel med högre chefer i företaget (COO, CTO, CFO), dÀr var och en anvÀnder specifik affÀrsprogramvara för olika ÀndamÄl. Resultatet Àr att var och en av dem kan implementera förÀndringar i programvaran utan att pÄverka de andra chefernas intressen.
Gudomlig objekt
Som vanligt Àr det bÀsta sÀttet att förstÄ SRP att se det i aktion. LÄt oss titta pÄ ett kodavsnitt som INTE följer principen om enhetligt ansvar. Detta Àr Ruby-kod som beskriver beteendet och attributen för en rymdstation.
Granska exemplet och försök att identifiera följande:
Ansvar för de objekt som deklareras i klassen SpaceStation.
De som kan vara intresserade av att arbeta med rymdstationen.
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
endVÄr rymdstation fungerar egentligen inte (jag tvivlar pÄ att jag fÄr ett samtal frÄn NASA inom en överskÄdlig framtid), men hÀr finns ÀndÄ nÄgot att analysera.
SÄ, klassen SpaceStation har flera olika ansvarsomrÄden (eller uppgifter). Dessa kan delas upp efter typ:
- sensorer;
- förnödenheter (förbrukningsvaror);
- brÀnsle;
- raketmotorer.
Ăven om ingen av stationens anstĂ€llda definieras i klassen kan vi enkelt förestĂ€lla oss vem som ansvarar för vad. Sannolikt övervakar en forskare sensorerna, en logistiker ansvarar för förnödenheterna, en ingenjör sköter brĂ€nslelagret, och en pilot kontrollerar raketmotorerna.
Kan vi sĂ€ga att det hĂ€r programmet inte följer SRP? Ja, absolut. Men klassen SpaceStation Ă€r en typisk "gudomlig objekt", som vet allt och gör allt. Detta Ă€r en grundlĂ€ggande anti-mall inom objektorienterad programmering. För en nybörjare Ă€r sĂ„dana objekt extremt svĂ„ra att underhĂ„lla. Ăn sĂ„ lĂ€nge Ă€r programmet vĂ€ldigt enkelt, ja, men förestĂ€ll dig vad som hĂ€nder om vi lĂ€gger till nya funktioner. Kanske behöver vĂ„r rymdstation en medicinsk enhet eller ett konferensrum. Ju fler funktioner, desto större blir SpaceStation. Eftersom detta objekt kommer att kopplas samman med andra, blir underhĂ„llet av hela komplexet Ă€nnu mer komplicerat. I slutĂ€ndan kan vi riskera att störa till exempel acceleratorerna. Om en forskare begĂ€r Ă€ndringar i arbetet med sensorer kan det mycket vĂ€l pĂ„verka stationens kommunikationssystem.
Att bryta mot SRP-principen kan ge en kortsiktig taktisk vinst, men i slutÀndan "förlorar vi kriget", att underhÄlla en sÄdan monster i framtiden kommer att bli ganska svÄrt. Det Àr bÀst att dela upp programmet i separata kodavsnitt, var och en ansvarig för att utföra en specifik operation. Med det i Ätanke, lÄt oss förÀndra klassen SpaceStation.
Fördela ansvar
Ovan har vi definierat fyra typer av operationer som kontrolleras av klassen SpaceStation. Vid refaktorering kommer vi att ha dem i Ätanke. Den uppdaterade koden följer SRP bÀttre.
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
endDet har skett mÄnga förÀndringar, programmet ser nu definitivt bÀttre ut. VÄr klass SpaceStation Àr nu mer som en behÄllare dÀr operationer för beroende delar initieras, inklusive en uppsÀttning sensorer, ett system för leverans av förbrukningsmaterial, en brÀnsletank och motorer.
För varje variabel finns det nu en motsvarande klass: Sensors; SupplyHold; FuelTank; Thrusters.
I denna version av koden finns det flera viktiga förÀndringar. Faktum Àr att de enskilda funktionerna inte bara Àr inkapslade i sina egna klasser, utan ocksÄ organiserade pÄ ett sÀtt som gör dem förutsÀgbara och konsekventa. Vi grupperar funktionellt liknande element för att följa principen om sammanhang. Nu, om vi behöver Àndra systemets arbetsprincip, till exempel frÄn en hash-struktur till en array, kan vi enkelt anvÀnda klassen SupplyHold utan att pÄverka andra moduler. DÀrmed, om en officer som ansvarar för logistik gör nÄgra Àndringar i sin avdelning, förblir de andra elementen i stationen opÄverkade. Samtidigt kommer klassen SpaceStation inte ens att vara medveten om förÀndringarna.
VÄra officerare som arbetar pÄ rymdstationen Àr förmodligen glada över förÀndringarna, eftersom de kan begÀra det som de behöver. Observera att koden innehÄller metoder som report_supplies och report_fuel, som finns i klasserna SupplyHold och FuelTank. Vad hÀnder om jorden ber om att Àndra sÀttet att generera rapporterna? BÄda klasserna, SupplyHold och FuelTank, behöver Àndras. Och om vi behöver Àndra sÀttet att leverera brÀnsle och förnödenheter? DÄ mÄste vi sannolikt Àndra alla dessa klasser igen. Och det skulle vara ett brott mot SRP-principen. LÄt oss ÄtgÀrda det.
klass Rymdstation
attr_reader :sensorer, :försörjningsutrymme, :försörjningsrapportör,
:brÀnsletank, :brÀnslerapportör, :dÀmpare
def initialize
@sensorer = Sensorer.new
@försörjningsutrymme = Försörjningsutrymme.new
@försörjningsrapportör = Försörjningsrapportör.new(@försörjningsutrymme)
@brÀnsletank = BrÀnsletank.new
@brÀnslerapportör = BrÀnslerapportör.new(@brÀnsletank)
@dÀmpare = DÀmpare.new(@brÀnsletank)
end
end
klass Sensorer
def run_sensors
puts "----- SensorÄtgÀrd -----"
puts "Kör sensorer!"
end
end
klass Försörjningsutrymme
attr_accessor :förnödenheter
attr_reader :rapportör
def initialize
@förnödenheter = {}
end
def get_supplies
@förnödenheter
end
def load_supplies(type, quantity)
puts "----- FörsörjningsÄtgÀrd -----"
puts "LÀser in #{quantity} enheter av #{type} i försörjningsutrymmet."
if @förnödenheter[type]
@förnödenheter[type] += quantity
else
@förnödenheter[type] = quantity
end
end
def use_supplies(type, quantity)
puts "----- FörsörjningsÄtgÀrd -----"
if @förnödenheter[type] != nil && @förnödenheter[type] > quantity
puts "AnvÀnder #{quantity} av #{type} frÄn försörjningsutrymmet."
@förnödenheter[type] -= quantity
else
puts "Fel med försörjning: OtillrÀckligt med #{type} i försörjningsutrymmet."
end
end
end
klass BrÀnsletank
attr_accessor :brÀnsle
attr_reader :rapportör
def initialize
@brÀnsle = 0
end
def get_fuel_levels
@brÀnsle
end
def load_fuel(quantity)
puts "----- BrÀnsleÄtgÀrd -----"
puts "LÀser in #{quantity} enheter av brÀnsle i tanken."
@brÀnsle += quantity
end
def use_fuel(quantity)
puts "----- BrÀnsleÄtgÀrd -----"
puts "AnvÀnder #{quantity} enheter av brÀnsle frÄn tanken."
@brÀnsle -= quantity
end
end
klass DĂ€mpare
BRĂNSLE_PER_DĂMPNING = 10
def initialize(brÀnsletank)
@lÀnkad_brÀnsletank = brÀnsletank
end
def activate_thrusters
puts "----- DÀmpningsÄtgÀrd -----"
if @lĂ€nkad_brĂ€nsletank.get_fuel_levels >= BRĂNSLE_PER_DĂMPNING
puts "DÀmpningsÄtgÀrd framgÄngsrik."
@lĂ€nkad_brĂ€nsletank.use_fuel(BRĂNSLE_PER_DĂMPNING)
else
puts "Fel med dÀmpare: OtillrÀckligt med brÀnsle tillgÀngligt."
end
end
end
klass Rapportör
def initialize(item, type)
@lÀnkad_item = item
@type = type
end
def report
puts "----- #{@type.capitalize} Rapport -----"
end
end
klass BrÀnslerapportör < Rapportör
def initialize(item)
super(item, "brÀnsle")
end
def report
super
puts "#{@lÀnkad_item.get_fuel_levels} enheter av brÀnsle tillgÀngliga."
end
end
klass Försörjningsrapportör 0
@lÀnkad_item.get_supplies.each do |type, quantity|
puts "#{type} tillgÀnglig: #{quantity} enheter"
end
else
puts "Försörjningsutrymmet Àr tomt."
end
end
end
iss = Rymdstation.new
iss.sensorer.run_sensors
# ----- SensorÄtgÀrd -----
# Kör sensorer!
iss.försörjningsutrymme.use_supplies("delar", 2)
# ----- FörsörjningsÄtgÀrd -----
# Fel med försörjning: OtillrÀckligt med delar i försörjningsutrymmet.
iss.försörjningsutrymme.load_supplies("delar", 10)
# ----- FörsörjningsÄtgÀrd -----
# LÀser in 10 enheter av delar i försörjningsutrymmet.
iss.försörjningsutrymme.use_supplies("delar", 2)
# ----- FörsörjningsÄtgÀrd -----
# AnvÀnder 2 av delar frÄn försörjningsutrymmet.
iss.försörjningsrapportör.report
# ----- Försörjningsrapport -----
# delar tillgÀngliga: 8 enheter
iss.dÀmpare.activate_thrusters
# ----- DÀmpningsÄtgÀrd -----
# Fel med dÀmpare: OtillrÀckligt med brÀnsle tillgÀngligt.
iss.brÀnsletank.load_fuel(100)
# ----- BrÀnsleÄtgÀrd -----
# LÀser in 100 enheter av brÀnsle i tanken.
iss.dÀmpare.activate_thrusters
# ----- DÀmpningsÄtgÀrd -----
# DÀmpningsÄtgÀrd framgÄngsrik.
# ----- BrÀnsleÄtgÀrd -----
# AnvÀnder 10 enheter av brÀnsle frÄn tanken.
iss.brÀnslerapportör.report
# ----- BrÀnsle rapport -----
# 90 enheter av brÀnsle tillgÀngliga.I den hÀr senaste versionen av programmet har uppgifterna delats upp i tvÄ nya klasser, FuelReporter och SupplyReporter. BÄda Àr underklasser till klassen Reporter. Dessutom har vi lagt till instansvariabler till klassen SpaceStation sÄ att vi, om nödvÀndigt, kan initiera rÀtt underklass. Nu, om jorden beslutar att Àndra nÄgot mer, kommer vi att göra Àndringar i underklasserna istÀllet för i huvudklassen.
SjĂ€lvklart Ă€r vissa klasser fortfarande beroende av varandra. SĂ„, objektet SupplyReporter beror pĂ„ SupplyHold, medan FuelReporter beror pĂ„ FuelTank. SjĂ€lvklart mĂ„ste acceleratorerna kopplas till brĂ€nsletanken. Men hĂ€r ser allt logiskt ut, och att göra Ă€ndringar kommer inte att vara sĂ€rskilt svĂ„rt â att redigera koden för ett objekt kommer inte att pĂ„verka det andra nĂ€mnvĂ€rt.
SÄledes har vi skapat modulÀr kod dÀr varje objekts/klassens uppgifter Àr tydligt definierade. Att arbeta med sÄdan kod Àr inga problem, dess underhÄll kommer att vara en enkel uppgift. Vi har omvandlat hela den "gudomliga objektet" till SRP.
Skillbox rekommenderar:
- TvÄÄrig praktisk kurs .
- Onlinekurs .
- Praktisk Ärskurs .
KĂ€lla: habr.com
