Biz SOLID istifadə edərək çevik kod yazırıq

Biz SOLID istifadə edərək çevik kod yazırıq

Tərcüməçidən: sizin üçün dərc edilmişdir Severin Perezin məqaləsi proqramlaşdırmada SOLID prinsiplərindən istifadə haqqında. Məqalədəki məlumatlar həm yeni başlayanlar, həm də təcrübəli proqramçılar üçün faydalı olacaqdır.

Əgər inkişafdasınızsa, çox güman ki, SOLID prinsipləri haqqında eşitmisiniz. Onlar proqramçıya təmiz, yaxşı strukturlaşdırılmış və asanlıqla saxlanıla bilən kod yazmağa imkan verir. Qeyd etmək lazımdır ki, proqramlaşdırmada müəyyən bir işi necə düzgün yerinə yetirmək üçün bir neçə yanaşma var. Müxtəlif mütəxəssislərin "doğru yol" haqqında fərqli fikirləri və anlayışları var, hamısı hər bir insanın təcrübəsindən asılıdır. Bununla belə, SOLID-də elan edilən ideyalar İT ictimaiyyətinin demək olar ki, bütün nümayəndələri tərəfindən qəbul edilir. Onlar bir çox yaxşı inkişaf idarəetmə təcrübələrinin yaranması və inkişafı üçün başlanğıc nöqtəsi oldu.

Gəlin SOLID prinsiplərinin nə olduğunu və bizə necə kömək etdiyini anlayaq.

Skillbox tövsiyə edir: Praktik kurs "Mobil Developer PRO".

Xatırladırıq: "Habr" ın bütün oxucuları üçün - "Habr" promosyon kodundan istifadə edərək hər hansı bir Skillbox kursuna yazılarkən 10 000 rubl endirim.

SOLID nədir?

Bu termin bir abreviaturadır, terminin hər hərfi müəyyən bir prinsipin adının başlanğıcıdır:

  • Svahid Məsuliyyət Prinsipi. Modulda dəyişiklik üçün bir və yalnız bir səbəb ola bilər.
  • The Oqələm/Qapalı Prinsip (açıq/qapalı prinsip). Siniflər və digər elementlər uzadılmaq üçün açıq, lakin dəyişiklik üçün bağlanmalıdır.
  •  The Liskov Əvəzetmə prinsipi (Liskov əvəzetmə prinsipi). Baza tipindən istifadə edən funksiyalar onu bilmədən əsas tipin alt tiplərindən istifadə edə bilməlidir.
  • The Iİnterfeys Seqreqasiya Prinsipi  (interfeys ayırma prinsipi). Proqram təminatı obyektləri istifadə etmədikləri metodlardan asılı olmamalıdır.
  • The Dependency Inversion Prinsip (asılılığın inversiya prinsipi). Daha yüksək səviyyələrdəki modullar aşağı səviyyələrdəki modullardan asılı olmamalıdır.

Vahid Məsuliyyət Prinsipi


Vahid Məsuliyyət Prinsipi (SRP) bildirir ki, proqramdakı hər bir sinif və ya modul həmin proqramın funksionallığının yalnız bir hissəsinə cavabdeh olmalıdır. Bundan əlavə, bu məsuliyyətin elementləri əlaqəli olmayan siniflər arasında səpələnməkdənsə, öz siniflərinə təyin edilməlidir. SRP-nin tərtibçisi və baş müjdəçisi Robert S. Martin hesabatlılığı dəyişikliyin səbəbi kimi təsvir edir. O, əvvəlcə bu termini “Obyekt yönümlü dizaynın prinsipləri” əsərinin elementlərindən biri kimi təklif etmişdir. Konsepsiya əvvəllər Tom DeMarco tərəfindən müəyyən edilmiş əlaqə modelinin çox hissəsini özündə birləşdirir.

