Цепочка ответственности — как вызвать определенный обработчик при сбое текущего

#go #chain-of-responsibility

#Вперед #цепочка ответственности

Вопрос:

Я собираюсь использовать шаблон цепочки ответственности для простого диалога CLI:

 type Handler interface {
    Request(flag bool)
}

type AskName struct {
    next Handler
}

func (h *AskName) Request(flag bool) {
    fmt.Println("AskName.Request()")
    if flag {
        h.next.Request(flag)
    }
}

type AskAge struct {
    next Handler
}

func (h *AskAge) Request(flag bool) {
    fmt.Println("AskAge.Request()")
    if flag {
        h.next.Request(flag)
    }
}

type AskEmail struct {
    next Handler
}

func (h *AskEmail) Request(flag bool) {
    fmt.Println("AskEmail.Request()")
}

func main() {
    handlerA := amp;AskName{amp;AskAge{new(AskEmail)}}
    handlerA.Request(true)
}
  

Вопрос: представьте ситуацию, когда пользователь ввел неверное электронное письмо. Как я могу повторно вызвать AskEmail обработчик (или вообще вызвать другой обработчик)?

Например, если я разделю логику следующим образом:

 type AskEmail struct {
    next Handler
}

func (h *AskEmail) Request(flag bool) {
    fmt.Println("AskEmail.Request()")
    if flag {
        h.next.Request(flag)
    }
}

type ValidateEmail struct {
    next Handler
}

func (h *ValidateEmail) Request(flag bool) {
    fmt.Println("ValidateEmail.Request()")
}
  

как я могу вызвать AskEmail из ValidateEmail , если ValidateEmail произойдет сбой?

Ответ №1:

Я думаю, вы можете сделать это

 type AskEmail struct {
    next Handler
}

func (h *AskEmail) Request(flag bool) {
    fmt.Println("AskEmail.Request()")
    if flag {
        h.next.Request(flag)
    }
}

type ValidateEmail struct {
    next Handler
    prev Handler
}

func (h *ValidateEmail) Request(flag bool) {
    fmt.Println("ValidateEmail.Request()")
    if !valid {
       h.prev.Request(flag)
    }
}

askEmail := amp;AskEmail{}
validateEmail := amp;ValidateEmail{prev: askEmail}
askEmail.next = validateEmail

  

Ответ №2:

представьте ситуацию, когда пользователь ввел неверное электронное письмо. Как я могу повторно вызвать AskEmail обработчик (или вообще вызвать другой обработчик)?

На мой взгляд, AskEmail «ссылка» в цепочке не должна вызываться, next если только она не получила обратно действительный адрес электронной почты. Каждая «ссылка» должна вызываться только next в том случае, если была обработана ее собственная ответственность — если вы посмотрите на оба https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern#UML_class_and_sequence_diagram и https://refactoring.guru/design-patterns/chain-of-responsibility вы увидите, что управление перемещается только в одном направлении.

Пример на https://github.com/yksz/go-design-patterns/blob/master/behavior/chain_of_responsibility.go это немного упрощенно

Если вы будете следовать этой идее, ваш код станет чем-то вроде этого:

 func main() {
    // Build a chain of steps to take
    c := AskEmail{
        next: AskName{
            next: Print{},
        },
    }

    //  Run the chain with an empty "Person"
    c.Run(amp;Person{})
}

// Person holds the data that is relevant to your application. It should have a name that makes sense for your domain
type Person struct {
    Email string
    Name string
}

// PersonInfoChainLink is a link in the chain of command
type PersonInfoChainLink interface {
    Run(p *Person) (error)
}

// AskEmail can ask for an e-mail address
type AskEmail struct {
    next PersonInfoChainLink
}

func (a AskEmail) Run(p *Person) (error) {

    // Ask for e-mail addresses until the user gives a valid one
    var err error
    var email string
    for {
        email, err = askString(`What is your e-mail address?`)
        if err != nil {
            return err
        }

        if strings.Contains(email, `@`) {
            break
        }

        fmt.Printf("Invalid e-mail address %s!", email)

    }
    p.Email = email

    return a.next.Run(p)
}

// AskName can ask for the name of a person
type AskName struct {
    next PersonInfoChainLink
}

func (a AskName) Run(p *Person) (error) {
    name, err := askString(`What is your name?`)
    if err != nil {
        return err
    }
    p.Name = name
    return a.next.Run(p)
}

// Print can print the information about a person
type Print struct {

}

func (Print) Run(p *Person) (error) {
    log.Printf(`Email %s at %s!`, p.Name, p.Email)
    return nil
}

func askString(question string) (string, error) {
    reader := bufio.NewReader(os.Stdin)
    fmt.Print(question   ` `)
    return reader.ReadString('n')
}