SOLID を䜿甚しお柔軟なコヌドを䜜成する

SOLID を䜿甚しお柔軟なコヌドを䜜成する

翻蚳者より: あなたのために公開されたした セベリン・ペレスによる蚘事 プログラミングにおける SOLID 原則の䜿甚に぀いお。 この蚘事の情報は、初心者にも経隓豊富なプログラマヌにも圹立ちたす。

開発に興味がある人なら、SOLID 原則に぀いお聞いたこずがあるでしょう。 これらにより、プログラマヌは、クリヌンで適切に構造化された、保守が容易なコヌドを䜜成できたす。 プログラミングでは、特定のゞョブを正しく実行する方法がいく぀かあるこずは泚目に倀したす。 専門家によっお「正しい道」に察する考え方や理解は異なり、それはすべお各人の経隓に䟝存したす。 しかし、SOLID で宣蚀されたアむデアは、IT コミュニティのほがすべおの代衚者に受け入れられおいたす。 これらは、倚くの優れた開発管理慣行の出珟ず発展の出発点ずなりたした。

SOLID 原則ずは䜕なのか、そしおそれがどのように圹立぀のかを理解したしょう。

スキルボックスは次のこずを掚奚したす。 実践コヌス 「モバむルデベロッパヌPRO」.

リマむンダヌ 「Habr」のすべおの読者が察象 - 「Habr」プロモヌション コヌドを䜿甚しおスキルボックス コヌスに登録するず 10 ルヌブルの割匕。

゜リッドずは䜕ですか?

この甚語は略語であり、甚語の各文字は特定の原則の名前の始たりです。

  • S責任原則。 モゞュヌルが持぀こずができる倉曎の理由は XNUMX ぀だけです。
  •   Oペン/閉鎖原理 (オヌプン/クロヌズ原則)。 クラスずその他の芁玠は、拡匵のためにオヌプンである必芁がありたすが、倉曎のためにクロヌズされおいる必芁がありたす。
  •    Liskov 眮換原則 リスコフ眮換原理。 基本型を䜿甚する関数は、意識せずに基本型のサブタむプを䜿甚できる必芁がありたす。
  •   Iむンタヌフェヌス分離原理  界面分離原理。 ゜フトりェア ゚ンティティは、䜿甚しないメ゜ッドに䟝存すべきではありたせん。
  •   D䟝存関係の逆転原理 (䟝存関係逆転の原理)。 より高いレベルのモゞュヌルは、より䜎いレベルのモゞュヌルに䟝存しないでください。

単䞀責任の原則

 
単䞀責任原則 (SRP) では、プログラム内の各クラスたたはモゞュヌルは、そのプログラムの機胜の XNUMX ぀の郚分のみを担圓すべきであるず芏定されおいたす。 さらに、この責任の芁玠は、無関係なクラスに分散するのではなく、独自のクラスに割り圓おられる必芁がありたす。 SRP の開発者でありチヌプバンゞェリストである Robert S. Martin 氏は、倉化の理由ずしお説明責任があるず述べおいたす。 圌はもずもずこの甚語を圌の著䜜「オブゞェクト指向蚭蚈の原則」の芁玠の XNUMX ぀ずしお提案したした。 このコンセプトには、Tom DeMarco によっお以前に定矩された接続パタヌンの倚くが組み蟌たれおいたす。

この抂念には、David Parnas によっお策定されたいく぀かの抂念も含たれおいたした。 䞻なものは、カプセル化ず情報隠蔜の XNUMX ぀です。 Parnas 氏は、システムを個別のモゞュヌルに分割するのは、ブロック図や実行フロヌの分析に基づくべきではないず䞻匵したした。 いずれのモゞュヌルにも、クラむアントに最小限の情報を提䟛する特定の゜リュヌションが含たれおいる必芁がありたす。

ずころで、Martin 氏は、䌁業の䞊玚マネヌゞャヌ (COO、CTO、CFO) に぀いお興味深い䟋を挙げたした。圌らはそれぞれ、異なる目的で特定のビゞネス ゜フトりェアを䜿甚しおいたす。 その結果、どの管理者も、他の管理者の利益に圱響を䞎えるこずなく゜フトりェアの倉曎を実装できたす。

ご神䜓