Konsepsiya həmçinin David Parnas tərəfindən tərtib edilmiş bir neçə konsepsiyanı da əhatə edirdi. Əsas ikisi inkapsulyasiya və məlumatların gizlədilməsidir. Parnas iddia edirdi ki, sistemin ayrı-ayrı modullara bölünməsi blok diaqramların və ya icra axınlarının təhlilinə əsaslanmamalıdır. Modulların hər hansı birində müştərilərə minimum məlumat verən xüsusi bir həll olmalıdır.

Yeri gəlmişkən, Martin şirkətin yüksək səviyyəli menecerləri (COO, CTO, CFO) ilə maraqlı bir misal çəkdi, onların hər biri müxtəlif məqsədlər üçün xüsusi biznes proqramlarından istifadə edir. Nəticədə, onlardan hər hansı biri digər menecerlərin maraqlarına toxunmadan proqram təminatında dəyişikliklər həyata keçirə bilər.

İlahi obyekt

Həmişə olduğu kimi, SRP-ni öyrənməyin ən yaxşı yolu onu hərəkətdə görməkdir. Proqramın Vahid Məsuliyyət Prinsipinə əməl etməyən bölməsinə baxaq. Bu, kosmik stansiyanın davranışını və atributlarını təsvir edən Ruby kodudur.

Nümunəni nəzərdən keçirin və aşağıdakıları müəyyən etməyə çalışın:
SpaceStation sinfində elan edilən obyektlərin məsuliyyətləri.
Kosmik stansiyanın fəaliyyəti ilə maraqlananlar.

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

Əslində, kosmik stansiyamız qeyri-funksionaldır (mən düşünmürəm ki, tezliklə NASA-dan mənə zəng gələcək), amma burada təhlil ediləcək bir şey var.

Beləliklə, SpaceStation sinfinin bir neçə fərqli məsuliyyəti (və ya vəzifəsi) var. Onların hamısını növlərə bölmək olar:

  • sensorlar;
  • təchizat (istehlak materialları);
  • yanacaq;
  • sürətləndiricilər.

Stansiya işçilərinin heç birinə sinif təyin edilməsə də, kimin nəyə görə məsuliyyət daşıdığını asanlıqla təsəvvür edə bilərik. Çox güman ki, alim sensorları idarə edir, logistik resursların tədarükünə cavabdehdir, mühəndis yanacaq tədarükünə, pilot isə gücləndiricilərə nəzarət edir.

Bu proqramın SRP-yə uyğun olmadığını deyə bilərikmi? Bəli əminəm. Lakin SpaceStation sinfi hər şeyi bilən və hər şeyi edən tipik “tanrı obyektidir”. Bu, obyekt yönümlü proqramlaşdırmada əsas anti-naxışdır. Bir başlanğıc üçün belə obyektləri saxlamaq olduqca çətindir. Hələlik proqram çox sadədir, bəli, amma yeni funksiyalar əlavə etsək nə olacağını təsəvvür edin. Ola bilsin ki, kosmik stansiyamıza tibb məntəqəsi və ya iclas otağı lazım olacaq. Və nə qədər çox funksiya varsa, SpaceStation da bir o qədər böyüyəcək. Yaxşı, bu obyekt başqalarına qoşulacağından bütün kompleksə xidmət göstərmək daha da çətinləşəcək. Nəticədə, məsələn, sürətləndiricilərin işini poza bilərik. Tədqiqatçı sensorlarda dəyişiklik tələb edərsə, bu, stansiyanın rabitə sistemlərinə çox yaxşı təsir göstərə bilər.

SRP prinsipini pozmaq qısamüddətli taktiki qələbə verə bilər, amma sonda "müharibəni uduzacağıq" və gələcəkdə belə bir canavar saxlamaq çox çətin olacaq. Proqramı hər biri müəyyən bir əməliyyatı yerinə yetirmək üçün cavabdeh olan ayrı-ayrı kod bölmələrinə bölmək yaxşıdır. Bunu başa düşərək, SpaceStation sinifini dəyişdirək.

Gəlin məsuliyyəti bölüşdürək

