Schreiwen flexibel Code benotzt SOLID

Schreiwen flexibel Code benotzt SOLID

Vum Iwwersetzer: fir Iech publizéiert Artikel vum Severin Perez iwwer d'Benotzung vun SOLID Prinzipien am Programméiere. D'Informatioun vum Artikel wäert nëtzlech sinn fir Ufänger an erfuerene Programméierer.

Wann Dir an Entwécklung sidd, hutt Dir héchstwahrscheinlech vun de SOLID Prinzipien héieren. Si erméiglechen dem Programméierer propper, gutt strukturéiert an einfach erhale Code ze schreiwen. Et ass derwäert opgeschriwwen, datt am programméiere ginn et verschidden Approche wéi eng bestëmmte Aarbecht richteg ze Leeschtunge. Verschidde Spezialisten hunn verschidden Iddien a Verständnis vum "richtege Wee"; et hänkt alles vun der Erfahrung vun all Persoun of. Wéi och ëmmer, d'Iddien, déi am SOLID proklaméiert ginn, gi vu bal all Vertrieder vun der IT Gemeinschaft ugeholl. Si goufen den Ausgangspunkt fir d'Entstoe an d'Entwécklung vu ville gudde Entwécklungsmanagementpraktiken.

Loosst eis verstoen wat d'SOLID Prinzipien sinn a wéi se eis hëllefen.

Skillbox recommandéiert: Praktesch Cours "Mobilentwéckler PRO".

Mir erënneren Iech: fir all Habr Lieser - eng Remise vun 10 Rubel wann Dir Iech an all Skillbox Cours aschreift mat dem Habr Promo Code.

Wat ass SOLID?

Dëse Begrëff ass eng Ofkierzung, all Bréif vum Begrëff ass den Ufank vum Numm vun engem spezifesche Prinzip:

  • SIngle Verantwortungsprinzip. E Modul kann een an nëmmen ee Grond fir Ännerung hunn.
  • d' Open / zouene Prinzip (Open / zouene Prinzip). Klassen an aner Elementer solle fir Verlängerung op sinn, awer zou fir Ännerung.
  •  d' Liskov Substitution Prinzip (Liskov Substitution Prinzip). Fonctiounen, déi e Basistyp benotzen, solle fäeg sinn Ënnertypen vum Basistyp ze benotzen ouni et ze wëssen.
  • d' IInterface Segregatioun Prinzip  (Interface Trennung Prinzip). Software Entitéite sollen net op Methoden ofhänken, déi se net benotzen.
  • d' Dependency Inversion Prinzip (Prinzip vun Ofhängegkeet Inversion). Moduler op méi héijen Niveauen sollten net vu Moduler op méi nidderegen Niveauen ofhänken.

Eenzeg Verantwortung Prinzip


Den Single Responsibility Principle (SRP) seet datt all Klass oder Modul an engem Programm verantwortlech ass fir nëmmen een Deel vun der Funktionalitéit vum Programm. Zousätzlech, Elementer vun dëser Verantwortung sollen hir eege Klass zougewisen ginn, anstatt iwwer net verwandte Klassen verspreet. SRP Entwéckler a Chef Evangelist, Robert S. Martin, beschreift Rechenschaftspflicht als Grond fir Ännerung. Hien huet ursprénglech dëse Begrëff als ee vun den Elementer vu sengem Wierk "Principles of Object-Oriented Design" proposéiert. D'Konzept integréiert vill vum Konnektivitéitsmuster dat virdru vum Tom DeMarco definéiert gouf.

D'Konzept ëmfaasst och verschidde Konzepter formuléiert vum David Parnas. Déi zwee Haaptgrënn sinn d'Inkapselung an d'Informatiounsverstoppen. Parnas argumentéiert datt d'Divisioun vun engem System an getrennten Moduler net op Analyse vu Blockdiagrammer oder Ausféierungsfloss baséiert. Jidderee vun de Moduler muss eng spezifesch Léisung enthalen déi e Minimum vun Informatioun u Clienten ubitt.

Iwwregens, huet de Martin en interessant Beispill mat Senior Manager vun enger Firma (COO, CTO, CFO), jidderee vun deenen spezifesch Geschäftssoftware fir verschidden Zwecker benotzt. Als Resultat kann jidderee vun hinnen Ännerungen an der Software ëmsetzen ouni d'Interesse vun anere Manager ze beaflossen.

Göttlech Objet

Wéi ëmmer ass de beschte Wee fir SRP ze léieren ass et an Aktioun ze gesinn. Loosst eis eng Sektioun vum Programm kucken, deen NET dem eenzege Verantwortungsprinzip follegt. Dëst ass Ruby Code deen d'Behuelen an d'Attributer vun der Raumstatioun beschreift.

Iwwerpréift d'Beispill a probéiert déi folgend ze bestëmmen:
Verantwortung vun dësen Objeten déi an der SpaceStation Klass deklaréiert sinn.
Déi, déi fir de Fonctionnement vun der Raumstatioun interesséiert sinn.

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

Eigentlech ass eis Raumstatioun dysfunktionell (ech mengen net datt ech geschwënn en Uruff vun der NASA kréien), awer et gëtt eppes hei ze analyséieren.

Also huet d'SpaceStation Klass verschidde verschidde Verantwortung (oder Aufgaben). All vun hinnen kann an Zorte ënnerdeelt ginn:

  • Sensoren;
  • Ëmgeréits (Verbrauchsmaterial);
  • Brennstoff;
  • Beschleuniger.

