
Vom Übersetzer: wir haben 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 nützlich.
Wenn Sie in der Entwicklung tätig sind, haben Sie wahrscheinlich schon von den SOLID-Prinzipien gehört. Sie ermöglichen es Programmierern, sauberen, gut strukturierten und leicht wartbaren Code zu schreiben. Es ist wichtig zu beachten, dass es in der Programmierung verschiedene Ansätze gibt, wie bestimmte Aufgaben richtig ausgeführt werden sollten. Unterschiedliche Fachleute haben unterschiedliche Ansichten und Verständnisse des „richtigen Weges“, was oft von der Erfahrung jedes Einzelnen abhängt. Trotzdem werden die Ideen, die in SOLID vertreten sind, von nahezu allen Vertretern der IT-Community akzeptiert. Sie sind zu einem Ausgangspunkt für die Entstehung und Entwicklung vieler guter Methoden im Management von Softwareentwicklung geworden.
Lassen Sie uns herausfinden, 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, wobei jeder Buchstabe der Anfang eines bestimmten Prinzips ist:
- OSingle Responsibility Principle (Prinzip der einzelnen Verantwortung). Ein Modul sollte nur einen Grund für eine Änderung haben.
- (Offenheits-/Geschlossenheitsprinzip). Klassen und andere Elemente sollten für Erweiterungen offen, aber für Modifikationen geschlossen sein.
- (Liskovsches Substitutionsprinzip). Funktionen, die den Basistyp verwenden, sollten in der Lage sein, Subtypen des Basistyps zu verwenden, ohne darüber Bescheid zu wissen.
- (Prinzip der Schnittstellenauftrennung). Software-Einheiten sollten nicht von Methoden abhängen, die sie nicht verwenden.
- (Prinzip der Abhängigkeit-Inversion). Module höherer Ebenen sollten nicht von Modulen niedrigerer Ebenen abhängen.
Prinzip der einzelnen Verantwortung
Das Prinzip der Einzelverantwortung (SRP) besagt, dass jede Klasse oder jedes Modul in einem Programm nur für einen bestimmten Teil der Funktionalität dieses Programms verantwortlich sein sollte. Darüber hinaus sollten die Elemente dieser Verantwortung an ihre Klasse gebunden und nicht über nicht verwandte Klassen verteilt werden. Der Entwickler und Hauptverfechter des SRP, Robert C. Martin, beschreibt Verantwortung als Ursache für Veränderungen. Er führte diesen Begriff ursprünglich als eines der Elemente seiner Arbeit "Prinzipien der objektorientierten Programmierung" ein. In das Konzept flossen zahlreiche Aspekte der Kohärenz ein, die zuvor von Tom DeMarco definiert wurden.
In das Konzept flossen 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 der Analyse von Flussdiagrammen oder Ausführungsströmen basieren sollte. Jedes Modul sollte eine bestimmte Lösung enthalten, die den Kunden nur minimale Informationen bereitstellt.
Übrigens gab Martin ein interessantes Beispiel mit den oberen Führungskräften des Unternehmens (COO, CTO, CFO), von denen jeder spezielle Software für unterschiedliche Geschäftszwecke verwendet. Letztendlich kann jeder von ihnen Änderungen an der Software implementieren, ohne die Interessen der anderen Manager zu beeinträchtigen.
Göttliches Objekt
Wie gewohnt ist der beste Weg, das SRP zu lernen, 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.
Überprüfen Sie das Beispiel 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 SpaceStation
def initialize
@supplies = {}
@fuel = 0
end
def run_sensors
puts "----- Sensor Action -----"
puts "Sensoren werden aktiviert!"
end
def load_supplies(type, quantity)
puts "----- Supply Action -----"
puts "Lade #{quantity} Einheiten von #{type} in das Versorgungsdepot."
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 "Verwende #{quantity} von #{type} aus dem Versorgungsdepot."
@supplies[type] -= quantity
else
puts "Versorgungsfehler: Unzureichende #{type} im Versorgungsdepot."
end
end
def report_supplies
puts "----- Supply Report -----"
if @supplies.keys.length > 0
@supplies.each do |type, quantity|
puts "#{type} verfügbar: #{quantity} Einheiten"
end
else
puts "Das Versorgungsdepot ist leer."
end
end
def load_fuel(quantity)
puts "----- Fuel Action -----"
puts "Lade #{quantity} Einheiten Kraftstoff in den Tank."
@fuel += quantity
end
def report_fuel
puts "----- Fuel Report -----"
puts "#{@fuel} Einheiten Kraftstoff verfügbar."
end
def activate_thrusters
puts "----- Thruster Action -----"
if @fuel >= 10
puts "Schubaktion erfolgreich."
@fuel -= 10
else
puts "Thruster-Fehler: Unzureichender Kraftstoff verfügbar."
end
end
endNun, unsere Raumstation ist derzeit nicht funktionsfähig (ich denke, ich werde in naher Zukunft keinen Anruf von NASA erhalten), aber es gibt hier einiges zu analysieren.
Der Klasse SpaceStation obliegt eine Vielzahl von Verantwortlichkeiten (oder Aufgaben). Alle können nach Typen unterteilt werden:
- Sensoren;
- Versorgung (Verbrauchsmaterial);
- Kraftstoff;
- Beschleuniger.
Obwohl keiner der Mitarbeiter der Station einer Klasse zugeordnet ist, können wir leicht nachvollziehen, wer für was verantwortlich ist. Wahrscheinlich überwacht der Wissenschaftler die Sensoren, der Logistiker ist für die Versorgung mit Ressourcen zuständig, der Ingenieur kümmert sich um die Treibstoffvorräte, und der Pilot kontrolliert die Beschleuniger.
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 Hauptantimuster in der objektorientierten Programmierung. Für einen Anfänger sind solche Objekte äußerst schwierig zu warten. Im Moment ist das Programm sehr einfach, ja, aber stellen Sie sich vor, was passieren könnte, wenn wir neue Funktionen hinzufügen. Vielleicht benötigt unsere Raumstation eine Erste-Hilfe-Station oder einen Konferenzraum. Und je mehr Funktionen es gibt, desto komplizierter wird die SpaceStation. Da dieses Objekt zudem mit anderen verbunden ist, wird die Wartung des gesamten Komplexes noch herausfordernder. Am Ende könnten wir zum Beispiel die Funktionalität von Beschleunigern beeinträchtigen. Wenn ein Wissenschaftler Änderungen im Umgang mit Sensoren anfordert, könnte dies durchaus die Kommunikationssysteme der Station beeinträchtigen.
Eine Verletzung des SRP-Prinzips kann kurzzeitig einen taktischen Vorteil bringen, aber letztendlich „verlieren wir den Krieg“. Die Wartung eines solchen Monsters wird in Zukunft sehr schwierig sein. Es ist am besten, das Programm in separate Codeabschnitte zu unterteilen, wobei jeder Abschnitt für die Ausführung einer bestimmten Operation verantwortlich ist. Vor diesem Hintergrund wollen wir die Klasse SpaceStation anpassen.
Verantwortung verteilen
Wir haben oben vier Arten von Operationen definiert, die von der Klasse SpaceStation kontrolliert werden. Bei der Refaktorisierung werden wir dies im Hinterkopf behalten. Der aktualisierte Code entspricht besser dem 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-Aktion -----"
puts "Sensoren werden ausgeführt!"
end
end
class SupplyHold
attr_accessor :supplies
def initialize
@supplies = {}
end
def load_supplies(type, quantity)
puts "----- Lieferaktion -----"
puts "Lade #{quantity} Einheiten von #{type} im Versorgungslager."
if @supplies[type]
@supplies[type] += quantity
else
@supplies[type] = quantity
end
end
def use_supplies(type, quantity)
puts "----- Lieferaktion -----"
if @supplies[type] != nil && @supplies[type] > quantity
puts "Verwendung von #{quantity} von #{type} aus dem Versorgungslager."
@supplies[type] -= quantity
else
puts "Lieferfehler: Unzureichendes #{type} im Versorgungslager."
end
end
def report_supplies
puts "----- Lieferbericht -----"
if @supplies.keys.length > 0
@supplies.each do |type, quantity|
puts "#{type} verfügbar: #{quantity} Einheiten"
end
else
puts "Das Versorgungslager ist leer."
end
end
end
class FuelTank
attr_accessor :fuel
def initialize
@fuel = 0
end
def get_fuel_levels
@fuel
end
def load_fuel(quantity)
puts "----- Kraftstoffaktion -----"
puts "Lade #{quantity} Einheiten Kraftstoff in den Tank."
@fuel += quantity
end
def use_fuel(quantity)
puts "----- Kraftstoffaktion -----"
puts "Verwendung von #{quantity} Einheiten Kraftstoff aus dem Tank."
@fuel -= quantity
end
def report_fuel
puts "----- Kraftstoffbericht -----"
puts "#{@fuel} Einheiten Kraftstoff verfügbar."
end
end
class Thrusters
def initialize(fuel_tank)
@linked_fuel_tank = fuel_tank
end
def activate_thrusters
puts "----- Triebwerkaktion -----"
if @linked_fuel_tank.get_fuel_levels >= 10
puts "Schubaktion erfolgreich."
@linked_fuel_tank.use_fuel(10)
else
puts "Triebwerkfehler: Unzureichender Kraftstoff verfügbar."
end
end
endEs gibt viele Änderungen, die Software sieht jetzt definitiv besser aus. Unser SpaceStation-Klasse ist jetzt eher ein Container, in dem die Operationen für abhängige Teile initiiert werden, einschließlich einer Sensorgruppe, einem Liefermaterial-System, einem Tank und Triebwerken.
Für jede der Variablen gibt es jetzt die entsprechende Klasse: Sensors; SupplyHold; FuelTank; Thrusters.
In dieser Version des Codes gibt es einige wichtige Änderungen. Die einzelnen Funktionen sind nicht nur in eigenen Klassen gekapselt, sondern sie sind auch so organisiert, dass sie vorhersehbar und konsistent werden. Wir gruppieren funktional ähnliche Elemente, um den Kohäsionsprinzipien zu folgen. Wenn wir das System ändern müssen, indem wir von einer Hash-Struktur zu einem Array wechseln, verwenden wir einfach die Klasse SupplyHold, ohne andere Module berühren zu müssen. So bleibt, wenn der für die Logistik zuständige Offizier etwas in seinem Bereich ändert, der Rest der Station unberührt. Die SpaceStation-Klasse wird von den Änderungen sogar nichts mitbekommen.
Unsere Offiziere, die auf der Raumstation arbeiten, freuen sich wahrscheinlich ü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 darum bittet, den Berichtserstellungsprozess zu ändern? Es müsste in beiden Klassen, SupplyHold und FuelTank, eine Änderung vorgenommen werden. Und was ist, wenn die Art der Kraftstoff- und Verbrauchsmittelbelieferung geändert werden muss? Wahrscheinlich müssten dann wieder alle diese Klassen geändert werden. Das widerspricht bereits dem SRP-Prinzip. Lassen Sie uns das beheben.
class Raumstation
attr_reader :sensoren, :vorratslager, :vorratsberichtgeber,
:kraftstofftank, :kraftstoffberichtgeber, :triebwerke
def initialize
@sensoren = Sensoren.new
@vorratslager = Vorratslager.new
@vorratsberichtgeber = Vorratsberichtgeber.new(@vorratslager)
@kraftstofftank = Kraftstofftank.new
@kraftstoffberichtgeber = Kraftstoffberichtgeber.new(@kraftstofftank)
@triebwerke = Triebwerke.new(@kraftstofftank)
end
end
class Sensoren
def run_sensoren
puts "----- Sensor Aktion -----"
puts "Sensoren aktivieren!"
end
end
class Vorratslager
attr_accessor :vorräte
attr_reader :berichtgeber
def initialize
@vorräte = {}
end
def get_vorräte
@vorräte
end
def lade_vorräte(typ, menge)
puts "----- Vorrats Aktion -----"
puts "Lade #{menge} Einheiten von #{typ} in das Vorratslager."
if @vorräte[typ]
@vorräte[typ] += menge
else
@vorräte[typ] = menge
end
end
def verwende_vorräte(typ, menge)
puts "----- Vorrats Aktion -----"
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
end
class Kraftstofftank
attr_accessor :kraftstoff
attr_reader :berichtgeber
def initialize
@kraftstoff = 0
end
def get_kraftstoffniveaus
@kraftstoff
end
def lade_kraftstoff(menge)
puts "----- Kraftstoff Aktion -----"
puts "Lade #{menge} Einheiten Kraftstoff in den Tank."
@kraftstoff += menge
end
def verwende_kraftstoff(menge)
puts "----- Kraftstoff Aktion -----"
puts "Verwende #{menge} Einheiten Kraftstoff aus dem Tank."
@kraftstoff -= menge
end
end
class Triebwerke
KRAFTSTOFF_PRO_TRIEB = 10
def initialize(kraftstofftank)
@verknüpft_kraftstofftank = kraftstofftank
end
def aktiviere_triebwerke
puts "----- Triebwerk Aktion -----"
if @verknüpft_kraftstofftank.get_kraftstoffniveaus >= KRAFTSTOFF_PRO_TRIEB
puts "Schubaktion erfolgreich."
@verknüpft_kraftstofftank.verwendekraftstoff(KRAFTSTOFF_PRO_TRIEB)
else
puts "Triebwerk Fehler: Unzureichender Kraftstoff vorhanden."
end
end
end
class Berichtgeber
def initialize(element, typ)
@verknüpft_element = element
@typ = typ
end
def bericht
puts "----- #{@typ.capitalize} Bericht -----"
end
end
class Kraftstoffberichtgeber < Berichtgeber
def initialize(element)
super(element, "kraftstoff")
end
def bericht
super
puts "#{@verknüpft_element.get_kraftstoffniveaus} Einheiten Kraftstoff verfügbar."
end
end
class Vorratsberichtgeber 0
@verknüpft_element.get_vorräte.each do |typ, menge|
puts "#{typ} verfügbar: #{menge} Einheiten"
end
else
puts "Vorratslager ist leer."
end
end
end
iss = Raumstation.new
iss.sensoren.run_sensoren
# ----- Sensor Aktion -----
# Sensoren aktivieren!
iss.vorratslager.verwende_vorräte("teile", 2)
# ----- Vorrats Aktion -----
# Vorratsfehler: Unzureichende Teile im Vorratslager.
iss.vorratslager.lade_vorräte("teile", 10)
# ----- Vorrats Aktion -----
# Lade 10 Einheiten von Teilen in das Vorratslager.
iss.vorratslager.verwende_vorräte("teile", 2)
# ----- Vorrats Aktion -----
# Verwende 2 von Teilen aus dem Vorratslager.
iss.vorratsberichtgeber.bericht
# ----- Vorrats Bericht -----
# Teile verfügbar: 8 Einheiten
iss.triebwerke.aktiviere_triebwerke
# ----- Triebwerk Aktion -----
# Triebwerk Fehler: Unzureichender Kraftstoff vorhanden.
iss.kraftstofftank.lade_kraftstoff(100)
# ----- Kraftstoff Aktion -----
# Lade 100 Einheiten Kraftstoff in den Tank.
iss.triebwerke.aktiviere_triebwerke
# ----- Triebwerk Aktion -----
# Schubaktion erfolgreich.
# ----- Kraftstoff Aktion -----
# Verwende 10 Einheiten Kraftstoff aus dem Tank.
iss.kraftstoffberichtgeber.bericht
# ----- Kraftstoff Bericht -----
# 90 Einheiten Kraftstoff verfügbar.In dieser letzten Version des Programms wurden die Verantwortlichkeiten auf zwei neue Klassen aufgeteilt: FuelReporter und SupplyReporter. Beide sind Unterklassen der Klasse Reporter. Außerdem haben wir Instanzvariablen zur Klasse SpaceStation hinzugefügt, um bei Bedarf die richtige Unterklasse zu initialisieren. Wenn die Erde also etwas ändern möchte, werden wir Änderungen in den Unterklassen vornehmen und nicht in der Hauptklasse.
Natürlich hängen einige Klassen immer noch voneinander ab. So hängt das Objekt SupplyReporter von SupplyHold ab, während FuelReporter von FuelTank abhängt. Außerdem müssen die Treibstoffe mit dem Treibstofftank verbunden sein. Aber das macht hier alles Sinn, und Änderungen werden nicht besonders schwierig sein – die Bearbeitung des Codes eines Objekts hat nicht allzu große Auswirkungen auf ein anderes.
So haben wir modularen Code erstellt, in dem die Verantwortlichkeiten jedes Objekts/Klasse klar definiert sind. Mit solch einem Code zu arbeiten, ist kein Problem; seine 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
