Навиштани рамзи чандир бо истифода аз SOLID

Навиштани рамзи чандир бо истифода аз SOLID

Аз тарҷумон: барои шумо нашр шудааст мақолаи Северин Перес дар бораи истифодаи принципхои SOLID дар барномасозй. Маълумот аз мақола ҳам барои шурӯъкунандагон ва ҳам барои барномасозони ботаҷриба муфид хоҳад буд.

Агар шумо ба рушд машғул бошед, эҳтимол шумо дар бораи принсипҳои SOLID шунидаед. Онҳо ба барномасоз имкон медиҳанд, ки рамзи тоза, сохторбандишуда ва ба осонӣ нигоҳ дошташаванда нависад. Қобили зикр аст, ки дар барномасозӣ якчанд равишҳо барои дуруст иҷро кардани кори муайян мавҷуданд. Мутахассисони гуногун дар бораи «роҳи дуруст» ақида ва фаҳмиши гуногун доранд, ҳамааш ба таҷрибаи ҳар як шахс вобаста аст. Бо вуҷуди ин, ғояҳои дар SOLID эълоншуда аз ҷониби қариб ҳамаи намояндагони ҷомеаи IT қабул карда мешаванд. Онҳо нуқтаи ибтидоии пайдоиш ва рушди бисёр таҷрибаҳои хуби идоракунии рушд гардиданд.

Биёед бифаҳмем, ки принсипҳои SOLID чист ва онҳо ба мо чӣ гуна кӯмак мерасонанд.

Skillbox тавсия медиҳад: Курси амалӣ "Таҳиягари мобилӣ PRO".

Мо ба шумо хотиррасон мекунем: барои ҳамаи хонандагони "Habr" - тахфифи 10 000 рубл ҳангоми номнавис шудан ба курсҳои Skillbox бо истифода аз рамзи таблиғотии "Habr".

SOLID чист?

Ин истилоҳ ихтисор аст, ҳар ҳарфи истилоҳ ибтидои номи як принсипи мушаххас аст:

  • SПринсипи ягонаи масъулият. Модул метавонад як ва танҳо як сабаби тағирот дошта бошад.
  • Дар Oқалам / Принсипи пӯшида (принсипи кушода/пўшида). Синфҳо ва дигар унсурҳо бояд барои васеъкунӣ кушода бошанд, аммо барои тағир додан пӯшида бошанд.
  •  Дар LИсков Принсипи ивазкунӣ (Принсипи ивазкунии Лисков). Функсияҳое, ки навъи асосиро истифода мебаранд, бояд бидуни донистани он зернамудҳои навъи асосиро истифода баранд.
  • Дар IПринсипи сегрегатсияи интерфейс  (принсипи ҷудокунии интерфейс). Объектҳои нармафзор набояд аз усулҳое, ки онҳо истифода намебаранд, вобаста бошанд.
  • Дар DПринсипи инверсионии ependency (принсипи инверсияи вобастагӣ). Модулҳои сатҳҳои боло набояд аз модулҳои сатҳҳои поёнӣ вобаста бошанд.

Принсипи масъулиятшиносии ягона


Принсипи Ягона Масъулият (SRP) мегӯяд, ки ҳар як синф ё модули барнома бояд танҳо барои як қисми функсияи ин барнома масъул бошад. Илова бар ин, унсурҳои ин масъулият бояд ба синфи худ таъин карда шаванд, на дар синфҳои ба ҳам алоқаманд. Таҳиягар ва сарвари башоратгари SRP Роберт С. Мартин масъулиятро сабаби тағирот тавсиф мекунад. Ӯ дар ибтидо ин истилоҳро ҳамчун яке аз ҷузъҳои асари худ "Принсипҳои тарҳрезии ба объект нигаронидашуда" пешниҳод кардааст. Консепсия қисми зиёди намунаи пайвастшавиро дар бар мегирад, ки қаблан Том ДеМарко муайян карда буд.

Консепсия инчунин якчанд мафҳумҳоеро дар бар мегирад, ки аз ҷониби Дэвид Парнас таҳия шудааст. Ду чизи асосӣ инкапсуляция ва пинҳон кардани иттилоот мебошанд. Парнас изҳор дошт, ки тақсим кардани система ба модулҳои алоҳида набояд ба таҳлили диаграммаҳои блок ё ҷараёнҳои иҷро асос ёбад. Ҳар яке аз модулҳо бояд ҳалли мушаххасеро дар бар гиранд, ки ҳадди аққал маълумотро ба муштариён таъмин кунад.

