Saka penerjemah: diterbitake kanggo sampeyan artikel dening Severin Perez babagan nggunakake prinsip SOLID ing pemrograman. Informasi saka artikel bakal migunani kanggo pamula lan programer sing berpengalaman.
Yen sampeyan ana ing pembangunan, mesthine sampeyan wis krungu babagan prinsip SOLID. Dheweke ngaktifake programer kanggo nulis kode sing resik, terstruktur lan gampang dijaga. Wigati dicathet yen ing program ana sawetara cara kanggo nindakake tugas tartamtu kanthi bener. Spesialis sing beda-beda duwe gagasan lan pangerten sing beda babagan "dalan sing bener"; kabeh gumantung saka pengalaman saben wong. Nanging, gagasan sing diproklamasiake ing SOLID ditampa dening meh kabeh perwakilan komunitas IT. Dheweke dadi titik wiwitan kanggo muncule lan pangembangan akeh praktik manajemen pangembangan sing apik.
Ayo ngerti apa prinsip SOLID lan carane mbantu kita.
Kita ngelingake:kanggo kabeh sing maca "Habr" - diskon 10 rubel nalika ndhaptar kursus Skillbox nggunakake kode promosi "Habr".
Apa iku SOLID?
Istilah iki minangka singkatan, saben huruf saka istilah kasebut minangka wiwitan jeneng prinsip tartamtu:
SPrinsip Tanggung Jawab. A modul bisa duwe siji lan mung siji alesan kanggo owah-owahan.
The Open / Prinsip Tertutup (prinsip mbukak/tertutup). Kelas lan unsur liyane kudu mbukak kanggo extension, nanging ditutup kanggo modifikasi.
βThe LPrinsip Substitusi iskov (prinsip substitusi Liskov). Fungsi sing nggunakake jinis dhasar kudu bisa nggunakake subtipe saka jinis dhasar tanpa ngerti.
The IPrinsip Segregasi Antarmukaβ (prinsip pamisahan antarmuka). Entitas piranti lunak ora kudu gumantung marang cara sing ora digunakake.
The DPrinsip Inversi ependency (prinsip inversi dependensi). Modul ing tingkat sing luwih dhuwur ngirim ora gumantung ing modul ing tingkat ngisor.
Prinsip Tanggung Jawab Tunggal
β
Prinsip Tanggung Jawab Tunggal (SRP) nyatakake yen saben kelas utawa modul ing program kudu tanggung jawab mung siji bagean saka fungsi program kasebut. Kajaba iku, unsur tanggung jawab iki kudu ditugasake ing kelas dhewe, tinimbang kasebar ing kelas sing ora ana hubungane. Pangembang lan pangareping penginjil SRP, Robert S. Martin, nggambarake tanggung jawab minangka alesan kanggo owah-owahan. Dheweke wiwitane ngusulake istilah iki minangka salah sawijining unsur karyane "Principles of Object-Oriented Design". Konsep kasebut kalebu akeh pola konektivitas sing sadurunge ditetepake dening Tom DeMarco.
Konsep kasebut uga kalebu sawetara konsep sing dirumusake dening David Parnas. Loro sing utama yaiku enkapsulasi lan ndhelikake informasi. Parnas ujar manawa mbagi sistem dadi modul sing kapisah ora kudu adhedhasar analisis diagram blok utawa aliran eksekusi. Sembarang modul kudu ngemot solusi tartamtu sing nyedhiyakake informasi minimal kanggo klien.
Kaya biasane, cara paling apik kanggo sinau SRP yaiku ndeleng tumindak. Ayo goleki bagean saka program sing ora netepi Prinsip Tanggung Jawab Tunggal. Iki minangka kode Ruby sing nggambarake prilaku lan atribut stasiun ruang angkasa.
Deleng conto kasebut lan coba nemtokake ing ngisor iki:
Tanggung jawab obyek kasebut sing diumumake ing kelas SpaceStation.
Sing bisa uga kasengsem ing operasi stasiun ruang angkasa.
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
Bener, stasiun ruang angkasa kita ora bisa digunakake (aku ora mikir yen aku bakal ditelpon saka NASA kapan wae), nanging ana sing kudu dianalisis ing kene.
Sanajan ora ana karyawan stasiun sing diwenehi kelas, kita bisa mbayangake sapa sing tanggung jawab. Paling kamungkinan, ilmuwan ngontrol sensor, logistik tanggung jawab kanggo nyuplai sumber daya, insinyur tanggung jawab kanggo pasokan bahan bakar, lan pilot ngontrol boosters.
Nglanggar prinsip SRP bisa menehi kamenangan taktik short-term, nanging ing pungkasan kita bakal "kalah perang", lan bakal dadi angel banget kanggo njaga monster kuwi ing mangsa. Iku paling apik kanggo dibagi program menyang bagean kapisah saka kode, saben kang tanggung jawab kanggo nindakake operasi tartamtu. Ngerteni iki, ayo ngganti kelas SpaceStation.
Ayo dibagi tanggung jawab
Ndhuwur kita nemtokake papat jinis operasi sing dikontrol dening kelas SpaceStation. Kita bakal mbudidaya nalika refactoring. Kode sing dianyari luwih cocog karo 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
Ana akeh owah-owahan, program mesthi katon luwih apik saiki. Saiki kelas SpaceStation kita wis dadi luwih saka wadhah sing operasi diwiwiti kanggo bagean sing gumantung, kalebu set sensor, sistem pasokan sing bisa dikonsumsi, tangki bahan bakar, lan booster.
Kanggo samubarang variabel saiki ana kelas sing cocog: Sensor; SupplyHold; Tangki Bahan Bakar; Pendorong.
Ana sawetara owah-owahan penting ing versi kode iki. Titik iku fungsi individu ora mung encapsulated ing kelas dhewe, padha diatur ing kuwi cara dadi bisa katebak lan konsisten. Kita nglumpukake unsur kanthi fungsi sing padha kanggo ngetutake prinsip koherensi. Saiki, yen kita kudu ngganti cara kerja sistem, pindhah saka struktur hash menyang array, gunakake kelas SupplyHold; kita ora kudu ndemek modul liyane. Kanthi cara iki, yen petugas logistik ngganti apa wae ing bageane, stasiun liyane bakal tetep utuh. Ing kasus iki, kelas SpaceStation malah ora bakal weruh owah-owahan.
Petugas kita sing kerja ing stasiun ruang angkasa mbokmenawa seneng karo owah-owahan amarga bisa njaluk sing dibutuhake. Elinga yen kode kasebut nduweni cara kayata report_supplies lan report_fuel sing ana ing kelas SupplyHold lan FuelTank. Apa sing bakal kelakon yen Bumi njaluk ngganti cara laporan? Loro kelas, SupplyHold lan FuelTank, kudu diganti. Kepiye yen sampeyan kudu ngganti cara pangiriman bahan bakar lan bahan bakar? Sampeyan mbokmenawa kudu ngganti kabeh kelas sing padha maneh. Lan iki wis nglanggar prinsip SRP. Ayo didandani iki.
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.
Mesthi, sawetara kelas kita isih gumantung ing saben liyane. Mangkono, obyek SupplyReporter gumantung ing SupplyHold, lan FuelReporter gumantung ing FuelTank. Mesthi, boosters kudu disambungake menyang tank bahan bakar. Nanging ing kene kabeh wis katon logis, lan owah-owahan ora bakal angel banget - nyunting kode siji obyek ora bakal mengaruhi liyane.
Mangkono, kita wis nggawe kode modular ngendi tanggung jawab saben obyek / kelas wis ditetepake sabenere. Nggarap kode kasebut ora dadi masalah, njaga bakal dadi tugas sing gampang. Kita wis ngowahi kabeh "obyek gaib" dadi SRP.