Go 中的装饰器函数

问题描述 投票:0回答:3

装饰器模式(函数)有很多好处

当一个方法有许多正交关注点时,它非常有用......即,这些关注点都不相关,除了我们在调用我们的方法时想要执行所有(或部分)它们之外。这就是装饰器模式真正有用的地方。

通过实现装饰器模式,我们遵守开闭原则。我们的方法对未来的扩展开放,但对未来的修改封闭。遵守开闭原则有很多好处。

但是,我发现的所有示例都非常复杂(例如,使用许多中间件编写 HTTP 服务器)。这使得我很难将该原则应用到其他地方。我需要一些我可以轻松尝试的东西,以便我能专注于它。

有人可以给我一个更简单的例子来最好地说明如何在 Go 中实现装饰器模式(函数)吗?

Alex Alehano的这个例子太简单了,无法投入实际使用。我需要一些可以说明这一点的东西:

func Decorate(c Decorated, ds ...Decorator) Decorated {
    decorated := c
    for _, decorate := range ds {
        decorated = decorate(decorated)
    }
    return decorated
}

根据不同的选项/指令进行字符串操作,例如,上、下、base64等,在我看来是最好的例子,并且也添加前缀/后缀,如“如果装饰器,这种技术被证明特别有价值它们本身被参数化了”。

go design-patterns decorator demo
3个回答
32
投票

首先,装饰器基本上是一个函数,它接受另一个特定类型的函数作为其参数并返回相同类型的函数。这本质上允许您创建一系列函数。所以在 Go 中它看起来像这样:

// this is the type of functions you want to decorate
type StringManipulator func(string) string

// this is your decorator.
func ToLower(m StringManipulator) StringManipulator {
    return func(s string) string {
        lower := strings.ToLower(s)
        return m(lower)
    }
}

这是一个更完整的示例

更新:

这是一个修改后的例子,fn3和fn4的应用顺序是相同的


7
投票

根据不同的选项/指令进行字符串操作,例如,向上、向下、添加前缀/后缀、base64 等,将是我认为最好的例子。

这是您询问的示例:

package main

import (
    "fmt"
    "strings"
)

const (
    prefix = "PREFIX"
    suffix = "SUFFIX"
)

type Decorated=string

func addConstPrefix(s string) string {
    return prefix + s
}

func addConstSuffix(s string) string {
    return s + suffix
}

type Decorator=func(string) string

func Decorate(c Decorated, ds ...Decorator) Decorated {
    decorated := c
    for _, decorator := range ds {
        decorated = decorator(decorated)
    }
    return decorated
}

func main() {

    var toLower Decorator = strings.ToLower
    var toUpper Decorator = strings.ToUpper
    var dec3 Decorator = addConstPrefix
    var dec4 Decorator = addConstSuffix

    input := "Let's decorate me!"
    inputUppercase := Decorate(input, []Decorator{toUpper}...)
    fmt.Println(inputUppercase)

    allDecorators := []Decorator{toUpper, toLower, dec3, dec4}
    output := Decorate(input, allDecorators...)
    fmt.Println(output)
}

请注意,为了简单起见,它使用 Golang 的 类型别名,这是在 Go 1.9 中引入的。这是我们正在使用的两个别名:

type Decorated=string
type Decorator=func(string)string

以便稍后可以应用您的

Decorate()
功能。然后我们只需创建一些与我们定义的类型签名匹配的装饰器(函数):

var toLower Decorator = strings.ToLower
var toUpper Decorator = strings.ToUpper
var dec3 Decorator = addConstPrefix
var dec4 Decorator = addConstSuffix

并应用它们:

allDecorators := []Decorator{toUpper, toLower, dec3, dec4}
output := Decorate(input, allDecorators...)

编辑:

一个参数化装饰器只是一个返回另一个函数的函数,例如:

func addPrefix(prefix string) func(string) string {
    return func(s string) string {
        return prefix + s
    }
}

然后可以通过以下方式应用:

input2 := "Let's decorate me!"
prefixed := Decorate(input2, []Decorator{addPrefix("Well, ")}...)

5
投票

我知道一个非常好的装饰器/中间件示例,它使用了与您演示的相同的技术。虽然这并不是特定于字符串操作,但我发现它制作得非常精美,就是这样(扫描代码):

https://github.com/basvanbeek/pubsub/blob/master/kafka/publisher.go

看看

type PublisherOption func(*publisherConfig) error

您会注意到某些函数返回

PublisherOption
类型。这些是装饰器/中间件。

该操作发生在

NewKafkaPublisher
函数上:

func NewKafkaPublisher(
  broker, topic string,
  options ...PublisherOption,
  ) (pubsub.Publisher, error) {
  if len(strings.Trim(broker, " \t")) == 0 {
      return nil, ErrNoBrokers
  }
  brokerHosts := strings.Split(broker, ",")

  if len(topic) == 0 {
      return nil, ErrNoTopic
  }
  // set sensible defaults
  pc := &publisherConfig{
    syncPublisher: defaultSyncPublisher,
    ackMode:       defaultRequiredAcks,
    successes:     nil,
    logger:        log.NewNopLogger(),
    topic:         topic,
    partitioner:   sarama.NewManualPartitioner(topic),
    partition:     0,
  }

 // parse optional parameters
 for _, option := range options {
     if err := option(pc); err != nil {
        return nil, err
     }
 }

请注意,它循环遍历

option
并调用每个装饰器/中间件。

补充示例在这里:https://gist.github.com/stevenferrer/e4c1eb973a930c2205039448cda6d3d9

可运行代码:https://play.golang.org/p/ZXixnyTHXH

希望有帮助:)

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.