Дар омади гап, Мартин бо менеҷерони аршади ширкат (COO, CTO, CFO), ки ҳар кадоми онҳо нармафзори мушаххаси тиҷорӣ барои мақсадҳои гуногун истифода мебаранд, як мисоли ҷолиб овард. Дар натиҷа, ҳар кадоми онҳо метавонанд бидуни таъсир ба манфиатҳои дигар менеҷерҳо дар нармафзор тағйирот ворид кунанд.

Объекти илоҳӣ

Чун ҳамеша, роҳи беҳтарини омӯхтани SRP ин дидани он дар амал аст. Биёед як фасли барномаро бубинем, ки Принсипи ягонаи масъулиятро риоя намекунад. Ин рамзи Ruby аст, ки рафтор ва атрибутҳои истгоҳи кайҳонро тавсиф мекунад.

Намунаро аз назар гузаронед ва кӯшиш кунед, ки чизҳои зеринро муайян кунед:
Масъулияти он объектҳое, ки дар синфи SpaceStation эълон шудаанд.
Онхое, ки ба кори станциям кайхонй марок зохир карда метавонанд.

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

Воқеан, истгоҳи кайҳонии мо корношоям аст (ман фикр намекунам, ки ба зудӣ ба ман аз NASA занг занам), аммо дар ин ҷо чизе барои таҳлил вуҷуд дорад.

Ҳамин тариқ, синфи SpaceStation дорои якчанд масъулиятҳо (ё вазифаҳо) мебошад. Ҳамаи онҳоро метавон ба намудҳо тақсим кард:

  • сенсорҳо;
  • лавозимот (маводи истеъмолӣ);
  • сӯзишворӣ;
  • суръатдиҳандагон.

Ба ягон коркуни станция синф дода нашуда бошад хам, мо ба осонй тасаввур карда метавонем, ки кй барои чй чавобгар аст. Эҳтимол аст, ки олим сенсорҳоро идора мекунад, логист барои таъмини захираҳо, муҳандис барои таъминоти сӯзишворӣ ва пилот боркунакҳоро идора мекунад.

Оё мо гуфта метавонем, ки ин барнома ба SRP мувофиқ нест? Ҳа, албатта. Аммо синфи SpaceStation як "объекти худо" аст, ки ҳама чизро медонад ва ҳама чизро мекунад. Ин як намунаи асосии зиддитеррористӣ дар барномасозии ба объект нигаронидашуда мебошад. Барои шурӯъкунандагон нигоҳ доштани чунин объектҳо хеле душвор аст. То ҳол барнома хеле содда аст, ҳа, аммо тасаввур кунед, ки агар мо хусусиятҳои навро илова кунем, чӣ мешавад. Шояд ба станцияи кайхонии мо пункти тиббй ё мачлисгох лозим бошад. Ва чӣ қадаре ки функсияҳо зиёд бошанд, ҳамон қадар SpaceStation афзоиш хоҳад ёфт. Хуб, азбаски ин иншоот ба дигарон пайваст мешавад, хизматрасонӣ ба тамоми комплекс боз ҳам мураккабтар мешавад. Дар натица мо метавонем кори, масалан, тез-тезонро вайрон кунем. Агар тадқиқотчӣ тағиротро дар сенсорҳо талаб кунад, ин метавонад ба системаҳои алоқаи станция хеле хуб таъсир расонад.

Вайрон кардани принципи СРП метавонад галабаи кутохмуддати тактикй ба даст оварад, вале дар нихояти кор мо «чангро аз даст медихем» ва дар оянда нигох доштани ин гуна монстр хеле душвор мегардад. Беҳтар аст, ки барномаро ба қисмҳои алоҳидаи код тақсим кунед, ки ҳар яки онҳо барои иҷрои амалиёти мушаххас масъуланд. Инро фаҳмида, биёед синфи SpaceStation-ро тағир диҳем.

Биёед масъулиятро тақсим кунем

Дар боло мо чор намуди амалиётро муайян кардем, ки аз ҷониби синфи SpaceStation идора карда мешаванд. Ҳангоми рефакторинг мо онҳоро дар хотир хоҳем дошт. Рамзи навшуда ба 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