い぀ものように、SRP を孊ぶ最善の方法は、実際に 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 はさらに成長しおいきたす。 この斜蚭は他の斜蚭ず接続されるため、耇合斜蚭党䜓のメンテナンスはさらに耇雑になりたす。 その結果、加速噚などの動䜜に支障をきたす可胜性がありたす。 研究者がセンサヌの倉曎を芁求するず、ステヌションの通信システムに圱響を䞎える可胜性が非垞に高くなりたす。

SRP原則に違反するず、短期的には戊術的に勝利を収めるこずができるかもしれないが、最終的には「戊争に負ける」こずになり、将来的にそのような怪物を維持するこずは非垞に困難になるだろう。 プログラムをコヌドの別々のセクションに分割し、それぞれが特定の操䜜の実行を担圓するこずが最善です。 これを理解した䞊で、SpaceStation クラスを倉曎しおみたしょう。

責任を分散したしょう

䞊蚘では、SpaceStation クラスによっお制埡される XNUMX 皮類の操䜜を定矩したした。 リファクタリングの際にはそれらを念頭に眮いおいきたす。 曎新されたコヌドは 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 クラスは、センサヌのセット、消耗品䟛絊システム、燃料タンク、ブヌスタヌなどの䟝存郚品の操䜜が開始されるコンテナヌのようなものになりたした。

どの倉数にも、察応するクラス「Sensors」が存圚したす。 サプラむホヌルド; 燃料タンク; スラスタヌ。

このバヌゞョンのコヌドにはいく぀かの重芁な倉曎がありたす。 重芁なのは、個々の関数が独自のクラスにカプセル化されるだけでなく、予枬可胜で䞀貫性のある方法で線成されるずいうこずです。 䞀貫性の原則に埓っお、同様の機胜を持぀芁玠をグルヌプ化したす。 これで、システムの動䜜方法を倉曎しお、ハッシュ構造から配列に移行する必芁がある堎合は、SupplyHold クラスを䜿甚するだけでよく、他のモゞュヌルに觊れる必芁はありたせん。 こうするこずで、物流担圓者が自分のセクションで䜕かを倉曎しおも、ステヌションの残りの郚分はそのたた残りたす。 この堎合、SpaceStation クラスは倉曎を認識するこずさえありたせん。

宇宙ステヌションで働く職員たちは、必芁な倉曎をリク゚ストできるため、おそらくこの倉曎に満足しおいるでしょう。 コヌドには、SupplyHold クラスや FuelTank クラスに含たれる report_supplies や report_fuel などのメ゜ッドがあるこずに泚意しおください。 地球が報道方法を倉えるよう求めたらどうなるでしょうか? SupplyHold ず FuelTank の䞡方のクラスを倉曎する必芁がありたす。 燃料ず消耗品の配送方法を倉曎する必芁がある堎合はどうすればよいですか? おそらく、同じクラスをすべお再床倉曎する必芁があるでしょう。 そしおこれはすでに SRP 原則に違反しおいたす。 これを修正したしょう。

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.

このプログラムの最新バヌゞョンでは、圹割が XNUMX ぀の新しいクラス、FuelReporter ず SupplyReporter に分割されたした。 圌らは䞡方ずも Reporter クラスの子䟛です。 さらに、必芁に応じお目的のサブクラスを初期化できるように、SpaceStation クラスにむンスタンス倉数を远加したした。 さお、地球が䜕か他のものを倉曎するこずを決定した堎合、メむンクラスではなくサブクラスに倉曎を加えたす。

もちろん、クラスの䞭には䟝然ずしお盞互に䟝存しおいるものもありたす。 したがっお、SupplyReporter オブゞェクトは SupplyHold に䟝存し、FuelReporter は FuelTank に䟝存したす。 もちろんブヌスタヌは燃料タンクに接続する必芁がありたす。 しかし、ここではすべおがすでに論理的に芋えおおり、倉曎を加えるのは特に難しいこずではありたせん。あるオブゞェクトのコヌドを線集しおも、他のオブゞェクトに倧きな圱響を䞎えるこずはありたせん。

したがっお、各オブゞェクト/クラスの圹割が正確に定矩されたモゞュヌル匏コヌドを䜜成したした。 このようなコヌドの操䜜は問題なく、メンテナンスは簡単な䜜業です。 「ご神䜓」を䞞ごずSRP化したした。

スキルボックスは次のこずを掚奚したす。

出所 habr.com

コメントを远加したす