You don't have access to this class

Keep learning! Join and start boosting your career

Aprovecha el precio especial y haz tu profesión a prueba de IA

Antes: $249

Currency
$209
Suscríbete

Termina en:

0 Días
12 Hrs
41 Min
4 Seg

Factory

9/19
Resources

What are design patterns and how do they help us?

Design patterns are common solutions to common problems in software development. These patterns facilitate code reuse, improve comprehension and encourage good programming practices. Today we will explore the Factory design pattern, a creative pattern that allows you to create flexible and scalable object factories.

How to implement the Factory pattern in Go?

To begin with, we will create a basic interface and the necessary structures by implementing them in Go language. Let's see the process step by step:

  1. Create the base interface: First, we define an interface, called iProduct, which will require the implementation of several essential methods:

    type iProduct interface { setStock(stock int) getStock() int setName(name string) getName() string}
  2. Define concrete structures: Now, we define our Computer structure and its methods to comply with the iProduct interface.

    type Computer struct { name string stock int}
    func (c *Computer) setStock(stock int) { c.stock = stock }func (c *Computer) getStock() int { return c.stock }func (c *Computer) setName(name string) { c.name = name }func (c *Computer) getName() string { return c.name }
  3. Composition over inheritance: We create subclasses like Laptop and Desktop, using composition in Go instead of inheritance.

    type Laptop struct { Computer}
    func newLaptop() iProduct { return &Laptop{Computer{name: "Laptop Computer", stock:  25}}}
    type Desktop struct { Computer}
    func newDesktop() iProduct { return &Desktop{Computer{name: "Desktop Computer", stock:  35}}}
  4. Implement Factory: The next step is to create the getComputerFactory function that uses the Factory pattern to return the appropriate product.

    func getComputerFactory(computerType string) (iProduct, error) { if computerType == "laptop" { return newLaptop(), nil } else if computerType == "desktop" { return newDesktop(), nil } return nil, fmt.Errorf("Invalid computer type")}

Why use the Factory pattern?

The Factory pattern offers multiple benefits:

  • Flexibility: It allows us to extend the number of products to be created without modifying the existing code.
  • Polymorphism: All instances are treated as iProduct, promoting the use of polymorphism and code reuse.
  • Maintenance: Centralizes and simplifies instance management allowing modifications in one place.

How to test and verify the implementation?

Create a print function to visualize the results and verify the polymorphic behavior:

 { fmt.Printf("Product Name: %s, Stock: %d", p.getName(), p.getStock())}
func main() { laptop, _ := getComputerFactory("laptop") desktop, _ := getComputerFactory("desktop")
 printNameAndStock(laptop) printNameAndStock(laptop) printNameAndStock(desktop)}

Running this code will verify that the Laptop and Desktop instances are properly treated as iProduct instances.

What are the pros and cons of the Factory pattern?

Using the Factory pattern offers clarity in object creation and flexibility in adding new products. However, it can make code more complex and difficult to read, especially for programming novices.

By addressing the initial complexity, the Factory pattern becomes a powerful tool for robust software architectures. Continue exploring more design patterns to further hone your skills!

Contributions 8

Questions 2

Sort by:

Want to see more contributions, questions and answers from the community?

Acá unas modificaciones que agregué al Código, espero les sirva:

  • Función para el ToString que funcione unicamente para los Computers
// ToString function
func (c *Computer) String() string {
	s := fmt.Sprintf("Product: %s, with Stock: %d", c.name, c.stock)
	return s
} 
  • Evitando errores al pasar un String en el Factory:
// This is the factory
func ComputerFactory(computerType Product) (Product, error) {
	switch computerType.(type) {
	case *Desktop:
		return NewDesktop(), nil
	case *Laptop:
		return NewLaptop(), nil
	default:
		return nil, fmt.Errorf("Unknown computer type: %T", computerType)
	}
}

La función main se vería algo así al hacer el proceso de creación:

func main() {
	laptop, _ := ComputerFactory(&Desktop{})
	desktop, _ := ComputerFactory(&Laptop{})

	fmt.Println(laptop)
	fmt.Println(desktop)
}

Factory
Es un patrón creacional, que nos permite crear una “fabrica” de objetos a partir de una clase base y a su vez va implementar comportamientos polimórficos que permite modificar el comportamiento de las clases heredadas

package main

import (
	"fmt"
	"strings"
)

type IProduct interface {
	setStock(stock int)
	getStock() int
	setName(name string)
	getName() string
}

type Computer struct {
	name  string
	stock int
}

func (c *Computer) setStock(stock int) {
	c.stock = stock
}

func (c *Computer) setName(name string) {
	c.name = name
}

func (c *Computer) getName() string {
	return c.name
}

func (c *Computer) getStock() int {
	return c.stock
}

type Laptop struct {
	Computer
}