Yuxarıda biz SpaceStation sinfi tərəfindən idarə olunan dörd növ əməliyyat müəyyən etdik. Refaktorinq zamanı bunları nəzərə alacağıq. Yenilənmiş kod SRP ilə daha yaxşı uyğunlaşır.

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

Bir çox dəyişiklik var, proqram mütləq indi daha yaxşı görünür. İndi bizim SpaceStation sinifimiz daha çox sensorlar dəsti, istehlak materialı təchizatı sistemi, yanacaq çəni və gücləndiricilər daxil olmaqla, asılı hissələr üçün əməliyyatların başlandığı konteynerə çevrilib.

Dəyişənlərin hər hansı biri üçün indi müvafiq sinif var: Sensorlar; Haqqımızda Şirkətin Adı: SupplyHold; FuelTank; İticilər.

Kodun bu versiyasında bir sıra mühüm dəyişikliklər var. Məsələ burasındadır ki, fərdi funksiyalar təkcə öz siniflərində əhatə olunmur, onlar proqnozlaşdırıla bilən və ardıcıl olacaq şəkildə təşkil edilir. Uyğunluq prinsipinə əməl etmək üçün oxşar funksionallığı olan elementləri qruplaşdırırıq. İndi, sistemin işləmə tərzini dəyişmək, hash strukturundan massivə keçmək lazımdırsa, sadəcə SupplyHold sinifindən istifadə edin; digər modullara toxunmaq lazım deyil. Bu yolla, logistika məmuru öz bölməsində nəyisə dəyişdirsə, stansiyanın qalan hissəsi toxunulmaz qalacaq. Bu halda, SpaceStation sinfi dəyişikliklərdən belə xəbərdar olmayacaq.

Kosmik stansiyada çalışan zabitlərimiz yəqin ki, dəyişikliklərdən məmnundurlar, çünki onlar ehtiyac duyduqları dəyişiklikləri tələb edə bilərlər. Qeyd edək ki, kodun SupplyHold və FuelTank siniflərində olan report_supplies və report_fuel kimi üsulları var. Yer hesabat tərzini dəyişməyi istəsə nə baş verərdi? Hər iki sinif, SupplyHold və FuelTank dəyişdirilməlidir. Əgər yanacaq və istehlak materiallarının çatdırılma üsulunu dəyişdirmək lazımdırsa? Yəqin ki, bütün eyni sinifləri yenidən dəyişməli olacaqsınız. Bu isə artıq SRP prinsipinin pozulmasıdır. Gəlin bunu düzəldək.

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.

Proqramın bu son versiyasında vəzifələr FuelReporter və SupplyReporter olmaqla iki yeni sinifə bölünüb. Onların hər ikisi “Reportyor” sinfinin uşaqlarıdır. Bundan əlavə, SpaceStation sinfinə misal dəyişənləri əlavə etdik ki, lazım olduqda istənilən alt sinif işə salınsın. İndi Yer başqa bir şey dəyişdirmək qərarına gəlsə, o zaman əsas sinfə deyil, alt siniflərə dəyişiklik edəcəyik.

Təbii ki, bəzi dərslərimiz hələ də bir-birindən asılıdır. Beləliklə, SupplyReporter obyekti SupplyHold-dan, FuelReporter isə FuelTank-dan asılıdır. Təbii ki, gücləndiricilər yanacaq çəninə qoşulmalıdır. Ancaq burada hər şey artıq məntiqli görünür və dəyişikliklər etmək xüsusilə çətin olmayacaq - bir obyektin kodunu redaktə etmək digərinə çox təsir etməyəcək.

Beləliklə, biz hər bir obyektin/siniflərin məsuliyyətlərinin dəqiq müəyyən edildiyi modul kodu yaratdıq. Belə kodla işləmək problem deyil, onu saxlamaq sadə iş olacaq. Biz bütün “ilahi obyekti” SRP-yə çevirdik.

Skillbox tövsiyə edir:

Mənbə: www.habr.com

Добавить комментарий