
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
