💬

Functional Option Patterns

2023/11/14に公開

Functional Option パターン

はじめまして!エンジニアのビクトルと申します。

この技術がどのようにしてオブジェクトファクトリーの作成について説明しましょー。

オブジェクトをN個のパラメータを使用して作成するのに苦労していますか?もし1つ以上のパラメータを追加する場合や必要がない場合はどうなるのでしょうか?ここで、Functional Optionパターンのパワーです!

コード書きましょ

例えば、あるコードで。。。

type person struct {
  name, surName, middleName string
}

func NewPerson(name, surName, middleName string) *person {
  return &person{
    name:       name,
    surName:    surName,
    middleName: middleName,
  }
}

国によって、人々はミドルネームを持っている可能性があります。その場合、NewPerson()関数を使用する際に、middleNameパラメータには空文字を入り、少し読みにくいになっています。オブジェクトの作成に必要なパラメータのみを使用する方法はどのように実現できるでしょうか?また、構造体に新しいプロパティを追加したい場合はどうなるのでしょうか?

パターン使いましょ!

2つの方法で書くことができます:

  • Applied functions
  • Normal functions

Applied functions

type person struct {
  name, surName, middleName string
}

type Option interface {
  apply(*person)
}

type (
  nameOption       string
  surNameOption    string
  middleNameOption string
)

func (n nameOption) apply(p *person) {
  p.name = string(n)
}

func (n surNameOption) apply(p *person) {
  p.surName = string(n)
}

func (n middleNameOption) apply(p *person) {
  p.middleName = string(n)
}

func WithName(name string) PersonOption {
  return nameOption(name)
}

func WithSurName(surName string) PersonOption {
  return surNameOption(surName)
}

func WithMiddleName(name string) PersonOption {
  return middleNameOption(name)
}

func defaultPerson() *person {
  return &person{
    name: "anonymous",
  }
}

func NewPerson(pOpts ...PersonOption) Person {
  defOpts := defaultPerson()

  for _, opt := range pOpts {
    opt.apply(defOpts)
  }
  return defOpts
}

Normal functions

type (
  person struct {
    name       string
    surName    string
    middleName string
  }

  Option func(*person)
)

func WithName(name string) Option {
  return func(parameter *person) {
    parameter.name = name
  }
}

func WithSurName(surName string) Option {
  return func(parameter *person) {
    parameter.surName = surName
  }
}

func WithMiddleName(surName string) Option {
  return func(parameter *person) {
    parameter.surName = surName
  }
}

func defaultPerson() *person {
  return &person{
    name: "anonymous",
  }
}

func NewPerson(opts ...Option) *person {
  fn := defaultPerson()

  for _, opt := range opts {
    opt(fn)
  }

  return fn
}

使い方

func main() {
  person1 := NewPerson(
    WithName("victor"), 
    WithSurName("massuru"),
    WithMiddleName("kinniku"),
  )
}

そして、必要なパラメータを追加して使用することができます。

でも。。。こいう書き方長くて。。。自動化できないのでしょうか?

。。。あります!

https://github.com/rikeda71/foggo

Happy Hacking!

Related doc:

Discussion