Modelul Options în Go (*Options pattern)

  • 2 minute de citit
  • 6 exemple de cod în Go

Disponibil și în English

Pe această pagină

Introducere

Salut tuturor! 👋

În acest articol vă prezint modelul options din Golang. Modelul este util atunci când vrei să creezi o funcție care primește diferiți parametri sub formă de opțiuni.

Studiu de cod: construirea unei rachete

Codul următor definește o structură Rocket cu 3 câmpuri și o funcție NewRocket care construiește o instanță a Rocket.

// Rocket modelează o rachetă.type Rocket struct {	name         string	nose         Nose	fuelCapacity int}// NewRocket returnează o nouă instanță de rachetă.func NewRocket(name string, nose Nose, fuelCapacity int) Rocket {	return Rocket{		name:         name,		nose:         nose,		fuelCapacity: fuelCapacity,	}}

Pentru a folosi funcția NewRocket, ar trebui să scriem ceva de genul acesta:

func main() {	rocket := NewRocket("Techno", PointyNose, 100)	println(rocket.String())}

Este simplu, dar ce se întâmplă dacă am dori ca numele rachetei, vârful și capacitatea de combustibil să fie parametri opționali?

Am putea rescrie funcția NewRocket să primească pointeri în loc de valori și, dacă facem asta, am putea construi o rachetă implicită cu:

func main() {	rocket := NewRocket(nil, nil, nil)	println(rocket.String())}

Acest lucru nu este, desigur, foarte lizibil; ochii mi se umplu de lacrimi doar privind acest cod.

Când vrem să modificăm structura Rocket și să adăugăm un câmp nou, care este și el inițializat, ar trebui să ne amintim poziția din cod și să modificăm toate apelurile funcției NewRocket.

Până acum, nefolosirea modelului options pentru instanțierea unei Rocket are următoarele dezavantaje:

  • Cod urât.
  • Cod greu de modificat.
  • Trebuie refactorizată fiecare utilizare a funcției.

Modelul Options

Modelul Options este simplu de implementat și există multe variante; în acest articol vom explora o variantă simplă:

Mai întâi, definim structura RocketOptions, care are aceleași câmpuri configurabile ca structura Rocket și definește funcții pentru setarea lor.

Câmpurile sunt definite ca pointeri în loc de valori, deoarece vrem să tratăm valoarea nil ca fiind nespecificată de utilizator.

func NewRocketOptions() *RocketOptions {	return &RocketOptions{		name:         nil,		nose:         nil,		fuelCapacity: nil,	}}func (o *RocketOptions) WithName(name string) *RocketOptions {	o.name = &name	return o}func (o *RocketOptions) WithNose(nose Nose) *RocketOptions {	o.nose = &nose	return o}func (o *RocketOptions) WithFuelCapacity(capacity int) *RocketOptions {	o.fuelCapacity = &capacity	return o}

Apoi, modificăm funcția NewRocket să folosească structura RocketOptions astfel:

// NewRocket returnează o nouă instanță de rachetă.func NewRocket(options *RocketOptions) Rocket {	var name string = "Unknown"	if options.name != nil {		name = *options.name	}	var nose Nose = PointyNose	if options.nose != nil {		nose = *options.nose	}	var fuelCapacity int = 100	if options.fuelCapacity != nil {		fuelCapacity = *options.fuelCapacity	}	return Rocket{		name:         name,		nose:         nose,		fuelCapacity: fuelCapacity,	}}

Acum, NewRocket oferă valori implicite pentru opțiunile nil și, atunci când sunt specificate, folosește valoarea furnizată de utilizator.

Altă variantă

O altă variantă poate fi implementată astfel:

type Nose stringconst PointyNose Nose = "POINTY"const RoundNose Nose = "ROUND"type rocketOptions struct {	name         string	nose         Nose	fuelCapacity int}type RocketOption func(options *rocketOptions)// Rocket modelează o rachetă.type Rocket struct {	options rocketOptions}func (r *Rocket) String() string {	return fmt.Sprintf("%s rocket with %s nose and fuel capacity of %d", r.options.name, r.options.nose, r.options.fuelCapacity)}// NewRocket returnează o nouă instanță de rachetă.func NewRocket(options ...RocketOption) Rocket {	rocketOptions := rocketOptions{		name:         "Unknown",		nose:         PointyNose,		fuelCapacity: 100,	}	for _, option := range options {		option(&rocketOptions)	}	return Rocket{		options: rocketOptions,	}}func NameOption(name string) RocketOption {	return func(options *rocketOptions) {		options.name = name	}}func main() {	rocket := NewRocket()	println(rocket.String())	otherRocket := NewRocket(NameOption("Cool"))	println(otherRocket.String())}

Concluzie

Am explorat cum modelul Options poate îmbunătăți semnificativ designul și utilizarea funcțiilor din Golang, mai ales atunci când avem de-a face cu mai mulți parametri opționali.

Vă mulțumesc că ați citit!