sábado, 14 de ene. de 2023
Patrones de diseño de software en Go
Patrones creacionales
Establecen mecanismos de creación de objetos que aumentan la flexibilidad y reutilización de código. Los patrones creacionales más importantes son:
- Abstract Factory. Proporciona una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas.
En Go, podemos implementar el patrón Abstract Factory de la siguiente manera:
package mainimport ("fmt""math")type SphericalFactory struct {radius float64}type PyramidFactory struct {base float64height float64}type AbstractFactory interface {GetVolume() float64GetArea() float64}func (s SphericalFactory) GetVolume() float64 {return (4 / 3) * math.Pi * math.Pow(s.radius, 3)}func (s SphericalFactory) GetArea() float64 {return 4 * math.Pi * math.Pow(s.radius, 2)}func (p PyramidFactory) GetVolume() float64 {return (p.base * p.height) / 3}func (p PyramidFactory) GetArea() float64 {return p.base * p.height}func GetFactory(shape string) AbstractFactory {switch shape {case "sphere":return SphericalFactory{radius: 10}case "pyramid":return PyramidFactory{base: 10, height: 10}default:return nil}}func PrinData(factory AbstractFactory) {fmt.Println("Area:", factory.GetArea())fmt.Println("Volume:", factory.GetVolume())}func main() {sphere := GetFactory("sphere")PrinData(sphere)pyramid := GetFactory("pyramid")PrinData(pyramid)}
- Singleton. Asegura que una clase sólo tenga una instancia y proporciona un punto de acceso global a ella.
En Go, podemos implementar el patrón Singleton de la siguiente manera:
package main// singleton exampleimport ("fmt""time")type Database struct{}var instance *Databasefunc (d *Database) CreateConnection() {fmt.Println("Creating connection...")time.Sleep(5 * time.Second)fmt.Println("Connection created")}func GetInstance() *Database {if instance == nil {instance = &Database{}instance.CreateConnection()}return instance}func main() {conection1 := GetInstance()conection2 := GetInstance()fmt.Println(conection1 == conection2)}
Patrones estructurales
Los patrones estructurales se preocupan por cómo se componen los objetos y clases. Con el fin de lograr una mayor flexibilidad y reutilización de código.
- Adapter. Convierte la interfaz de una clase en otra interfaz que el cliente espera. Permite que clases trabajen juntas que de otra forma no podrían debido a interfaces incompatibles.
En Go, podemos implementar el patrón Adapter de la siguiente manera:
package mainimport "fmt"type Payment interface {Pay(amount float32) string}type Cash struct{}type BankPayment struct{}func (c *Cash) Pay(amount float32) string {return fmt.Sprintf("%0.2f paid using cash", amount)}func (b *BankPayment) Pay(amount float32, account string) string {return fmt.Sprintf("%0.2f paid using bank(%s)", amount, account)}func processPayment(p Payment, amount float32) {fmt.Println(p.Pay(amount))}type BankPaymentAdapter struct {bankPayment *BankPaymentaccount string}func (b *BankPaymentAdapter) Pay(amount float32) string {return b.bankPayment.Pay(amount, b.account)}func main() {cash := &Cash{}processPayment(cash, 100)backPayment := &BankPaymentAdapter{bankPayment: &BankPayment{},account: "123456789",}processPayment(backPayment, 200)}
Patrones de comportamiento
Estos patrones se preocupan por cómo los objetos interactúan y distribuyen la responsabilidad entre ellos.
- Observer. Define una dependencia de uno a muchos entre objetos de modo que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente.
En Go, podemos implementar el patrón Observer de la siguiente manera:
package mainimport "fmt"type Observer interface {notify(string)}type Product struct {observers []Observername stringavailable bool}type EmailClient struct {email string}func NewProduct(name string) *Product {return &Product{name: name,available: false,}}func (prod *Product) register(observer Observer) {prod.observers = append(prod.observers, observer)}func (prod *Product) updateAvailability(available bool) {prod.available = availableprod.boradcast()}func (prod *Product) boradcast() {if !prod.available {return}for _, observer := range prod.observers {observer.notify(prod.name)}}func (client *EmailClient) notify(prodName string) {fmt.Printf("*) Item %s is now available , please check your account %s \n", prodName, client.email)}func main() {product1 := NewProduct("iPhone X")product2 := NewProduct("iPhone X2")observer1 := &EmailClient{email: "carlos@gmail.com"}observer2 := &EmailClient{email: "mila@gmail.com"}observer3 := &EmailClient{email: "joe@gmail.com"}product1.register(observer1)product1.register(observer2)product2.register(observer3)product1.updateAvailability(true)}
- Strategy. Define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo usan.
En Go, podemos implementar el patrón Strategy de la siguiente manera:
package maintype Algorithm interface {hash(p *PasswordProtector)}type PasswordProtector struct {password stringalgorithm Algorithm}func (p *PasswordProtector) SetAlgorithm(a Algorithm) {p.algorithm = a}func (p *PasswordProtector) Hash() {p.algorithm.hash(p)}type SHA256 struct{}type MD5 struct{}func (s *SHA256) hash(p *PasswordProtector) {p.password = "SHA256->" + p.password}func (m *MD5) hash(p *PasswordProtector) {p.password = "MD5->" + p.password}func main() {pass := &PasswordProtector{password: "123456"}pass.SetAlgorithm(&SHA256{})pass.Hash()println(pass.password)pass.SetAlgorithm(&MD5{})pass.Hash()println(pass.password)}