Тағйироти зиёде мавҷуданд, барнома бешубҳа ҳоло беҳтар ба назар мерасад. Ҳоло синфи SpaceStation мо бештар ба як контейнер табдил ёфтааст, ки дар он амалиёт барои қисмҳои вобаста, аз ҷумла маҷмӯи сенсорҳо, системаи таъминоти истеъмолӣ, зарфи сӯзишворӣ ва тақвиятдиҳандаҳо оғоз мешавад.

Барои ҳар як тағирёбанда ҳоло синфи мувофиқ вуҷуд дорад: Сенсорҳо; SupplyHold; FuelTank; Мошинҳо.

Дар ин версияи код якчанд тағйироти муҳим вуҷуд дорад. Гап дар сари он аст, ки функсияҳои инфиродӣ на танҳо дар синфҳои худ фаро гирифта шудаанд, онҳо тавре ташкил карда шудаанд, ки пешгӯишаванда ва пайгирона шаванд. Мо унсурҳои дорои функсияҳои шабеҳро гурӯҳбандӣ мекунем, то принсипи ҳамоҳангиро риоя кунем. Ҳоло, агар ба мо лозим ояд, ки тарзи кори системаро тағир дода, аз сохтори хэш ба массив гузаред, танҳо синфи SupplyHold -ро истифода баред; мо набояд ба модулҳои дигар даст занем. Ҳамин тавр, агар корманди таъминоти моддӣ дар қисмати худ чизеро тағир диҳад, қисми боқимондаи станция бетағйир мемонад. Дар ин ҳолат, синфи SpaceStation ҳатто аз тағирот огоҳ нахоҳад шуд.

Афсарони мо, ки дар истгоҳи кайҳонӣ кор мекунанд, эҳтимол аз тағирот хушҳоланд, зеро онҳо метавонанд чизҳои заруриро дархост кунанд. Аҳамият диҳед, ки код дорои усулҳое ба монанди report_supplies ва report_fuel, ки дар синфҳои SupplyHold ва FuelTank мавҷуд аст. Агар Замин талаб кунад, ки тарзи гузориши худро тағир диҳад, чӣ мешавад? Ҳарду синф, SupplyHold ва FuelTank, бояд тағир дода шаванд. Чӣ бояд кард, агар ба шумо тарзи интиқоли сӯзишворӣ ва масолеҳи масрафиро тағир диҳед? Эҳтимол шумо бояд ҳамаи ҳамон синфҳоро дубора иваз кунед. Ва ин аллакай вайрон кардани принципи СРП мебошад. Биёед инро ислоҳ кунем.

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.

Дар ин версияи охирини барнома, масъулиятҳо ба ду синфи нав, FuelReporter ва SupplyReporter тақсим шудаанд. Ҳардуи онҳо фарзандони синфи «Репортер» мебошанд. Илова бар ин, мо ба синфи SpaceStation тағирёбандаҳои мисол илова кардем, то дар ҳолати зарурӣ зеркласси дилхоҳро оғоз кардан мумкин аст. Акнун, агар Замин тасмим гирад, ки чизи дигареро тағир диҳад, пас мо ба зерсинфҳо тағирот ворид хоҳем кард, на ба синфи асосӣ.

Албатта, баъзе синфхои мо то хол аз хамдигар вобастаанд. Ҳамин тариқ, объекти SupplyReporter аз SupplyHold ва FuelReporter аз FuelTank вобаста аст. Албатта, кувватдиҳандаҳо бояд ба зарфи сӯзишворӣ пайваст карда шаванд. Аммо дар ин ҷо ҳама чиз аллакай мантиқӣ ба назар мерасад ва ворид кардани тағирот махсусан душвор нахоҳад буд - таҳрир кардани рамзи як объект ба дигараш чандон таъсир намерасонад.

Ҳамин тариқ, мо коди модулиро эҷод кардем, ки дар он масъулиятҳои ҳар як объект/синфҳо дақиқ муайян карда шудаанд. Кор бо чунин код мушкил нест, нигоҳ доштани он кори оддӣ хоҳад буд. Мо тамоми «объекти илоҳӣ»-ро ба SRP табдил додем.

Skillbox тавсия медиҳад:

Манбаъ: will.com

Илова Эзоҳ