Menu
Coddy logo textTech

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-data

Wzorzec 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.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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 ReportGenerator z trzema metodami odpowiadającymi etapom generowania raportu:

    • GatherData() string: pobiera surowe dane do raportu
    • FormatData(data string) string: przekształca dane do formatu raportu
    • OutputReport(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:

    • SalesReport z polem Region (string)
      • GatherData() zwraca sales-data-[region]
      • FormatData(data) zwraca SALES REPORT: [data]
      • OutputReport(formatted) zwraca Printed: [formatted]
    • InventoryReport z polem Warehouse (string)
      • GatherData() zwraca inventory-[warehouse]
      • FormatData(data) zwraca *** [data] ***
      • OutputReport(formatted) zwraca Exported: [formatted]
  • main.go: Pokaż, jak ta sama struktura algorytmu daje różne wyniki zależnie od implementacji.

    Wczytaj typ raportu (sales lub inventory) oraz wartość konfiguracji (region dla raportu sprzedaży, magazyn dla raportu stanu magazynowego). Utwórz odpowiedni generator, opakuj go w ReportProcessor, wywołaj Generate() i wypisz wynik.

Dane wejściowe będą następujące:

  • Wiersz 1: typ raportu (sales lub inventory)
  • Wiersz 2: wartość konfiguracji (nazwa regionu lub nazwa magazynu)

Na przykład dla danych:

sales
North

Wynik powinien być następujący:

Printed: SALES REPORT: sales-data-North

A dla danych:

inventory
MainHub

Wynik powinien być następujący:

Exported: *** inventory-MainHub ***

A dla danych:

sales
West

Wynik powinien być następujący:

Printed: SALES REPORT: sales-data-West

Zwróć 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")
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online