Skriuwen fleksibele koade mei help fan SOLID

Skriuwen fleksibele koade mei help fan SOLID

Fan de oersetter: publisearre foar dy artikel troch Severin Perez oer it brûken fan SOLID prinsipes yn programmearring. De ynformaasje út it artikel sil nuttich wêze foar sawol begjinners as erfarne programmeurs.

As jo ​​yn ûntwikkeling binne, hawwe jo wierskynlik heard fan 'e SOLID-prinsipes. Se meitsje it mooglik foar de programmeur om skjinne, goed strukturearre en maklik te ûnderhâlden koade te skriuwen. It is de muoite wurdich op te merken dat yn programmearring binne d'r ferskate oanpak foar hoe't jo in bepaalde taak korrekt útfiere. Ferskillende spesjalisten hawwe ferskillende ideeën en begryp fan it "rjochte paad"; it hinget allegear ôf fan 'e ûnderfining fan elke persoan. De ideeën útroppen yn SOLID wurde lykwols akseptearre troch hast alle fertsjintwurdigers fan 'e IT-mienskip. Se waarden it útgongspunt foar it ûntstean en ûntwikkeling fan in protte goede praktyk foar ûntwikkelingsbehear.

Litte wy begripe wat de SOLID-prinsipes binne en hoe't se ús helpe.

Skillbox advisearret: Praktyske kursus "Mobiele ûntwikkelder PRO".

Wy herinnerje: foar alle lêzers fan "Habr" - in koarting fan 10 roebel by it ynskriuwen fan in Skillbox-kursus mei de promoasjekoade "Habr".

Wat is SOLID?

Dizze term is in ôfkoarting, elke letter fan 'e term is it begjin fan' e namme fan in spesifyk prinsipe:

  • Single Responsibility Principle. In module kin ien en mar ien reden hawwe foar feroaring.
  • De Opinne / sletten Prinsipe (iepen/sletten prinsipe). Klassen en oare eleminten moatte iepen wêze foar útwreiding, mar sletten foar wiziging.
  •  De Liskov Substitution Principle (Liskov ferfanging prinsipe). Funksjes dy't in basistype brûke, moatte subtypen fan it basistype brûke kinne sûnder it te witten.
  • De IInterface Segregation Principle  (ynterface skieding prinsipe). Software-entiteiten moatte net ôfhinklik wêze fan metoaden dy't se net brûke.
  • De Dependency Inversion Principle (prinsipe fan ôfhinklikens omkearing). Modules op hegere nivo's moatte net ôfhinklik wêze fan modules op legere nivo's.

Ienfâldich ferantwurdlikensprinsipe


It Single Responsibility Principle (SRP) stelt dat elke klasse of module yn in programma allinich ferantwurdlik wêze moat foar ien diel fan 'e funksjonaliteit fan dat programma. Derneist moatte eleminten fan dizze ferantwurdlikens wurde tawiisd oan har eigen klasse, ynstee fan ferspraat oer net-relatearre klassen. De ûntwikkelder en haadevangelist fan SRP, Robert S. Martin, beskriuwt ferantwurding as de reden foar feroaring. Hy foarstelde dizze term oarspronklik as ien fan 'e eleminten fan syn wurk "Principles of Object-Oriented Design". It konsept omfettet in protte fan it ferbiningspatroan dat earder waard definieare troch Tom DeMarco.

It konsept omfette ek ferskate konsepten formulearre troch David Parnas. De twa wichtichste binne ynkapseling en ynformaasjeferbergjen. Parnas bewearde dat it dielen fan in systeem yn aparte modules net moat wurde basearre op analyze fan blokdiagrammen of útfieringsstreamen. Elk fan 'e modules moat in spesifike oplossing befetsje dy't in minimum oan ynformaasje leveret oan kliïnten.

Troch de wei, Martin joech in nijsgjirrich foarbyld mei senior managers fan in bedriuw (COO, CTO, CFO), elk fan wa brûkt spesifike saaklike software foar ferskate doelen. As resultaat kin elk fan har wizigingen yn 'e software ymplementearje sûnder de belangen fan oare managers te beynfloedzjen.

Godlik objekt

Lykas altyd is de bêste manier om SRP te learen om it yn aksje te sjen. Litte wy nei in seksje fan it programma sjen dat NET it ienige ferantwurdlikensprinsipe folget. Dit is Ruby-koade dy't it gedrach en attributen fan it romtestasjon beskriuwt.

Besjoch it foarbyld en besykje it folgjende te bepalen:
Ferantwurdlikheden fan dy objekten dy't wurde ferklearre yn de SpaceStation klasse.
Dejingen dy't miskien wêze ynteressearre yn de eksploitaasje fan it romtestasjon.

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

Eins is ús romtestasjon dysfunksjoneel (ik tink net dat ik gau in oprop fan NASA sil krije), mar d'r is hjir wat te analysearjen.

Sa hat de SpaceStation-klasse ferskate ferskillende ferantwurdlikheden (as taken). Allegear kinne wurde ferdield yn soarten:

  • sensors;
  • foarrieden (verbruiksartikelen);
  • brânstof;
  • accelerators.

Ek al krijt gjin fan de meiwurkers fan it stasjon in klasse tawiisd, wy kinne ús maklik yntinke wa't ferantwurdlik is foar wat. Meast wierskynlik, de wittenskipper kontrolearret de sensoren, de logistyk is ferantwurdlik foar it leverjen fan middels, de yngenieur is ferantwurdlik foar brânstof foarrieden, en de piloat kontrolearret de boosters.

