Einführung in Entwurfsmuster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 85 von 107.
Entwurfsmuster sind wiederverwendbare Lösungen für häufige Probleme, die beim Softwareentwurf auftreten. Sie sind kein Code, den du direkt kopierst, sondern vielmehr Vorlagen, die dich dabei unterstützen, deine Typen und ihre Interaktionen zu strukturieren, um bestimmte Herausforderungen zu bewältigen.
Entwurfsmuster werden typischerweise in drei Kategorien gruppiert:
| Kategorie | Zweck | Beispiele |
|---|---|---|
| Erzeugungsmuster | Mechanismen zur Objekterzeugung | Singleton, Factory, Builder |
| Strukturmuster | Zusammensetzen von Typen zu größeren Strukturen | Adapter, Decorator, Composite |
| Verhaltensmuster | Kommunikation zwischen Objekten | Observer, Strategy, Command |
In Go sehen Entwurfsmuster anders aus als in traditionellen OOP-Sprachen.
Ohne Klassen oder Vererbung implementiert Go Muster mithilfe von Schnittstellen, Struktur-Einbettung und Komposition. Wo Java möglicherweise eine abstrakte Klasse verwenden würde, nutzt Go eine Schnittstelle. Wo C++ möglicherweise Vererbung verwenden würde, nutzt Go Einbettung.
Das Erlernen von Mustern hilft dir, häufige Probleme zu erkennen und Lösungen effektiv mit anderen Entwicklerinnen und Entwicklern zu kommunizieren. Wenn jemand erwähnt: „Verwende hier das Strategy-Muster“, versteht das gesamte Team den Ansatz ohne lange Erklärungen.
In den nächsten Lektionen wirst du mehrere grundlegende Muster in Go implementieren, angefangen mit Erzeugungsmustern wie Singleton und Factory und anschließend mit Verhaltensmustern wie Observer und Strategy.
Aufgabe
EinfachErstellen wir ein einfaches Benachrichtigungssystem, das zeigt, wie Design Patterns dabei helfen, Code zu organisieren! Du wirst eine Struktur erstellen, die in zukünftigen Lektionen problemlos um Muster wie Observer oder Strategy erweitert werden könnte. Konzentriere dich zunächst jedoch darauf, saubere Schnittstellen und Typen einzurichten, die guten OOP-Prinzipien folgen.
Du wirst deinen Code auf drei Dateien verteilen:
notifier.go: Definiere die zentrale Schnittstelle und die Typen für dein Benachrichtigungssystem.Erstelle eine
Notifier-Schnittstelle mit einer einzigen MethodeSend(message string) string, die eine Bestätigungsnachricht zurückgibt.Implementiere zwei konkrete Benachrichtigungstypen:
EmailNotifiermit einemAddress-Feld. SeineSend-Methode gibtEmail to [address]: [message]zurückSMSNotifiermit einemPhoneNumber-Feld. SeineSend-Methode gibtSMS to [phone]: [message]zurück
dispatcher.go: Erstelle einen Dispatcher, der über die Schnittstelle mit jedem Benachrichtigungstyp funktioniert.Erstelle eine
NotificationDispatcher-Struktur, die ein Slice vonNotifier-Schnittstellen enthält. Füge diese Methoden hinzu:AddNotifier(n Notifier), um einen Benachrichtigungstyp zu registrierenBroadcast(message string) []string, die die Nachricht über alle registrierten Benachrichtigungstypen sendet und ein Slice mit allen Bestätigungszeichenfolgen zurückgibt
Erstelle außerdem einen
NewNotificationDispatcher()-Konstruktor, der einen initialisierten Dispatcher zurückgibt.main.go: Richte die Benachrichtigungstypen ein und sende Nachrichten an alle.Lies die Anzahl der zu erstellenden Benachrichtigungstypen ein. Lies für jeden Benachrichtigungstyp dessen Art (
emailodersms) und sein Ziel (Adresse oder Telefonnummer) ein. Füge jeden Typ einem Dispatcher hinzu.Lies anschließend eine Nachricht ein, die an alle gesendet werden soll. Sende sie über alle Benachrichtigungstypen und gib jede Bestätigung in einer eigenen Zeile aus.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Anzahl der Benachrichtigungstypen
- Für jeden Benachrichtigungstyp: Art (
emailodersms), dann Ziel - Letzte Zeile: Nachricht, die an alle gesendet werden soll
Zum Beispiel bei folgender Eingabe:
3
email
alice@example.com
sms
555-1234
email
bob@example.com
Server maintenance at midnightDeine Ausgabe sollte folgendermaßen aussehen:
Email to alice@example.com: Server maintenance at midnight
SMS to 555-1234: Server maintenance at midnight
Email to bob@example.com: Server maintenance at midnightUnd bei folgender Eingabe:
2
sms
555-9999
sms
555-0000
Meeting canceledDeine Ausgabe sollte folgendermaßen aussehen:
SMS to 555-9999: Meeting canceled
SMS to 555-0000: Meeting canceledDiese Struktur zeigt, wie Schnittstellen flexible und erweiterbare Designs ermöglichen. Der Dispatcher weiß nichts über bestimmte Benachrichtigungstypen. Er weiß nur, dass sie Send aufrufen können. Das ist die Grundlage, auf der Muster wie Observer und Strategy aufbauen!
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lese die Anzahl der Notifier
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Erstelle einen neuen Dispatcher
dispatcher := NewNotificationDispatcher()
// Lese jeden Notifier und füge ihn zum Dispatcher hinzu
for i := 0; i < count; i++ {
notifierType, _ := reader.ReadString('\n')
notifierType = strings.TrimSpace(notifierType)
destination, _ := reader.ReadString('\n')
destination = strings.TrimSpace(destination)
// TODO: Erstelle den passenden Notifier basierend auf dem Typ
// und füge ihn zum Dispatcher hinzu
// Hinweis: Prüfe, ob notifierType "email" oder "sms" ist
}
// Lese die zu übertragende Nachricht
message, _ := reader.ReadString('\n')
message = strings.TrimSpace(message)
// TODO: Übertrage die Nachricht und gib jede Bestätigung aus
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung13Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterAbstraktes Factory-MusterObserver-MusterStrategy-Muster2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler