#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')
}