
Nga përkthyesi: publikuar për ju rreth përdorimit të parimeve SOLID në programim. Informacioni nga artikulli do të jetë i dobishëm si për fillestarët, ashtu edhe për programuesit me përvojë.
Nëse merresh me zhvillimin, me siguri ke dëgjuar për parimet SOLID. Ato i japin mundësinë programuesit të shkruajë kod të pastër, të strukturuar mirë dhe lehtësisht të mbajtur. Duhet të theksohet se në programim ka disa qasje për mënyrën se si duhet bërë një punë e caktuar. Specialistët e ndryshëm kanë ide dhe kuptime të ndryshme për "rrugën e duhur", gjithçka varet nga përvoja e secilit. Megjithatë, idetë e shpallura në SOLID pranohen praktikisht nga të gjithë përfaqësuesit e komunitetit IT. Ato janë bërë një pikënisje për shfaqjen dhe zhvillimin e shumë metodave të mira të menaxhimit të zhvillimit.
Le të kuptojmë se çfarë janë parimet SOLID dhe si na ndihmojnë.
Skillbox rekomandon: Kurs praktik .
Kujtojmë: për të gjithë lexuesit e "Habra" — zbritje prej 10,000 rublej për regjistrimin në çdo kurs Skillbox me kodin promocional "Habr".
Çfarë është SOLID?
Ky term është një akronim, secila shkronjë e termit është fillimi i emrit të një principi të caktuar:
- SPrincipi i Përgjegjësisë së Vetme. Një modul mund të ketë një dhe vetëm një shkak për të ndryshuar.
- (principe i hapjes/mbylljes). Klasat dhe elementet e tjera duhet të jenë të hapura për zgjerim, por të mbyllura për modifikim.
- (principe i zëvendësimit të Liskov). Funksionet që përdorin tipin bazë duhet të kenë mundësinë të përdorin nën tipet e tipit bazë, pa e ditur këtë.
- (principe i ndarjes së interfesës). Entitetet programore nuk duhet të varen nga metodat që nuk i përdorin.
- (principe i inversimit të varësive). Modulet e niveleve të larta nuk duhet të varen nga modulit e niveleve të ulëta.
Principe e Vetëm një Përgjegjësie
Principi i përgjegjësisë së vetme (SRP) thotë se çdo klasë ose modul në program duhet të jetë përgjegjës vetëm për një pjesë të funksionalitetit të këtij programi. Për më tepër, elementet e kësaj përgjegjësie duhet të jenë të lidhura me klasën e tyre, dhe jo të shpërndara në klasa të pa lidhura. Zhvilluesi dhe evangjelisti kryesor i SRP, Robert C. Martin, e përshkruan përgjegjësinë si arsyen për ndryshim. Ai fillimisht e propozoi këtë termin si një nga elementët e punës së tij "Parimet e Projektimit të Orientuar nga Objektet". Në koncept janë përfshirë shumë nga rregullat e lidhjes, të cilat ishin përkufizuar më parë nga Tom DeMarco.
Edhe në koncept janë përfshirë disa nocione të formuluara nga David Parnas. Dy të parët janë — inkapsulimi dhe fshehja e informacionit. Parnas theksonte se ndarja e sistemit në module të veçanta nuk duhet të bazohet në analizat e diagrameve ose rrjedhave të ekzekutimit. Çdo modul duhet të përmbajë një zgjidhje të caktuar, e cila ofron informacion minimal për klientët.
Për më tepër, Martini dha një shembull interesante me menaxherët e lartë të kompanisë (COO, CTO, CFO), secili prej të cilëve përdor software specifik biznesi për qëllime të ndryshme. Në fund, secili prej tyre mund të implementojë ndryshime në software pa prekur interesat e menaxherëve të tjerë.
Objekti hyjnor
Si zakonisht, mënyra më e mirë për të mësuar SRP-në është ta shihni gjithçka në veprim. Le të shohim një pjesë të programit që NUK i përmbush parimin e një përgjegjësie. Ky është kodi Ruby, që përshkruan sjelljen dhe atributet e stacionit hapësinor.
Shikoni shembullin dhe provoni të identifikoni të volgende:
Detyrat e atyre objekteve që shpallen në klasën SpaceStation.
Atyre që mund të kenë interes për punën e stacionit hapësinor.
klasa Stacioni Hapësinor
def initialize
@supplies = {}
@fuel = 0
end
def run_sensors
puts "----- Veprimi i Sensorëve -----"
puts "Duke ekzekutuar sensorët!"
end
def load_supplies(type, quantity)
puts "----- Veprimi i Furnizimit -----"
puts "Duke ngarkuar #{quantity} njësi të #{type} në magazinën e furnizimeve."
if @supplies[type]
@supplies[type] += quantity
else
@supplies[type] = quantity
end
end
def use_supplies(type, quantity)
puts "----- Veprimi i Furnizimit -----"
if @supplies[type] != nil && @supplies[type] > quantity
puts "Duke përdorur #{quantity} të #{type} nga magazina e furnizimeve."
@supplies[type] -= quantity
else
puts "Gabim në Furnizim: Mjaftueshëm #{type} në magazinën e furnizimeve."
end
end
def report_supplies
puts "----- Raporti i Furnizimeve -----"
if @supplies.keys.length > 0
@supplies.each do |type, quantity|
puts "#{type} në dispozicion: #{quantity} njësi"
end
else
puts "Magazina e furnizimeve është bosh."
end
end
def load_fuel(quantity)
puts "----- Veprimi i Karburantit -----"
puts "Duke ngarkuar #{quantity} njësi karburant në rezervuar."
@fuel += quantity
end
def report_fuel
puts "----- Raporti i Karburantit -----"
puts "#{@fuel} njësi karburant në dispozicion."
end
def activate_thrusters
puts "----- Veprimi i Pompave -----"
if @fuel >= 10
puts "Veprimi i pompave ishte i suksesshëm."
@fuel -= 10
else
puts "Gabim i Pompas: Mjaftueshëm karburant në dispozicion."
end
end
endPra, stacioni ynë hapësinor nuk është funksional (mendoj se nuk do marr një telefonatë nga NASA në të ardhmen e afërt), por këtu ka shumë për të analizuar.
Pra, klasa SpaceStation ka disa përgjegjësi të ndryshme (ose detyra). Të gjitha mund të shpërndahen në lloje:
- sensorët;
- furnizimi (materiale konsumueshëm);
- karburanti;
- acceleratorët.
Megjithëse askush nga punonjësit e stacionit nuk është përcaktuar në klasë, ne mund të paraqesim lehtësisht kush është përgjegjës për çfarë. Me shumë mundësi, punonjësi shkencor kontrollon sensorët, logjistika është përgjegjëse për furnizimin me ресурs, inxhinieri është përgjegjës për rezervat e karburantit dhe piloti kontrollon acceleratorët.
A mund të themi se ky program nuk i përmbush standardet e SRP? Po, sigurisht. Por klasa SpaceStation është një "objekt hyjnor" tipik, i cili di gjithçka dhe bën gjithçka. Ky është anti-shembulli kryesor në programimin e orientuar në objekt. Për një fillestar, këto objekte janë jashtëzakonisht të vështira për t'u mirëmbajtur. Deri tani, programi është shumë i thjeshtë, po, por imagjinoni se çfarë do të ndodhë nëse shtojmë funksione të reja. Ndoshta stacionit tonë kozmik i nevojitet një qendër mjekësore ose një sallë takimesh. Sa më shumë funskione të ketë, aq më shumë do të rritet SpaceStation. Po ashtu, pasi ky objekt do të lidhet me të tjerët, mirëmbajtja e të gjithës do të bëhet edhe më e komplikuar. Si rezultat, mund të prishim, për shembull, drejtorët. Nëse një kërkues shkencor kërkon ndryshime në punën me(sensorët), kjo mund të ndikojë ndjeshëm në sistemet e komunikacionit të stacionit.
Shkelja e parimit SRP mund të sjellë një fitore taktike të përkohshme, por në fund do të "humim luftën", pasi do të jetë shumë e vështirë të shërbesh për një monstruozitet të tillë në të ardhmen. Më mirë është të ndash programin në pjesë të veçanta kodimi, secila e cila përgjigjet për realizimin e një operacioni të caktuar. Duke e kuptuar këtë, le të modifikojmë klasën SpaceStation.
Të ndajmë përgjegjësitë
Më lart ne përcaktuam katër lloje operacionesh që kontrollohen nga klasa SpaceStation. Gjatë rifaktorizimit, do t'i kemi parasysh. Kodi i përditësuar përputhet më mirë me 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} 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
endKa janë shumë ndryshime, programi tani duket pa dyshim më mirë. Tani klasa jonë SpaceStation është bërë më shumë një kontejner, në të cilin iniciohen operacione për pjesët e varura, duke përfshirë një grup sensorësh, sistemin e furnizimit me materiale, rezervuarin e karburantit dhe acceleratoret.
Për çdo variabël tani ka një klasë përkatëse: Sensors; SupplyHold; FuelTank; Thrusters.
Në këtë version të kodit ka disa ndryshime të rëndësishme. Çështja është që funksionet e veçanta jo vetëm që janë inkapsuluar në klasat e tyre, ato janë organizuar në një mënyrë që bëhen të parashikueshme dhe të qëndrueshme. Ne grupojmë elementë të ngjashëm sipas funksionalitetit për të ndjekur parimin e lidhshmërisë. Tani, nëse na duhet të ndryshojmë mënyrën e funksionimit të sistemit, duke kaluar nga struktura e heshit në një array, thjesht përdorni klasën SupplyHold, pa prekur modulat e tjera. Kështu, nëse oficeri përgjegjës për logjistikën bën ndonjë ndryshim në seksionin e tij, elementët e tjerë të stacionit do të mbeten të paprekur. Dheu, klasa SpaceStation nuk do të jetë as në dijeni të ndryshimeve.
Oficerët tanë që punojnë në stacionin kozmik, ndoshta janë të lumtur me ndryshimet, pasi mund të kërkojnë ato që u nevojiten atyre. Vini re se në kod ka metoda si report_supplies dhe report_fuel, që ndodhen në klasat SupplyHold dhe FuelTank. Çfarë do të ndodhte nëse Toka kërkon të ndryshojë mënyrën e formimit të raporteve? Do të nevojitej të ndryshoheshin të dy klasat, SupplyHold dhe FuelTank. Dhe nëse duhet të ndryshonim mënyrën e dorëzimit të karburantit dhe furnizimeve? Ka shumë të ngjarë që do të duhet të ndryshonim përsëri të gjitha ato të njëjtat klasa. Kjo tashmë është një shkelje e parimit SRP. Le ta rregullojmë këtë.
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.Në këtë version të fundit të programit, detyrat janë ndarë në dy klasa të reja, FuelReporter dhe SupplyReporter. Të dyja janë nënshtesa të klasës Reporter. Për më tepër, ne kemi shtuar variabla ekzemplar në klasën SpaceStation në mënyrë që, nëse është e nevojshme, të inicializojmë nënklasën e duhur. Tani, nëse Toka vendos të ndryshojë diçka tjetër, ne do të bëjmë ndryshimet në nënklasat, jo në klasën kryesore.
Sigurisht, disa klasa ende varen një nga tjetra. kështu, objekti SupplyReporter varet nga SupplyHold, ndërsa FuelReporter varet nga FuelTank. Natyrisht, përshpejtuesit duhet të lidhen me rezervuarin e karburantit. Por këtu gjithçka duket logjike, dhe të bësh ndryshime nuk do të jetë veçanërisht e vështirë — redaktimi i kodit të një objekti nuk do të ndikojë shumë te tjetri.
Kështu, ne krijuam kod modular, ku detyrat e çdo objekti/klase janë përcaktuar qartë. Të punosh me një kod të tillë nuk është problem, mirëmbajtja do të jetë një detyrë e thjeshtë. E gjithë "objekti hyjnor" e kemi transformuar në SRP.
Skillbox rekomandon:
- Kurs praktik dyvjeçar .
- Kurs online .
- Kurs praktik njëvjeçar .
Burimi: habr.com
