
Nga përkthyesi: publikuar për ju në lidhje me përdorimin e 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 merret me zhvillimin, shumë mund të keni dëgjuar për parimet SOLID. Ato i japin mundësinë programuesit të shkruajë kod të pastër, të mirëstrukturizuar dhe të lehtë për t'u mbajtur. Duhet të theksohet se në programim ka disa qasje se si duhet të realizohet çdo punë. Specialistë të 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 nga praktikisht të gjithë përfaqësuesit e komunitetit IT. Ato janë bërë një pikë fillestare për shfaqjen dhe zhvillimin e shumë metodave të mira të menaxhimit të zhvillimit.
Le të merremi me atë 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 rublesh për regjistrimin në çdo kurs Skillbox me kodin promovues «Habr».
Çfarë është SOLID?
Ky term është një akronim, çdo shkronjë e termit është fillimi i emrit të një principi të caktuar:
- SSingle Responsibility Principle (principi i përgjegjësisë së vetme). Një modul mund të ketë një dhe vetëm një arsye për ndryshim.
- (principi i hapjes/mbajtjes mbyllur). Klasa dhe elemente të tjera duhet të jenë të hapura për zgjerim, por të mbyllura për modifikim.
- (principi i zëvendësimit të Liskovit). Funksionet që përdorin tipin bazë duhet të kenë mundësi të përdorin nën-tipat e tipit bazë, pa e ditur këtë.
- (principi i ndarjes së ndërfaqeve). Entitetet programues duhet të mos varen nga metodat që ato nuk përdorin.
- (principi i inverzimit të varësisë). Modulët e niveleve të larta nuk duhet të varen nga modulët e niveleve të ulta.
Principi i përgjegjësisë së vetme
Principi i përgjegjësisë unike (SRP) thotë se çdo klasë ose moduli në program duhet të ketë përgjegjësi 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ërndahen në klasa të pa lidhura. Zhvilluesi dhe evangjelisti kryesor i SRP, Robert C. Martin, e përshkruan përgjegjësinë si shkakun e ndryshimeve. Ai fillimisht e propozoi këtë termin si një nga elementët e punës së tij "Principet e dizajnit të orientuar nga objekti". Koncepti përfshiu shumë nga rregullat e lidhshmërisë, të cilat ishin përcaktuar më parë nga Tom DeMarco.
Po ashtu, në koncept kanë hyrë disa nocione të formuluara nga David Parnas. Dy të parat janë enkapusulimi dhe fshehja e informacionit. Parnas konsideronte se ndarja e sistemit në module të veçanta nuk duhet të bazohet në analizën e diagramëve të blloqeve ose të rrjedhave të ekzekutimit. Çdo modul duhet të përmbajë një zgjidhje të caktuar që ofron minimumin e informacionit për klientët.
E kaluara, Martin sjell një shembull interesant me menaxherët e lartë të kompanisë (COO, CTO, CFO), secili prej të cilëve përdor softuer të veçantë për biznesin me qëllime të ndryshme. Në fund, çdo prej tyre mund të implementojë ndryshime në softuer, pa prekur interesat e menaxherëve të tjerë.
Objekti hyjnor
Si zakonisht, mënyra më e mirë për të mësuar SRP është ta shohësh atë në veprim. Le të shohim një pjesë të programit që NUK respekton principin e përgjegjësisë unike. Ky është një kod Ruby që përshkruan sjelljen dhe atributet e një stacioni hapësinor.
Rishikoni shembullin dhe provoni të përcaktoni:
Përgjegjësitë e objekteve që shpallën në klasën SpaceStation.
Atyre që mund të jenë të interesuar për funksionimin e stacionit hapësinor.
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
endPra realmente, stacioni ynë hapësinor nuk është funksional (mendoj se nuk do të marr një telefonatë nga NASA në të ardhmen e afërt), por ka diçka për të analizuar këtu.
Pra, klasi SpaceStation ka disa përgjegjësi të ndryshme (ose detyra). Të gjitha ato mund të ndahen sipas llojit:
- sensorë;
- furnizimi (artikujt e nevojshëm);
- karburant;
- shtytësit.
Edhe pse askush nga punonjësit e stacionit nuk është përshkruar në klasë, ne mund të imagjinojmë lehtë se kush është përgjegjës për çdo gjë. Ndoshta punonjësi shkencor kontrollon sensorët, logjisti merret me furnizimin me burime, inxhinieri është përgjegjës për rezervat e karburantit, ndërsa piloti kontrollon shtytësit.
A mund të themi se ky program nuk përputhet me SRP? Po, sigurisht. Por klasa SpaceStation është një «objekt i hyjnishëm» tipik, i cili di gjithçka dhe bën gjithçka. Ky është anti-shablloni kryesor në programimin objektor. Për një fillestar, këto objekte janë jashtëzakonisht të vështira për t'u menaxhuar. Deri tani, programi është shumë i thjeshtë, po, por imagjinoni çfarë do ndodhte nëse shtojmë funksione të reja. Ndoshta stacioni ynë hapësinor do të ketë nevojë për një qendër mjekësore ose një dhomë takimesh. Dhe sa më shumë funksione të shtohen, aq më i madh do të bëhet SpaceStation. Duke qenë se ky objekt do të lidhjet me të tjerët, mbajtja e gjithë kompleksit do të bëhet edhe më e komplikuar. Në fund, ne mund të prishim funksionimin, për shembull, të acceleratatorëve. Nëse një shkencëtar kërkon ndryshime në funksionimin me sensorët, kjo mund të ndikojë në sistemet e komunikimit të stacionit.
Shkelja e parimit SRP mund të sjellë një fitore taktike afatshkurtër, por në përfundim ne «do ta humbim luftën», menaxhimi i një monstru të tillë në të ardhmen do të bëhet shumë i vështirë. Më mirë do të ishte të ndajmë programin në pjesë të veçanta kode, secila prej të cilave është përgjegjëse për kryerjen e një operacioni të caktuar. Duke e kuptuar këtë, le të ndryshojmë klasën SpaceStation.
Të shpërndajmë përgjegjësinë
Më lart ne përcaktuam katër lloje operacionesh, të cilat kontrollohen nga klasa SpaceStation. Gjatë ristrukturimit, ne do t'i kemi ata parasysh. Kodi i azhurnuar 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 ndodhi shumë ndryshime, programi tani duket me të vërtetë më i mirë. Tani klasa jonë SpaceStation është më shumë një enë, ku iniciohen operacionet për pjesët e varura, duke përfshirë një set sensorësh, sistemin e furnizimit, tankun e karburantit dhe thrusterët.
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. E gjithë kjo lidhet me faktin se funksionet e veçanta jo vetëm që janë inkapsuluar në klasat e tyre përkatëse, por gjithashtu janë organizuar në një mënyrë që i bën ato parashikueshmërish dhe të qëndrueshme. Ne grupojmë elementët me funksionalitet të ngjashëm për të ndjekur parimin e lidhshmërisë. Tani, nëse na nevojitet të ndryshojmë mënyrën si funksionon sistema, duke kaluar nga një strukturë hash në një array, thjesht do të përdorim klasën SupplyHold, pa e prekur modulat e tjera. Kështu, nëse oficerët përgjegjës për logjistikën bëjnë ndonjë ndryshim në seksionin e tyre, elementët e tjerë të stacionit do të mbeten të paprekur. Po ashtu, klasa SpaceStation edhe nuk do ta kuptojë ndryshimin.
Oficerët tanë që punojnë në stacionin kozmik ndoshta janë të kënaqur 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, të përfshira në klasat SupplyHold dhe FuelTank. Çfarë do të ndodhë nëse Toka kërkon të ndryshojë mënyrën e ndërtimit të raporteve? Do të nevojitet të ndryshohen të dy klasat, SupplyHold dhe FuelTank. Po nëse duam të ndryshojmë mënyrën e dërgimit të karburantit dhe materialeve të konsumit? Ndoshta do të duhet të ndryshojmë përsëri të njëjtat klasa. Dhe kjo tashmë është një shkelje e parimit SRP. Le të e zgjidhim 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 ato janë nënpjesë të klasës Reporter. Për më tepër, ne kemi shtuar variabla eksperimentale në klasën SpaceStation në mënyrë që, kur të jetë e nevojshme, të initializejmë nënklasën e duhur. Tani, nëse Toka vendos të ndryshojë diçka tjetër, do të bëjmë modifikime në nënklasa dhe jo në klasën kryesore.
Natyrisht, disa klasa ende varen nga njëra-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 ketu gjithçka duket e arsyeshme, dhe bërrja e ndryshimeve nuk do të jetë veçanërisht e vështirë — redaktimi i kodit të një objekti nuk do të ndikojë shumë në tjetrin.
Kështu, ne krijuam kod modular, ku detyrat e çdo objekti/klase janë të përcaktuara saktësisht. Të punosh me një kod të tillë nuk është problem, mirëmbajtja e tij do të jetë një detyrë e thjeshtë. E gjithë "objekti hyjnor" e kemi transformuar në SRP.
Skillbox rekomandon:
- Kursi praktik dyvjeçar .
- Kurs online .
- Kurs praktik njëvjeçar .
Burimi: habr.com