Kinne wy ​​sizze dat dit programma net SRP-kompatibel is? Ja, wiswier. Mar de SpaceStation-klasse is in typysk "godobjekt" dat alles wit en alles docht. Dit is in wichtich anty-patroan yn objekt-rjochte programmearring. Foar in begjinner binne sokke objekten ekstreem lestich te ûnderhâlden. Oant no ta is it programma heul ienfâldich, ja, mar stel jo foar wat der barre sil as wy nije funksjes tafoegje. Miskien hat ús romtestasjon in medysk stasjon of in gearkomsteromte nedich. En hoe mear funksjes d'r binne, hoe mear SpaceStation sil groeie. No, om't dizze foarsjenning ferbûn wurdt mei oaren, sil it betsjinjen fan it heule kompleks noch komplekser wurde. Dêrtroch kinne wy ​​de wurking fan bygelyks accelerators fersteure. As in ûndersiker feroarings oan de sensoren freget, kin dat hiel goed ynfloed hawwe op de kommunikaasjesystemen fan it stasjon.

It skeinen fan it SRP-prinsipe kin in koarte termyn taktyske oerwinning jaan, mar op it lêst sille wy "de oarloch ferlieze", en it sil heul lestich wurde om sa'n meunster yn 'e takomst te behâlden. It is it bêste om it programma te ferdielen yn aparte seksjes fan koade, elk fan dat is ferantwurdlik foar it útfieren fan in spesifike operaasje. As wy dit begripe, litte wy de SpaceStation-klasse feroarje.

Lit ús ferantwurdlikens fersprieden

Hjirboppe definieare wy fjouwer soarten operaasjes dy't wurde regele troch de SpaceStation-klasse. Wy sille se yn gedachten hâlde by refactoring. De bywurke koade komt better oerien mei de 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

Der binne in soad feroaringen, it programma sjocht der no grif better út. No is ús SpaceStation-klasse mear fan in kontener wurden wêryn operaasjes wurde inisjearre foar ôfhinklike dielen, ynklusyf in set fan sensoren, in konsumpsjesysteem, in brânstoftank en boosters.

Foar ien fan 'e fariabelen is der no in oerienkommende klasse: Sensors; SupplyHold; Brânstoftank; Thrusters.

D'r binne ferskate wichtige feroarings yn dizze ferzje fan 'e koade. It punt is dat yndividuele funksjes net allinich yn har eigen klassen ynkapsulearre binne, se binne sa organisearre dat se foarsisber en konsistint wurde. Wy groepearje eleminten mei ferlykbere funksjonaliteit om it prinsipe fan gearhing te folgjen. No, as wy moatte feroarje de manier wêrop it systeem wurket, ferpleatse fan in hash-struktuer nei in array, brûk gewoan de SupplyHold-klasse; wy hoege oare modules net oan te raken. Op dizze manier, as de logistyk offisier wat feroaret yn syn seksje, bliuwt de rest fan it stasjon yntakt. Yn dit gefal sil de SpaceStation-klasse net iens bewust wêze fan 'e feroarings.

Us offisieren dy't wurkje oan it romtestasjon binne wierskynlik bliid oer de feroaringen, om't se dejinge kinne oanfreegje dy't se nedich binne. Merken dat de koade hat metoaden lykas report_supplies en report_fuel befette yn de SupplyHold en FuelTank klassen. Wat soe barre as de ierde frege om de manier wêrop it rapporteart te feroarjen? Beide klassen, SupplyHold en FuelTank, sille moatte wurde feroare. Wat as jo moatte feroarje de manier wêrop brânstof en verbruiksartikelen wurde levere? Jo sille wierskynlik alle deselde klassen wer moatte feroarje. En dit is al in ynbreuk op it SRP-prinsipe. Litte wy dit reparearje.

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.

Yn dizze lêste ferzje fan it programma binne de ferantwurdlikheden ferdield yn twa nije klassen, FuelReporter en SupplyReporter. It binne beide bern fan de Ferslachjouwerklasse. Derneist hawwe wy eksimplaarfariabelen tafoege oan 'e SpaceStation-klasse, sadat de winske subklasse as it nedich is inisjalisearre wurde kin. No, as de ierde beslút wat oars te feroarjen, dan sille wy feroaringen meitsje oan 'e subklassen, en net oan' e haadklasse.

Fansels binne guon fan ús klassen noch ôfhinklik fan elkoar. Sa, de SupplyReporter foarwerp hinget ôf fan SupplyHold, en FuelReporter hinget ôf fan FuelTank. Fansels moatte de boosters ferbûn wêze mei de brânstoftank. Mar hjir sjocht alles al logysk, en it meitsjen fan feroarings sil net bysûnder dreech wêze - it bewurkjen fan de koade fan ien foarwerp sil net folle ynfloed hawwe op in oar.

Sa hawwe wy in modulêre koade makke wêr't de ferantwurdlikheden fan elk fan 'e objekten / klassen krekt definieare binne. Wurkje mei sa'n koade is gjin probleem, it behâlden sil in ienfâldige taak wêze. Wy hawwe it hiele "godlike objekt" omboud yn SRP.

Skillbox advisearret:

Boarne: www.habr.com

Add a comment