Wprowadzenie do wzorców projektowych
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 85 z 107.
Wzorce projektowe to wielokrotnego użytku rozwiązania typowych problemów pojawiających się podczas projektowania oprogramowania. Nie są to fragmenty kodu, które kopiujesz bezpośrednio, lecz szablony wskazujące, jak strukturyzować typy i ich interakcje, aby rozwiązywać konkretne wyzwania.
Wzorce projektowe są zazwyczaj podzielone na trzy kategorie:
| Kategoria | Cel | Przykłady |
|---|---|---|
| Kreacyjne | Mechanizmy tworzenia obiektów | Singleton, Factory, Builder |
| Strukturalne | Łączenie typów w większe struktury | Adapter, Decorator, Composite |
| Behawioralne | Komunikacja między obiektami | Observer, Strategy, Command |
W Go wzorce projektowe wyglądają inaczej niż w tradycyjnych językach programowania obiektowego.
Bez klas i dziedziczenia Go implementuje wzorce za pomocą interfejsów, osadzania struktur i kompozycji. Na przykład tam, gdzie Java mogłaby użyć klasy abstrakcyjnej, Go używa interfejsu. Tam, gdzie C++ mogłoby użyć dziedziczenia, Go używa osadzania.
Znajomość wzorców pomaga rozpoznawać typowe problemy i skutecznie komunikować rozwiązania innym programistom. Gdy ktoś wspomina „użyj tutaj wzorca Strategia”, cały zespół rozumie to podejście bez długich wyjaśnień.
W kolejnych lekcjach zaimplementujesz kilka podstawowych wzorców w Go, zaczynając od wzorców kreacyjnych, takich jak Singleton i Factory, a następnie przechodząc do wzorców behawioralnych, takich jak Observer i Strategy.
Wyzwanie
ŁatwyZbudujemy prosty system powiadomień, który pokaże, jak wzorce projektowe pomagają porządkować kod! Utworzysz strukturę, którą w przyszłych lekcjach można łatwo rozszerzyć o wzorce takie jak Observer czy Strategy, ale na razie skup się na przygotowaniu przejrzystych interfejsów i typów zgodnych z dobrymi zasadami OOP.
Podzielisz kod na trzy pliki:
notifier.go: Zdefiniuj podstawowy interfejs i typy dla systemu powiadomień.Utwórz interfejs
Notifierz jedną metodąSend(message string) string, która zwraca wiadomość z potwierdzeniem.Zaimplementuj dwa konkretne typy powiadomień:
EmailNotifierz polemAddress. Jego metodaSendzwracaEmail to [address]: [message]SMSNotifierz polemPhoneNumber. Jego metodaSendzwracaSMS to [phone]: [message]
dispatcher.go: Utwórz dyspozytor, który obsługuje dowolne powiadomienia za pośrednictwem interfejsu.Utwórz strukturę
NotificationDispatcher, która przechowuje wycinek interfejsówNotifier. Dodaj następujące metody:AddNotifier(n Notifier)do rejestrowania powiadomieniaBroadcast(message string) []string, która wysyła wiadomość za pośrednictwem wszystkich zarejestrowanych powiadomień i zwraca wycinek wszystkich wiadomości z potwierdzeniem
Utwórz również konstruktor
NewNotificationDispatcher(), który zwraca zainicjalizowany dyspozytor.main.go: Skonfiguruj powiadomienia i rozsyłaj wiadomości.Odczytaj liczbę powiadomień do utworzenia. Dla każdego powiadomienia odczytaj jego typ (
emaillubsms) oraz miejsce docelowe (adres lub numer telefonu). Dodaj każde z nich do dyspozytora.Następnie odczytaj wiadomość do rozesłania. Wyślij ją za pośrednictwem wszystkich powiadomień i wypisz każde potwierdzenie w osobnym wierszu.
Dane wejściowe będą miały następujący format:
- Wiersz 1: Liczba powiadomień
- Dla każdego powiadomienia: typ (
emaillubsms), a następnie miejsce docelowe - Ostatni wiersz: Wiadomość do rozesłania
Na przykład dla danych:
3
email
alice@example.com
sms
555-1234
email
bob@example.com
Server maintenance at midnightOczekiwany wynik:
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 midnightA dla danych:
2
sms
555-9999
sms
555-0000
Meeting canceledOczekiwany wynik:
SMS to 555-9999: Meeting canceled
SMS to 555-0000: Meeting canceledTa struktura pokazuje, jak interfejsy umożliwiają tworzenie elastycznych i rozszerzalnych rozwiązań. Dyspozytor nie zna konkretnych typów powiadomień. Wie tylko, że mogą one wywołać Send. To podstawa, na której opierają się wzorce takie jak Observer i Strategy!
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Odczytaj liczbę obiektów powiadamiających
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Utwórz nowy dyspozytor
dispatcher := NewNotificationDispatcher()
// Odczytaj każdy obiekt powiadamiający i dodaj go do dyspozytora
for i := 0; i < count; i++ {
notifierType, _ := reader.ReadString('\n')
notifierType = strings.TrimSpace(notifierType)
destination, _ := reader.ReadString('\n')
destination = strings.TrimSpace(destination)
// TODO: Utwórz odpowiedni obiekt powiadamiający na podstawie typu
// i dodaj go do dyspozytora
// Wskazówka: Sprawdź, czy notifierType to "email" czy "sms"
}
// Odczytaj wiadomość do rozesłania
message, _ := reader.ReadString('\n')
message = strings.TrimSpace(message)
// TODO: Roześlij wiadomość i wypisz każde potwierdzenie
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja13Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec Abstract FactoryWzorzec ObserverWzorzec Strategy2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online