1. Co to jest wzorzec łańcucha odpowiedzialności
Wzorzec łańcucha odpowiedzialności to wzorzec behawioralny, który odłącza nadawcę i odbiorcę żądania, umożliwiając wielu obiektom szansę na obsłużenie żądania. Każdy odbiorca zawiera odniesienie do innego odbiorcy, a jeśli nie może obsłużyć żądania, przekazuje je do następnego odbiorcy, dopóki żądanie nie zostanie obsłużone lub nie dotrze do końca łańcucha.
2. Charakterystyka i zalety wzorca łańcucha odpowiedzialności
Charakterystyka i zalety wzorca łańcucha odpowiedzialności są następujące:
- Odłączenie nadawcy i odbiorcy: Nadawca nie musi martwić się, który odbiorca obsługuje żądanie, ani nie musi znać konkretnych obsługujących w łańcuchu.
- Elastyczność: Umożliwia dynamiczne dodawanie, usuwanie lub zmienianie kolejności obsługujących w łańcuchu bez modyfikowania kodu nadawcy i odbiorcy.
- Rozszerzalność: Łatwe rozszerzanie łańcucha odpowiedzialności poprzez dodawanie nowych konkretnych obsługujących.
- Zasada pojedynczej odpowiedzialności: Każdy konkretny obsługujący musi troszczyć się tylko o swoją własną logikę obsługi.
- Konfigurowalność: Łańcuch obsługujących może być konfigurowany według potrzeb, umożliwiając różnym żądaniom posiadanie różnych łańcuchów obsługujących.
3. Przykłady praktycznych zastosowań wzorca łańcucha odpowiedzialności
Wzorzec łańcucha odpowiedzialności ma wiele praktycznych zastosowań, takich jak:
- Obsługa żądań w aplikacjach internetowych: Może być używany do obsługi różnych typów żądań, takich jak uwierzytelnianie tożsamości, logowanie i weryfikacja uprawnień.
- Obsługa błędów: Może być używany do obsługi błędów, gdzie każdy obsługujący jest odpowiedzialny za obsługę konkretnego typu błędu i przekazywanie błędu do następnego obsługującego w miarę potrzeb.
- Obsługa zdarzeń: Może być używany do obsługi różnych typów zdarzeń, takich jak zdarzenia kliknięcia użytkownika, zdarzenia żądania sieciowego i inne.
4. Implementacja wzorca łańcucha odpowiedzialności w języku Golang
4.1 Diagram klas UML
4.2 Przykład wprowadzenia
Na powyższym diagramie klas UML zdefiniowano abstrakcyjny obsługujący (Handler) i dwa konkretne obsługujące (ConcreteHandler1 i ConcreteHandler2). Klient (Client) inicjuje żądania, wywołując metodę handleRequest obsługującego.
4.3 Krok 1 implementacji: Zdefiniowanie interfejsu abstrakcyjnego obsługującego
type Handler interface {
HandleRequest(request Request) error
SetNext(handler Handler)
}
type Request interface {
Condition bool
}
Interfejs abstrakcyjnego obsługującego definiuje metodę HandleRequest do przetwarzania żądań i metodę SetNext do ustawiania kolejnego obsługującego.
4.4 Krok 2 implementacji: Implementacja klas konkretnych obsługujących
type ConcreteHandler1 struct {
next Handler
}
func (h *ConcreteHandler1) HandleRequest(request Request) error {
// Logika obsługi żądania
if request.Condition {
// Kod obsługi żądania
return nil
} else {
if h.next != nil {
return h.next.HandleRequest(request)
}
return errors.New("Nie znaleziono obsługującego")
}
}
func (h *ConcreteHandler1) SetNext(handler Handler) {
h.next = handler
}
type ConcreteHandler2 struct {
next Handler
}
func (h *ConcreteHandler2) HandleRequest(request Request) error {
// Logika obsługi żądania
if request.Condition {
// Kod obsługi żądania
return nil
} else {
if h.next != nil {
return h.next.HandleRequest(request)
}
return errors.New("Nie znaleziono obsługującego")
}
}
func (h *ConcreteHandler2) SetNext(handler Handler) {
h.next = handler
}
Klasy konkretnych obsługujących implementują interfejs abstrakcyjnego obsługującego i przesłaniają metody HandleRequest i SetNext.
4.5 Krok 3 implementacji: Budowanie łańcucha odpowiedzialności
handler1 := &ConcreteHandler1{}
handler2 := &ConcreteHandler2{}
handler1.SetNext(handler2)
Poprzez instancjonowanie konkretnych obsługujących i ustawianie kolejnego obsługującego, budowany jest łańcuch odpowiedzialności.
4.6 Krok 4 implementacji: Kod klienta
func main() {
handler := &ConcreteHandler1{}
// Budowanie łańcucha odpowiedzialności
handler.SetNext(&ConcreteHandler2{})
// Wysłanie żądania
handler.HandleRequest(Request{Condition: true})
}
W kodzie klienta instancjonowany jest konkretny obsługujący, ustawiany jest kolejny obsługujący, a następnie wywoływana jest metoda HandleRequest w celu wysłania żądania.