Wzorzec metody szablonowej
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 94 z 107.
Wzorzec metody szablonowej definiuje szkielet algorytmu w typie bazowym, umożliwiając podtypom nadpisywanie określonych kroków bez zmiany ogólnej struktury. Podczas gdy Decorator dodaje zachowanie przez opakowywanie obiektów, metoda szablonowa kontroluje przebieg algorytmu i umożliwia dostosowywanie poszczególnych kroków.
W Go, ponieważ nie ma dziedziczenia, implementujemy ten wzorzec za pomocą osadzania struktur połączonego z interfejsami. Struktura „szablonu” definiuje strukturę algorytmu i wywołuje metody, które można dostosować:
type DataProcessor interface {
ReadData() string
ProcessData(data string) string
SaveData(result string) string
}
type BaseProcessor struct {
Impl DataProcessor
}
func (b *BaseProcessor) Execute() string {
data := b.Impl.ReadData()
result := b.Impl.ProcessData(data)
return b.Impl.SaveData(result)
}Konkretne implementacje udostępniają własne wersje każdego kroku, podczas gdy przebieg algorytmu pozostaje niezmieniony:
type CSVProcessor struct{}
func (c CSVProcessor) ReadData() string { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string { return "Saved: " + r }
type JSONProcessor struct{}
func (j JSONProcessor) ReadData() string { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string { return "Stored: " + r }Metoda szablonowa Execute koordynuje kroki w ustalonej kolejności:
csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute()) // Zapisano: processed-csv-data
jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Zapisano: parsed-json-dataWzorzec Template Method sprawdza się idealnie, gdy masz algorytmy o tej samej strukturze, które różnią się konkretnymi krokami, takie jak potoki importu/eksportu danych, generatory raportów lub frameworki testowe.
Wyzwanie
ŁatwyZbudujmy system generowania raportów z użyciem wzorca Template Method! Utworzysz framework, w którym różne typy raportów przechodzą przez ten sam proces generowania: pobierają dane, formatują je i tworzą wynik, ale każdy typ raportu dostosowuje te kroki na swój sposób.
Rozmieścisz kod w trzech plikach:
report.go: Zdefiniuj interfejs i bazowy procesor, który koordynuje algorytm generowania raportu.Utwórz interfejs
ReportGeneratorz trzema metodami odpowiadającymi etapom generowania raportu:GatherData() string: pobiera surowe dane do raportuFormatData(data string) string: przekształca dane do formatu raportuOutputReport(formatted string) string: tworzy końcowy komunikat wyjściowy
Zbuduj strukturę
ReportProcessor, która przechowuje implementacjęReportGenerator. Dodaj metodęGenerate() string, która wykonuje trzy kroki w podanej kolejności: pobiera dane, formatuje je, a następnie tworzy wynik: zwracając końcowy rezultat.generators.go: Zaimplementuj konkretne generatory raportów, które dostosowują każdy krok.Utwórz dwa typy raportów:
SalesReportz polemRegion(string)GatherData()zwracasales-data-[region]FormatData(data)zwracaSALES REPORT: [data]OutputReport(formatted)zwracaPrinted: [formatted]
InventoryReportz polemWarehouse(string)GatherData()zwracainventory-[warehouse]FormatData(data)zwraca*** [data] ***OutputReport(formatted)zwracaExported: [formatted]
main.go: Pokaż, jak ta sama struktura algorytmu daje różne wyniki zależnie od implementacji.Wczytaj typ raportu (
saleslubinventory) oraz wartość konfiguracji (region dla raportu sprzedaży, magazyn dla raportu stanu magazynowego). Utwórz odpowiedni generator, opakuj go wReportProcessor, wywołajGenerate()i wypisz wynik.
Dane wejściowe będą następujące:
- Wiersz 1: typ raportu (
saleslubinventory) - Wiersz 2: wartość konfiguracji (nazwa regionu lub nazwa magazynu)
Na przykład dla danych:
sales
NorthWynik powinien być następujący:
Printed: SALES REPORT: sales-data-NorthA dla danych:
inventory
MainHubWynik powinien być następujący:
Exported: *** inventory-MainHub ***A dla danych:
sales
WestWynik powinien być następujący:
Printed: SALES REPORT: sales-data-WestZwróć uwagę, że ReportProcessor zawsze wywołuje te same trzy kroki w tej samej kolejności, ale każdy typ raportu dostarcza własną implementację tych kroków. Szkielet algorytmu pozostaje niezmienny, a szczegóły się różnią: tak działa wzorzec Template Method!
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Odczytaj typ raportu
scanner.Scan()
reportType := scanner.Text()
// Odczytaj wartość konfiguracji (region lub magazyn)
scanner.Scan()
configValue := scanner.Text()
// TODO: Utwórz odpowiedni generator na podstawie reportType
// - Jeśli reportType to "sales", utwórz SalesReport z Region ustawionym na configValue
// - Jeśli reportType to "inventory", utwórz InventoryReport z Warehouse ustawionym na configValue
// TODO: Utwórz ReportProcessor z generatorem
// TODO: Wywołaj Generate() i wypisz wynik
_ = reportType
_ = configValue
fmt.Println("TODO: Generate and print the report")
}
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 kolekcja2Typy 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ów11Biblioteka standardowa i OOP
io.Reader i io.Writersort.InterfaceInterfejs fmt.Stringerencoding/json ze strukturamiInterfejs http.HandlerPowtórzenie – modele REST API14Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytuMiddleware jako dekorator3Wskaź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