Maison >développement back-end >Golang >Un modèle de programmation actif dans de nombreux projets Go
Supposons que nous définissions un objet de structure utilisateur User dans le code, qui a les propriétés suivantes.
type User struct { ID string // 必需项 Name string // 必需项 Age int // 非必需项 Gender bool // 非必需项 }
Lors de l'initialisation de l'objet, le plus simple est de renseigner directement les valeurs des attributs, comme
u := &User{ID: "12glkui234d", Name: "菜刀", Age: 18, Gender: true}
Mais il y a un problème : les attributs de l'objet User ne sont pas forcément exportables. Par exemple, User. a un Le champ d'attribut est le mot de passe (la première lettre est minuscule, non exportée). Si l'objet Utilisateur doit être construit dans d'autres modules, le champ du mot de passe ne peut pas être rempli.
Nous devons donc définir une fonction pour construire l'objet User. La méthode de constructeur la plus simple à laquelle on puisse penser est la suivante.
func NewUser(id, name string, age int, gender bool) *User { return &User{ ID: id, Name: name, Age: age, Gender: gender, } }
Mais il y a aussi quelques problèmes : pour l'objet Utilisateur, seuls les attributs ID et Nom sont requis, Age et Gender sont facultatifs et la valeur par défaut ne peut pas être définie. Par exemple, la valeur par défaut de Age est 0. . La valeur par défaut de Gender est false, ce qui est évidemment déraisonnable.
Face à ce problème, quelles sont les solutions que nous pouvons adopter ?
La solution la plus grossière à laquelle nous pouvons penser est de définir un constructeur pour chaque situation de paramètre. Comme indiqué dans le code suivant
func NewUser(id, name string) *User { return &User{ID: id, Name: name} } func NewUserWithAge(id, name string, age int) *User { return &User{ID: id, Name: name, Age: age} } func NewUserWithGender(id, name string, gender bool) *User { return &User{ID: id, Name: name, Gender: gender} } func NewUserWithAgeGender(id, name string, age int, gender bool) *User { return &User{ID: id, Name: name, Age: age, Gender: gender} }
这种方式适合参数较少且不易发生变化的情况。该方式在 Go 标准库中也有使用,例如 net 包中的 Dial 和 DialTimeout 方法。
func Dial(network, address string) (Conn, error) {} func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {}
但该方式的缺陷也很明显:试想,如果构造对象 User 增加了参数字段 Phone,那么我们需要新增多少个组合函数?
另外一种常见的方式是配置化,我们将所有可选的参数放入一个 Config 的配置结构体中。
type User struct { ID string Name string Cfg *Config } type Config struct { Age int Gender bool } func NewUser(id, name string, cfg *Config) *User { return &User{ID: id, Name: name, Cfg: cfg} }
这样,我们只需要一个 NewUser() 函数,不管之后增加多少配置选项,NewUser 函数都不会得到破坏。
但是,这种方式,我们需要先构造 Config 对象,这时候对 Config 的构造又回到了方案一中存在的问题。
面对这样的问题,我们还可以选择函数式选项模式。
首先,我们定义一个 Option 函数类型
type Option func(*User)
然后,为每个属性值定义一个返回 Option 函数的函数
func WithAge(age int) Option { return func(u *User) { u.Age = age } } func WithGender(gender bool) Option { return func(u *User) { u.Gender = gender } }
此时,我们将 User 对象的构造函数改为如下所示
func NewUser(id, name string, options ...Option) *User { u := &User{ID: id, Name: name} for _, option := range options { option(u) } return u }
按照这种构造方式,我们就可以这样配置 User 对象了
u := NewUser("12glkui234d", "菜刀", WithAge(18), WithGender(true))
以后不管 User 增加任何参数 XXX,我们只需要增加对应的 WithXXX 函数即可,是不是非常地优雅?
Functional Options 这种编程模式,我们经常能在各种项目中找到它的身影。例如,我在 tidb 项目中仅使用 opts ... 关键字搜索,就能看到这么多使用了 Functional Options 的代码(截图还未包括全部)。
Le modèle d'option fonctionnelle résout le problème de la configuration dynamique et flexible des paramètres des objets, mais il doit être utilisé dans des scénarios appropriés.
Lorsque les paramètres de configuration de l'objet sont complexes, comme de nombreux paramètres optionnels, des champs non importés, des paramètres pouvant augmenter avec les versions, etc., alors le mode option fonctionnel peut très bien nous aider.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!