
Von dem Ăbersetzer: haben wir fĂŒr Sie veröffentlicht ĂŒber die Anwendung der SOLID-Prinzipien in der Programmierung. Die Informationen aus dem Artikel sind sowohl fĂŒr AnfĂ€nger als auch fĂŒr erfahrene Programmierer hilfreich.
Wenn Sie sich mit Entwicklung beschĂ€ftigen, haben Sie wahrscheinlich von den SOLID-Prinzipien gehört. Sie ermöglichen es Programmierern, sauberen, gut strukturierten und leicht wartbaren Code zu schreiben. Es ist anzumerken, dass es in der Programmierung verschiedene AnsĂ€tze gibt, wie man bestimmte Aufgaben korrekt ausfĂŒhrt. Unterschiedliche Fachleute haben unterschiedliche Ideen und Auffassungen vom 'richtigen Weg', alles hĂ€ngt von den Erfahrungen jedes Einzelnen ab. Dennoch werden die Ideen, die in SOLID verkĂŒndet werden, von fast allen Vertretern der IT-Community akzeptiert. Sie wurden zum Ausgangspunkt fĂŒr die Entstehung und Entwicklung vieler guter Methoden im Projektmanagement.
Lassen Sie uns klÀren, was die SOLID-Prinzipien sind und wie sie uns helfen.
Skillbox empfiehlt: Praktischer Kurs .
Wir erinnern daran: alle Leser von âHabrâ erhalten einen Rabatt von 10.000 Rubel bei der Anmeldung zu einem beliebigen Kurs von Skillbox mit dem Aktionscode âHabrâ.
Was ist SOLID?
Dieser Begriff ist ein Akronym, jeder Buchstabe steht fĂŒr den Anfang eines bestimmten Prinzips:
- SSingle Responsibility Principle (Prinzip der einzigen Verantwortung). Ein Modul sollte nur einen Grund fĂŒr eine Ănderung haben.
- (Prinzip der Offenheit/Schlossenheit). Klassen und andere Elemente sollten fĂŒr Erweiterungen offen sein, aber fĂŒr Modifikationen geschlossen.
- (Prinzip der Liskovschen Substitution). Funktionen, die den Basistyp verwenden, sollten in der Lage sein, Untertypen des Basistyps zu verwenden, ohne davon zu wissen.
- â(Prinzip der Schnittstellen-Trennung). Softwareeinheiten sollten nicht von Methoden abhĂ€ngen, die sie nicht verwenden.
- (Prinzip der AbhÀngkeitsumkehr). Module höherer Ebenen sollten nicht von Modulen niedrigerer Ebenen abhÀngen.
Prinzip der einzigen Verantwortung
â
Das Prinzip der einzelnen Verantwortung (SRP) besagt, dass jede Klasse oder jedes Modul im Programm nur fĂŒr einen Teil der FunktionalitĂ€t dieses Programms verantwortlich sein sollte. DarĂŒber hinaus sollten die Elemente dieser Verantwortung an ihrer Klasse festgelegt werden und nicht ĂŒber nicht verwandte Klassen verteilt sein. Der Entwickler und Hauptverfechter des SRP, Robert C. Martin, beschreibt Verantwortung als die Ursache fĂŒr VerĂ€nderungen. UrsprĂŒnglich fĂŒhrte er diesen Begriff als eines der Elemente seiner Arbeit 'Prinzipien des objektorientierten Designs' ein. In das Konzept flossen viele Aspekte der KohĂ€sionsgesetzmĂ€Ăigkeit ein, die zuvor von Tom DeMarco definiert wurde.
In das Konzept gingen auch mehrere Begriffe ein, die von David Parnas formuliert wurden. Die beiden Hauptbegriffe sind Kapselung und Informationsverbergung. Parnas argumentierte, dass die Aufteilung eines Systems in separate Module nicht auf einer Analyse von Flussdiagrammen oder AusfĂŒhrungsströmen basieren sollte. Jedes der Module sollte eine bestimmte Lösung enthalten, die den Kunden mindestens Informationen bereitstellt.
Ăbrigens brachte Martin ein interessantes Beispiel mit höheren Managern eines Unternehmens (COO, CTO, CFO), von denen jeder spezifische Software fĂŒr das GeschĂ€ft zu unterschiedlichen Zwecken verwendet. Letztendlich kann jeder von ihnen Ănderungen an der Software vornehmen, ohne die Interessen der anderen Manager zu berĂŒhren.
Göttliches Objekt
Wie immer ist der beste Weg, SRP zu verstehen, alles in Aktion zu sehen. Schauen wir uns einen Abschnitt des Programms an, der nicht dem Prinzip der einzelnen Verantwortung entspricht. Dies ist Ruby-Code, der das Verhalten und die Attribute einer Raumstation beschreibt.
Sehen Sie sich das Beispiel an und versuchen Sie folgendes zu bestimmen:
Die Verantwortlichkeiten der Objekte, die in der Klasse SpaceStation deklariert sind.
Diejenigen, die an der Arbeit der Raumstation interessiert sein könnten.
class Raumstation
def initialize
@vorrÀte = {}
@kraftstoff = 0
end
def run_sensors
puts "----- Sensor Aktion -----"
puts "Sensoren laufen!"
end
def load_supplies(type, quantity)
puts "----- Lieferaktion -----"
puts "Lade #{quantity} Einheiten von #{type} in den Lagerraum."
if @vorrÀte[type]
@vorrÀte[type] += quantity
else
@vorrÀte[type] = quantity
end
end
def use_supplies(type, quantity)
puts "----- Lieferaktion -----"
if @vorrÀte[type] != nil && @vorrÀte[type] > quantity
puts "Verwende #{quantity} von #{type} aus dem Lager."
@vorrÀte[type] -= quantity
else
puts "Lieferfehler: Unzureichende #{type} im Lager."
end
end
def report_supplies
puts "----- Lieferbericht -----"
if @vorrÀte.keys.length > 0
@vorrÀte.each do |type, quantity|
puts "#{type} verfĂŒgbar: #{quantity} Einheiten"
end
else
puts "Lager ist leer."
end
end
def load_fuel(quantity)
puts "----- Kraftstoffaktion -----"
puts "Lade #{quantity} Einheiten Kraftstoff in den Tank."
@kraftstoff += quantity
end
def report_fuel
puts "----- Kraftstoffbericht -----"
puts "#{@kraftstoff} Einheiten Kraftstoff verfĂŒgbar."
end
def activate_thrusters
puts "----- Triebwerksaktion -----"
if @kraftstoff >= 10
puts "Triebwerksaktion erfolgreich."
@kraftstoff -= 10
else
puts "Triebwerkfehler: Unzureichender Kraftstoff verfĂŒgbar."
end
end
endEigentlich ist unsere Raumstation nicht funktionsfÀhig (ich denke, ich werde in naher Zukunft keinen Anruf von der NASA erhalten), aber es gibt hier etwas zu analysieren.
Die Klasse Raumstation hat mehrere verschiedene Verantwortlichkeiten (oder Aufgaben). Alle können nach Typen unterteilt werden:
- Sensoren;
- VorrÀte (Verbrauchsmaterialien);
- Kraftstoff;
- Triebwerke.
Obwohl kein Mitarbeiter der Station in die Klasse eingeordnet ist, können wir leicht annehmen, wer fĂŒr was verantwortlich ist. Höchstwahrscheinlich ĂŒberwacht ein Wissenschaftler die Sensoren, ein Logistiker ist fĂŒr die Bereitstellung der Ressourcen zustĂ€ndig, ein Ingenieur kĂŒmmert sich um die KraftstoffvorrĂ€te und ein Pilot kontrolliert die Triebwerke.
Können wir sagen, dass dieses Programm nicht dem SRP entspricht? Ja, natĂŒrlich. Aber die Klasse SpaceStation ist ein typisches âgöttliches Objektâ, das alles weiĂ und alles tut. Dies ist das grundlegende Antimuster in der objektorientierten Programmierung. FĂŒr AnfĂ€nger sind solche Objekte Ă€uĂerst schwierig zu warten. Bisher ist das Programm sehr einfach, ja, aber stellen Sie sich vor, was passiert, wenn wir neue Funktionen hinzufĂŒgen. Vielleicht benötigt unsere Raumstation einen medizinischen Raum oder einen Besprechungsraum. Und je mehr Funktionen es gibt, desto gröĂer wird die SpaceStation. Und da dieses Objekt mit anderen verbunden sein wird, wird die Wartung des gesamten Komplexes noch komplizierter. Letztendlich könnten wir die Funktionsweise beispielsweise der Beschleuniger beeintrĂ€chtigen. Wenn ein Wissenschaftler Ănderungen an der Sensortechnologie anfordert, könnte dies durchaus die Kommunikationssysteme der Station betreffen.
Eine Verletzung des SRP-Prinzips kann einen kurzfristigen taktischen Sieg bringen, aber letztendlich âverlieren wir den Kriegâ, und es wird ziemlich kompliziert, dieses Monster in der Zukunft zu warten. Am besten ist es, das Programm in separate Codeabschnitte zu unterteilen, von denen jeder fĂŒr die DurchfĂŒhrung einer bestimmten Operation verantwortlich ist. Unter BerĂŒcksichtigung dessen, lassen Sie uns die Klasse SpaceStation Ă€ndern.
Verantwortung verteilen
Wir haben oben vier Arten von Operationen definiert, die von der Klasse SpaceStation gesteuert werden. Bei der Refaktorisierung werden wir dies im Hinterkopf behalten. Der aktualisierte Code entspricht besser dem SRP.
class Raumstation
attr_reader :sensoren, :vorratslager, :kraftstofftank, :triebwerke
def initialize
@vorratslager = Vorratslager.new
@sensoren = Sensoren.new
@kraftstofftank = Kraftstofftank.new
@triebwerke = Triebwerke.new(@kraftstofftank)
end
end
class Sensoren
def run_sensoren
puts "----- Sensoraktion -----"
puts "Sensoren werden ausgefĂŒhrt!"
end
end
class Vorratslager
attr_accessor :vorrÀte
def initialize
@vorrÀte = {}
end
def lade_vorrÀte(typ, menge)
puts "----- Vorratsaktion -----"
puts "Lade #{menge} Einheiten von #{typ} ins Vorratslager."
if @vorrÀte[typ]
@vorrÀte[typ] += menge
else
@vorrÀte[typ] = menge
end
end
def verwende_vorrÀte(typ, menge)
puts "----- Vorratsaktion -----"
if @vorrÀte[typ] != nil && @vorrÀte[typ] > menge
puts "Verwende #{menge} von #{typ} aus dem Vorratslager."
@vorrÀte[typ] -= menge
else
puts "Vorratsfehler: Unzureichende #{typ} im Vorratslager."
end
end
def berichte_vorrÀte
puts "----- Vorratsbericht -----"
if @vorrÀte.keys.length > 0
@vorrÀte.each do |typ, menge|
puts "#{typ} verfĂŒgbar: #{menge} Einheiten"
end
else
puts "Vorratslager ist leer."
end
end
end
class Kraftstofftank
attr_accessor :kraftstoff
def initialize
@kraftstoff = 0
end
def get_kraftstoffstÀnde
@kraftstoff
end
def lade_kraftstoff(menge)
puts "----- Kraftstoffaktion -----"
puts "Lade #{menge} Einheiten Kraftstoff in den Tank."
@kraftstoff += menge
end
def verwende_kraftstoff(menge)
puts "----- Kraftstoffaktion -----"
puts "Verwende #{menge} Einheiten Kraftstoff aus dem Tank."
@kraftstoff -= menge
end
def berichte_kraftstoff
puts "----- Kraftstoffbericht -----"
puts "#{@kraftstoff} Einheiten Kraftstoff verfĂŒgbar."
end
end
class Triebwerke
def initialize(kraftstofftank)
@linked_kraftstofftank = kraftstofftank
end
def aktiviere_triebwerke
puts "----- Triebwerksaktion -----"
if @linked_kraftstofftank.get_kraftstoffstÀnde >= 10
puts "Schubaktion erfolgreich."
@linked_kraftstofftank.verwendekraftstoff(10)
else
puts "Triebwerksfehler: Unzureichender Kraftstoff verfĂŒgbar."
end
end
endEs gibt viele Ănderungen, das Programm sieht jetzt definitiv besser aus. Unser Raumstation-Klasse ist nun eher ein Container, in dem Operationen fĂŒr die abhĂ€ngigen Teile initiiert werden, einschlieĂlich einer Reihe von Sensoren, einem System zur Bereitstellung von Verbrauchsmaterialien, einem Kraftstofftank und Triebwerken.
FĂŒr jede der Variablen gibt es jetzt eine entsprechende Klasse: Sensoren; Vorratslager; Kraftstofftank; Triebwerke.
In dieser Version des Codes gibt es mehrere wichtige Ănderungen. Die Tatsache ist, dass einzelne Funktionen nicht nur in eigenen Klassen gekapselt sind, sondern auch so organisiert sind, dass sie vorhersehbar und konsistent werden. Wir gruppieren funktional Ă€hnliche Elemente, um dem KohĂ€renzprinzip zu folgen. Wenn wir nun das Funktionsprinzip des Systems Ă€ndern mĂŒssen, von einer Hash-Struktur auf ein Array wechseln, nutzen Sie einfach die Klasse SupplyHold, ohne dass andere Module betroffen sind. Auf diese Weise bleibt der Rest der Station unberĂŒhrt, selbst wenn der fĂŒr die Logistik zustĂ€ndige Offizier etwas in seinem Bereich Ă€ndert. Die Klasse SpaceStation wird nichts von den Ănderungen mitbekommen.
Unsere Offiziere, die auf der Raumstation arbeiten, sind wahrscheinlich erfreut ĂŒber die Ănderungen, da sie die fĂŒr sie notwendigen anfordern können. Beachten Sie, dass es im Code Methoden wie report_supplies und report_fuel gibt, die in den Klassen SupplyHold und FuelTank enthalten sind. Was passiert, wenn die Erde um eine Ănderung der Berichtserstellung bittet? Dann mĂŒssen beide Klassen, SupplyHold und FuelTank, geĂ€ndert werden. Und was ist, wenn der Lieferweg fĂŒr Treibstoff und Verbrauchsmaterialien verĂ€ndert werden muss? Wahrscheinlich mĂŒssen ebenfalls wieder all diese Klassen geĂ€ndert werden. Das wĂ€re bereits ein VerstoĂ gegen das SRP-Prinzip. Lassen Sie uns das beheben.
class Raumstation
attr_reader :sensoren, :vorratskammer, :vorrats_reporter,
:kraftstofftank, :kraftstoff_reporter, :thruster
def initialize
@sensoren = Sensoren.new
@vorratskammer = Vorratskammer.new
@vorrats_reporter = VorratsReporter.new(@vorratskammer)
@kraftstofftank = Kraftstofftank.new
@kraftstoff_reporter = KraftstoffReporter.new(@kraftstofftank)
@thruster = Thruster.new(@kraftstofftank)
end
end
class Sensoren
def run_sensoren
puts "----- Sensoraktion -----"
puts "Sensoren laufen!"
end
end
class Vorratskammer
attr_accessor :vorrÀte
attr_reader :reporter
def initialize
@vorrÀte = {}
end
def get_vorrÀte
@vorrÀte
end
def load_vorrÀte(typ, menge)
puts "----- Vorratsaktion -----"
puts "Lade #{menge} Einheiten von #{typ} in die Vorratskammer."
if @vorrÀte[typ]
@vorrÀte[typ] += menge
else
@vorrÀte[typ] = menge
end
end
def use_vorrÀte(typ, menge)
puts "----- Vorratsaktion -----"
if @vorrÀte[typ] != nil && @vorrÀte[typ] > menge
puts "Verwende #{menge} von #{typ} aus der Vorratskammer."
@vorrÀte[typ] -= menge
else
puts "Vorratsfehler: Unzureichende #{typ} in der Vorratskammer."
end
end
end
class Kraftstofftank
attr_accessor :kraftstoff
attr_reader :reporter
def initialize
@kraftstoff = 0
end
def get_kraftstoffstÀnde
@kraftstoff
end
def load_kraftstoff(menge)
puts "----- Kraftstoffaktion -----"
puts "Lade #{menge} Einheiten von Kraftstoff in den Tank."
@kraftstoff += menge
end
def use_kraftstoff(menge)
puts "----- Kraftstoffaktion -----"
puts "Verwende #{menge} Einheiten von Kraftstoff aus dem Tank."
@kraftstoff -= menge
end
end
class Thruster
KRAFTSTOFF_PRO_THRUST = 10
def initialize(kraftstofftank)
@verbunden_kraftstofftank = kraftstofftank
end
def activate_thruster
puts "----- Thruster Aktion -----"
if @verbunden_kraftstofftank.get_kraftstoffstÀnde >= KRAFTSTOFF_PRO_THRUST
puts "Thrust-Aktion erfolgreich."
@verbunden_kraftstofftank.use_kraftstoff(KRAFTSTOFF_PRO_THRUST)
else
puts "Thrusterfehler: Unzureichender Kraftstoff verfĂŒgbar."
end
end
end
class Reporter
def initialize(item, typ)
@verbunden_item = item
@typ = typ
end
def report
puts "----- #{@typ.capitalize} Bericht -----"
end
end
class KraftstoffReporter < Reporter
def initialize(item)
super(item, "kraftstoff")
end
def report
super
puts "#{@verbunden_item.get_kraftstoffstĂ€nde} Einheiten Kraftstoff verfĂŒgbar."
end
end
class VorratsReporter 0
@verbunden_item.get_vorrÀte.each do |typ, menge|
puts "#{typ} verfĂŒgbar: #{menge} Einheiten"
end
else
puts "Vorratskammer ist leer."
end
end
end
iss = Raumstation.new
iss.sensoren.run_sensoren
# ----- Sensoraktion -----
# Sensoren laufen!
iss.vorratskammer.use_vorrÀte("teile", 2)
# ----- Vorratsaktion -----
# Vorratsfehler: Unzureichende Teile in der Vorratskammer.
iss.vorratskammer.load_vorrÀte("teile", 10)
# ----- Vorratsaktion -----
# Lade 10 Einheiten Teile in die Vorratskammer.
iss.vorratskammer.use_vorrÀte("teile", 2)
# ----- Vorratsaktion -----
# Verwende 2 Teile aus der Vorratskammer.
iss.vorrats_reporter.report
# ----- Vorratsbericht -----
# Teile verfĂŒgbar: 8 Einheiten
iss.thruster.activate_thruster
# ----- Thruster Aktion -----
# Thrusterfehler: Unzureichender Kraftstoff verfĂŒgbar.
iss.kraftstofftank.load_kraftstoff(100)
# ----- Kraftstoffaktion -----
# Lade 100 Einheiten Kraftstoff in den Tank.
iss.thruster.activate_thruster
# ----- Thruster Aktion -----
# Thrust-Aktion erfolgreich.
# ----- Kraftstoffaktion -----
# Verwende 10 Einheiten Kraftstoff aus dem Tank.
iss.kraftstoff_reporter.report
# ----- Kraftstoffbericht -----
# 90 Einheiten Kraftstoff verfĂŒgbar.In dieser letzten Version des Programms wurden die Aufgaben auf zwei neue Klassen, FuelReporter und SupplyReporter, aufgeteilt. Beide sind Unterklassen der Klasse Reporter. DarĂŒber hinaus haben wir Instanzvariablen zur Klasse SpaceStation hinzugefĂŒgt, um bei Bedarf die benötigte Unterklasse zu initialisieren. Jetzt, wenn die Erde etwas Ă€ndern möchte, werden wir die Unterklassen anpassen und nicht die Hauptklasse.
NatĂŒrlich hĂ€ngen einige Klassen immer noch voneinander ab. So ist das Objekt SupplyReporter von SupplyHold abhĂ€ngig, wĂ€hrend FuelReporter von FuelTank abhĂ€ngt. SelbstverstĂ€ndlich mĂŒssen die Booster mit dem Treibstofftank verbunden sein. Aber hier scheint alles sinnvoll und Ănderungen werden nicht besonders schwierig sein â die Bearbeitung des Codes eines Objekts wird den anderen nicht stark beeinflussen.
So haben wir modularen Code erstellt, bei dem die Aufgaben jedes Objekts/Klasse eindeutig definiert sind. Mit diesem Code zu arbeiten ist kein Problem, die Wartung wird eine einfache Aufgabe sein. Das gesamte "göttliche Objekt" haben wir in SRP umgewandelt.
Skillbox empfiehlt:
- ZweijÀhriger praktischer Kurs .
- Online-Kurs .
- Praktischer Jahreskurs .
Quelle: habr.com
