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

Golang Chain of Responsibility Pattern

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.