Frá þýðanda: birt fyrir þig grein eftir Severin Perez um að nota SOLID meginreglur í forritun. Upplýsingarnar úr greininni munu nýtast bæði byrjendum og reyndum forriturum.
Ef þú ert í þróun, hefur þú líklegast heyrt um SOLID meginreglurnar. Þeir gera forritaranum kleift að skrifa hreinan, vel uppbyggðan og auðvelt að viðhalda kóða. Það er athyglisvert að í forritun eru nokkrar aðferðir við hvernig á að framkvæma tiltekið starf rétt. Mismunandi sérfræðingar hafa mismunandi hugmyndir og skilning á „réttu leiðinni“; það veltur allt á reynslu hvers og eins. Hins vegar eru hugmyndirnar sem lýst er yfir í SOLID samþykktar af næstum öllum fulltrúum upplýsingatæknisamfélagsins. Þau urðu upphafið að tilkomu og þróun margra góðra þróunarstjórnunarvenja.
Við skulum skilja hvað SOLID meginreglurnar eru og hvernig þær hjálpa okkur.
The Single Responsibility Principle (SRP) segir að hver flokkur eða eining í forriti ætti aðeins að bera ábyrgð á einum hluta af virkni þess forrits. Auk þess ætti að úthluta þáttum þessarar ábyrgðar á þeirra eigin bekk, frekar en að dreifa þeim á óskylda bekki. Hönnuður SRP og aðalboðberi, Robert S. Martin, lýsir ábyrgð sem ástæðu breytinga. Hann lagði upphaflega til þetta hugtak sem einn af þáttunum í verki sínu "Principles of Object-oriented Design". Hugmyndin felur í sér mikið af tengimynstrinum sem áður var skilgreint af Tom DeMarco.
Hugmyndin innihélt einnig nokkur hugtök mótuð af David Parnas. Þær tvær helstu eru hjúpun og fela upplýsingar. Parnas hélt því fram að skipting kerfis í aðskildar einingar ætti ekki að byggjast á greiningu á blokkarritum eða framkvæmdarflæði. Sérhver eining verður að innihalda sérstaka lausn sem veitir viðskiptavinum lágmarksupplýsingar.
Við the vegur, Martin gaf áhugavert dæmi með æðstu stjórnendum fyrirtækis (COO, CTO, CFO), sem hver um sig notar sérstakan viðskiptahugbúnað í mismunandi tilgangi. Þar af leiðandi getur hver þeirra innleitt breytingar á hugbúnaðinum án þess að hafa áhrif á hagsmuni annarra stjórnenda.
Guðdómlegur hlutur
Eins og alltaf er besta leiðin til að læra SRP að sjá það í aðgerð. Við skulum skoða hluta áætlunarinnar sem fylgir EKKI reglunni um eina ábyrgð. Þetta er Ruby kóða sem lýsir hegðun og eiginleikum geimstöðvarinnar.
Skoðaðu dæmið og reyndu að ákvarða eftirfarandi:
Ábyrgð þessara hluta sem lýst er yfir í SpaceStation bekknum.
Þeir sem gætu haft áhuga á rekstri geimstöðvarinnar.
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
end
Reyndar er geimstöðin okkar óvirk (ég held að ég fái ekki símtal frá NASA í bráð), en það er eitthvað til að greina hér.
Þannig hefur SpaceStation bekkurinn nokkrar mismunandi skyldur (eða verkefni). Öllum þeim má skipta í gerðir:
skynjarar;
vistir (neysluvörur);
eldsneyti;
eldsneytisgjöf.
Jafnvel þó að enginn starfsmaður stöðvarinnar sé skipaður bekk, getum við vel ímyndað okkur hver ber ábyrgð á hverju. Líklegast stjórnar vísindamaðurinn skynjurunum, flutningafræðingurinn ber ábyrgð á því að útvega auðlindir, verkfræðingurinn ber ábyrgð á eldsneytisbirgðum og flugmaðurinn stjórnar hvatanum.
Getum við sagt að þetta forrit sé ekki SRP samhæft? Já að sjálfsögðu. En SpaceStation bekkurinn er dæmigerður „guðshlutur“ sem veit allt og gerir allt. Þetta er mikil andstæðingur-mynstur í hlutbundinni forritun. Fyrir byrjendur er mjög erfitt að viðhalda slíkum hlutum. Hingað til er forritið mjög einfalt, já, en ímyndaðu þér hvað mun gerast ef við bætum við nýjum eiginleikum. Kannski mun geimstöðin okkar þurfa læknastöð eða fundarherbergi. Og því fleiri aðgerðir sem eru, því meira mun SpaceStation stækka. Jæja, þar sem þessi aðstaða verður tengd öðrum, verður þjónusta við alla samstæðuna enn flóknari. Fyrir vikið getum við truflað virkni til dæmis hraða. Ef rannsakandi óskar eftir breytingum á skynjurum gæti það mjög vel haft áhrif á fjarskiptakerfi stöðvarinnar.
Brot á SRP meginreglunni getur gefið skammtíma taktískan sigur, en á endanum munum við „tapa stríðinu“ og það verður mjög erfitt að viðhalda slíku skrímsli í framtíðinni. Það er best að skipta forritinu í aðskilda hluta kóða, sem hver um sig ber ábyrgð á að framkvæma ákveðna aðgerð. Til að skilja þetta, skulum við breyta SpaceStation bekknum.
Dreifum ábyrgðinni
Hér að ofan skilgreindum við fjórar tegundir aðgerða sem stjórnað er af SpaceStation bekknum. Við munum hafa þau í huga við endurnýjun. Uppfærði kóðinn passar betur við 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} avalilable: #{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
end
Það eru miklar breytingar, prógrammið lítur örugglega betur út núna. Nú er SpaceStation flokkurinn okkar orðinn meira gámur þar sem aðgerðir eru hafnar fyrir háða hluta, þar á meðal skynjara, neyslubúnað, eldsneytistank og örvunartæki.
Fyrir hverja breytu er nú samsvarandi flokkur: Skynjarar; SupplyHold; Eldsneytistankur; Þrýstivélar.
Það eru nokkrar mikilvægar breytingar á þessari útgáfu kóðans. Málið er að einstakar aðgerðir eru ekki aðeins bundnar inn í eigin flokka, þær eru skipulagðar á þann hátt að þær verða fyrirsjáanlegar og samkvæmar. Við flokkum þætti með svipaða virkni til að fylgja meginreglunni um samræmi. Nú, ef við þurfum að breyta því hvernig kerfið virkar, fara úr kjötkássabyggingu yfir í fylki, notaðu bara SupplyHold flokkinn; við þurfum ekki að snerta aðrar einingar. Þannig, ef flutningafulltrúinn breytir einhverju í hlutanum sínum, mun restin af stöðinni haldast ósnortinn. Í þessu tilviki mun SpaceStation bekkurinn ekki einu sinni vita af breytingunum.
Yfirmenn okkar sem vinna við geimstöðina eru líklega ánægðir með breytingarnar því þeir geta óskað eftir þeim sem þeir þurfa. Taktu eftir að kóðinn hefur aðferðir eins og report_supplies og report_fuel sem eru í SupplyHold og FuelTank flokkunum. Hvað myndi gerast ef jörðin myndi biðja um að breyta því hvernig hún tilkynnir? Breyta þarf báðum flokkum, SupplyHold og FuelTank. Hvað ef þú þarft að breyta því hvernig eldsneyti og rekstrarvörur eru afhentar? Þú verður líklega að skipta um alla sömu flokka aftur. Og þetta er nú þegar brot á SRP meginreglunni. Við skulum laga þetta.
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 < Reporter
def initialize(item)
super(item, "supply")
end
def report
super
if @linked_item.get_supplies.keys.length > 0
@linked_item.get_supplies.each do |type, quantity|
puts "#{type} avalilable: #{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 avalilable: 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.
Í þessari nýjustu útgáfu af forritinu hefur ábyrgðinni verið skipt í tvo nýja flokka, FuelReporter og SupplyReporter. Þau eru bæði börn í fréttamannabekknum. Að auki bættum við tilviksbreytum við SpaceStation flokkinn svo hægt sé að frumstilla þann undirflokk sem óskað er eftir ef þörf krefur. Nú, ef jörðin ákveður að breyta einhverju öðru, þá munum við gera breytingar á undirflokkunum, en ekki á aðalflokknum.
Auðvitað eru sumir bekkirnir okkar enn háðir hver öðrum. Þannig fer SupplyReporter hluturinn eftir SupplyHold og FuelReporter fer eftir FuelTank. Að sjálfsögðu verða hvatarnir að vera tengdir við eldsneytistankinn. En hér lítur allt nú þegar rökrétt út og það verður ekki sérstaklega erfitt að gera breytingar - að breyta kóða eins hlutar mun ekki hafa mikil áhrif á annan.
Þannig höfum við búið til einingakóða þar sem ábyrgð hvers hlutar/flokka er nákvæmlega skilgreind. Það er ekki vandamál að vinna með slíkan kóða, það er einfalt verkefni að viðhalda honum. Við höfum breytt öllu „guðlega hlutnum“ í SRP.