Och wa kee vun de Mataarbechter vun der Gare eng Klass zougewisen ass, kënne mir eis ganz einfach virstellen, wien fir wat verantwortlech ass. Wahrscheinlech kontrolléiert de Wëssenschaftler d'Sensoren, de Logistiker ass verantwortlech fir d'Ressourcen ze liwweren, den Ingenieur ass verantwortlech fir d'Brennstoffversuergung, an de Pilot kontrolléiert d'Boosteren.

Kënne mir soen datt dëse Programm net SRP-kompatibel ass? Jo, sécher. Awer d'SpaceStation Klass ass en typesche "Gott Objet" deen alles weess an alles mécht. Dëst ass e wichtegt Anti-Muster an objektorientéierter Programméierung. Fir en Ufänger sinn esou Objeten extrem schwéier ze pflegen. Bis elo ass de Programm ganz einfach, jo, awer stellt Iech vir wat geschitt wa mir nei Features addéieren. Vläicht brauch eis Raumstatioun eng medizinesch Gare oder e Versammlungsraum. A wat méi Funktiounen et ginn, wat méi SpaceStation wäert wuessen. Gutt, well dës Ariichtung mat aneren verbonne gëtt, gëtt de Service vum ganze Komplex nach méi schwéier. Als Resultat kënne mir d'Operatioun vun zum Beispill Beschleuniger stéieren. Wann e Fuerscher Ännerunge fir d'Sensoren freet, kann dat ganz gutt d'Kommunikatiounssystemer vun der Gare beaflossen.

D'Verletzung vum SRP-Prinzip kann eng kuerzfristeg taktesch Victoire ginn, awer um Enn wäerte mir "de Krich verléieren", an et wäert ganz schwéier ginn esou e Monster an Zukunft z'erhalen. Et ass am beschten de Programm an eenzel Sektiounen vum Code opzedeelen, jidderee vun deem verantwortlech ass fir eng spezifesch Operatioun ze maachen. Verstinn dëst, loosst eis d'SpaceStation Klass änneren.

Loosst d'Verantwortung verdeelen

Uewen hu mir véier Aarte vu Operatiounen definéiert, déi vun der SpaceStation Klass kontrolléiert ginn. Mir wäerten se am Kapp behalen wann se refactoréieren. Den aktualiséierten Code passt besser mam 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

Et gi vill Ännerungen, de Programm gesäit elo definitiv besser aus. Elo ass eis SpaceStation Klass méi vun engem Container ginn an deem Operatiounen fir ofhängeg Deeler initiéiert ginn, dorënner eng Rei vu Sensoren, e verbrauchbare Versuergungssystem, e Brennstofftank, a Boosteren.

Fir eng vun de Variabelen gëtt et elo eng entspriechend Klass: Sensoren; VersuergungHold; Brennstoff Tank; Thrusters.

Et gi verschidde wichteg Ännerungen an dëser Versioun vum Code. De Punkt ass, datt eenzel Funktiounen net nëmmen an hiren eegene Klassen akapsuléiert sinn, si sinn esou organiséiert datt se prévisibel a konsequent ginn. Mir gruppéiere Elementer mat ähnlecher Funktionalitéit fir de Prinzip vun der Kohärenz ze verfollegen. Elo, wa mir d'Art a Weis wéi de System funktionnéiert musse änneren, vun enger Hashstruktur an eng Array plënneren, benotzt just d'SupplyHold Klass; mir mussen net aner Moduler beréieren. Op dës Manéier, wann de Logistikbeamten eppes a senger Sektioun ännert, bleift de Rescht vun der Gare intakt. An dësem Fall wäert d'SpaceStation Klass net emol vun den Ännerungen bewosst sinn.

Eis Offizéier, déi op der Raumstatioun schaffen, si wahrscheinlech frou iwwer d'Ännerungen, well se kënnen déi ufroen, déi se brauchen. Notéiert datt de Code Methoden huet wéi report_supplies a report_fuel an de SupplyHold an FuelTank Klassen enthalen. Wat géif geschéien wann d'Äerd gefrot huet de Wee ze änneren wéi se bericht? Béid Klassen, SupplyHold a FuelTank, musse geännert ginn. Wat wann Dir de Wee wéi Brennstoff a Verbrauchsmaterial geliwwert muss änneren? Dir wäert wahrscheinlech all déi selwecht Klassen erëm änneren. An dat ass schonn eng Violatioun vum SRP Prinzip. Loosst eis dat fixéieren.

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.

An dëser leschter Versioun vum Programm sinn d'Verantwortung an zwou nei Klassen opgedeelt, FuelReporter an SupplyReporter. Si sinn allebéid Kanner vun der Reporter-Klass. Zousätzlech hu mir Instanzvariablen an d'SpaceStation-Klass bäigefüügt fir datt déi gewënscht Ënnerklass kann initialiséiert ginn wann néideg. Elo, wann d'Äerd decidéiert eppes anescht z'änneren, da maache mir Ännerunge fir d'Ënnerklassen, an net an d'Haaptklass.

Natierlech hänken e puer vun eise Klassen nach ëmmer vuneneen of. Also hänkt de SupplyReporter Objet vum SupplyHold of, a FuelReporter hänkt vum FuelTank of. Natierlech mussen d'Boosteren un de Brennstofftank verbonne sinn. Awer hei gesäit alles scho logesch aus, an d'Ännerunge maachen wäert net besonnesch schwéier sinn - d'Ännerung vum Code vun engem Objet wäert en aneren net vill beaflossen.

Also hu mir e modulare Code erstallt wou d'Verantwortung vun all Objeten / Klassen präzis definéiert ass. Mat esou Code ze schaffen ass kee Problem, et ass eng einfach Aufgab ze halen. Mir hunn de ganze "göttlechen Objet" an SRP ëmgewandelt.

Skillbox recommandéiert:

Source: will.com

Setzt e Commentaire