Go语言中的装饰器函数

huangapple go评论93阅读模式
英文:

Decorator functions in Go

问题

装饰器模式(函数)具有许多好处,特别适用于方法具有许多正交关注点的情况。这些关注点之间没有关联,除了我们在调用方法时希望执行所有(或部分)关注点。这就是装饰器模式的真正帮助之处。

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

然而,我找到的所有示例都非常复杂(例如,使用许多中间件编写HTTP服务器)。这使得我很难将这个原则应用到其他地方。我需要一个简单的示例来更好地理解它。

有人能给我一个更简单的示例,以最好地说明如何在Go中使用装饰器模式(函数)吗?

这个由Alex Alehano提供的示例太简单了,无法实际使用。我需要一个能够说明这个问题的示例:

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

根据不同的选项/指令进行字符串操作,例如转为大写、转为小写、转为Base64等,这是我认为最好的示例。还可以添加前缀/后缀,就像“如果装饰器本身是带参数的,则这种技术尤其有价值”一样。

英文:

Decorator pattern (functions) has many benefits:

> It is very useful when a method has many orthogonal concerns... I.e., None of these concerns are related, other than that we wanna do all (or some) of them whenever we call our method. This is where the decorator pattern really helps.
>
> By implementing the decorator pattern we subscribe to the open-closed principal. Our method is open to future extension but closed to future modification. There's a lot of groovy benefits to obeying the open-closed principle.

However, all the examples that I found are really complicated (e.g., writing HTTP servers with many middlewares). This make it difficult for me to apply the principle elsewhere. I need something that I can easily try on so as to wrap my head around it.

Can someone give me an simpler example that can best illustrate how to do Decorator pattern (functions) in Go please?

This example by Alex Alehano, is too simple to be put into practical use. I need something that can illustrate this:

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

A string manipulation according to different option/instruction, e.g., to upper, to lower, to base64, etc, would be the best example IMO, and adding prefix/suffix too, as "This technique proves especially valuable if the decorators themselves are parameterized".

答案1

得分: 25

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

// 这是您想要装饰的函数类型
type StringManipulator func(string) string

// 这是您的装饰器。
func ToLower(m StringManipulator) StringManipulator {
return func(s string) string {
lower := strings.ToLower(s)
return m(lower)
}
}

这里有一个更完整的示例

更新:

这里有一个修订后的示例,fn3和fn4的应用顺序相同

英文:

First of all, a decorator is basically a function that takes another function of a specific type as its argument and returns a function of the a same type. This essentially allows you to create a chain of functions. So in Go it would look something like this:

// 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)
    }
}

here's a more complete example

UPDATE:

and here's a revised example that the applying order of fn3 and fn4 are the same

答案2

得分: 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, ")}...)
英文:

> A string manipulation according to different option/instruction, e.g., to upper, to lower, adding prefix/suffix, to base64, etc, would be the best example IMO.

This is the example you asked about:

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

Note that for the sake of simplicity, it uses Golang's type aliases, which were introduced in Go 1.9. These are the two aliases we are using:

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

so that your Decorate() function could later be applied. Then we are simply creating few decorators (functions) which match the type signature we defined:

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

and apply them:

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

EDIT:

A parametrized decorator would simply be a function which returns another function, for example:

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

It could then be applied in the following way:

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

答案3

得分: 4

我知道一个非常好的装饰器/中间件的例子,它使用了你展示的相同技术。虽然这个例子不特定于字符串操作,但我觉得它非常精心设计。你可以在这里查看代码:

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
	}
	// 设置合理的默认值
	pc := &publisherConfig{
		syncPublisher: defaultSyncPublisher,
		ackMode:       defaultRequiredAcks,
		successes:     nil,
		logger:        log.NewNopLogger(),
		topic:         topic,
		partitioner:   sarama.NewManualPartitioner(topic),
		partition:     0,
	}

	// 解析可选参数
	for _, option := range options {
		if err := option(pc); err != nil {
			return nil, err
		}
	}

请注意它遍历options并调用每个装饰器/中间件。

这里还有一个补充的例子:https://gist.github.com/steven-ferrer/e4c1eb973a930c2205039448cda6d3d9

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

希望对你有帮助 Go语言中的装饰器函数

英文:

I know a very good example of decorators/middlewares that utilizes the same technique that you demonstrated. This is not specific to string manipulation though, but I find it really beautifully crafted, here it is (scan through the code):

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

Take a look at the type PublisherOption func(*publisherConfig) error

You will notice that some of the functions returns PublisherOption type. These are the decorators/middleware.

The action happens on NewKafkaPublisher function:

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
	 }
 }

Notice that it loops through the option and calls each decorator/middleware.

Supplemental example is here: https://gist.github.com/steven-ferrer/e4c1eb973a930c2205039448cda6d3d9

Runnable code: https://play.golang.org/p/ZXixnyTHXH

Hope that helped Go语言中的装饰器函数

huangapple
  • 本文由 发表于 2017年8月30日 01:14:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/45944781.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定