func newLaptop() IProduct {
	return &Laptop{
		Computer: Computer{
			name:  "Laptop computer",
			stock: 25,
		},
	}
}

type Desktop struct {
	Computer
}

func newDesktop() IProduct {
	return &Desktop{
		Computer: Computer{
			name:  "Desktop computer",
			stock: 35,
		},
	}
}

func GetComputerFactory(computerType string) (IProduct, error) {
	if strings.ToLower(computerType) == "laptop" {
		return newLaptop(), nil
	}
	if strings.ToLower(computerType) == "desktop" {
		return newDesktop(), nil
	}
	return nil, fmt.Errorf("Invalid computer type")
}

func printNameAndStock(p IProduct) {
	fmt.Printf("Product name: %s, with stock %d\n", p.getName(), p.getStock())
}

func main() {
	laptop, _ := GetComputerFactory("laptop")
	desktop, _ := GetComputerFactory("desktop")
	printNameAndStock(laptop)
	printNameAndStock(desktop)
}
package main

import "errors"

// Patron Creacional

// Interface que define el comportamiento de un producto
type IProduct interface {
	setStocked(stock int)
	getStocked() int

	getName() string
	setName(name string)
}

// Implementacion de la interfaz IProduct para el producto de tipo "Computadora"
type Computer struct {
	name  string
	stock int
}

// Implementando de forma implicita la interfaz IProduct
func (c *Computer) setStocked(stock int) {
	c.stock = stock
}

func (c *Computer) getStocked() int {
	return c.stock
}

func (c *Computer) getName() string {
	return c.name
}

func (c *Computer) setName(name string) {
	c.name = name
}

// Creando clase base de computadora, por composicion sobre herencia
type Laptop struct {
	Computer
}

func NewLaptop() IProduct {
	return &Laptop{Computer{"Laptop", 25}}
}

type Desktop struct {
	Computer
}

func NewDesktop() IProduct {
	return &Desktop{Computer{"Desktop", 35}}
}

// Creando fabrica de productos: Factory pattern
func GetComputerFactory(computerType string) (IProduct, error) {
	switch computerType {
	case "Laptop":
		return NewLaptop(), nil
	case "Desktop":
		return NewDesktop(), nil
	default:
		return nil, errors.New("invalid computer type")
	}
}

// Trying polymorphism
func PrintNameAndStock(product IProduct) {
	println("Name:", product.getName(), "Stock:", product.getStocked())
}

func main() {
	laptop, _ := GetComputerFactory("Laptop")
	desktop, _ := GetComputerFactory("Desktop")

	PrintNameAndStock(laptop)
	PrintNameAndStock(desktop)
}

Aquí dejo mi código con comentarios

package main

import "fmt"

// interfaz que cumple la estructura Computer

type IProduct interface {
	setStock(stock int)
	getStock() int
	setName(name string)
	getName() string
}

//Las estructuras son literalmente clases

type Computer struct {
	name  string
	stock int
}

//metodos de Computer para satisfacer la interfaz

func (c *Computer) setStock(stock int) {
	c.stock = stock
}

func (c *Computer) setName(name string) {
	c.name = name
}

func (c *Computer) getName() string {
	return c.name
}

func (c *Computer) getStock() int {
	return c.stock
}

// Podríamos decir que la estructura Laptop es hija de la
// estructura Computer

type Laptop struct {
	Computer //composition over inheritance
}

// Constructor para crear una nueva laptop

func newLaptop() IProduct {
	return &Laptop{
		Computer: Computer{
			name:  "Laptop Computer",
			stock: 25,
		},
	}
}

// Estructura hija de Computer, por lo que obtiene todos sus
// métodos

type Desktop struct {
	Computer
}

// Constructor para crear una instancia de la estructura Desktop

func newDesktop() IProduct {
	return &Desktop{
		Computer{
			name:  "Desktop Computer",
			stock: 35,
		},
	}
}

// Creamos función para determinar cuál estructura se debe
// instanciar

func GetComputerFactory(computerType string) (IProduct, error) {
	if computerType == "laptop" {
		return newLaptop(), nil
	} else if computerType == "desktop" {
		return newDesktop(), nil
	}

	return nil, fmt.Errorf("Invalid computer type")
}

// imprimimos los productos deseados utilizando la interfaz

func printNameAndStoc(p IProduct) {
	fmt.Printf("PRoduct name: %s, with stock %d\n", p.getName(), p.getStock())
}

func main() {
	laptop, _ := GetComputerFactory("laptop")
	// instancia de la estructura Laptop

	desktop, _ := GetComputerFactory("desktop")
	// instancia de la estructura Desktop

	printNameAndStoc(laptop)
	printNameAndStoc(desktop)
}

Excelente explicación! 😄

Esa definición de interfaces con el prefijo I es muy java de tu parte, no se suele ser tan verbosos en go