DEV Community

Maximiliano Burgos
Maximiliano Burgos

Posted on

Diario de Python | #16. Patrón Builder

Estaba mirando un video de Youtube sobre un desafío de programación en Python, y en un momento mencionan algo como "esto se podría implementar con el patrón Builder".

Esto me remontó a mis tiempos en Java, allá por el año [inserte aquí fecha que no recuerdo]. En esa época tu seniority se definía por cuántos patrones de diseño conocías, y cómo podías implementarlos. Fueron buenos años, porque cuando entendías cada uno, te dabas cuenta de que ya no podías vivir sin ellos.

El patrón Builder, o cómo construir cosas.

Este patrón de diseño fue pensado para trabajar sobre clases que se componían de muchísimos atributos, y no siempre queríamos inicializarlos todos.

Por lo cual, tomando la base de los famosos métodos setters, se creó el patrón Builder, el cual permitía "construir parcialmente" a un objeto mediante métodos que apuntaban a propiedades de formas independientes. Era básicamente un conjunto sofisticado de setters.

La implementación

Si quieren saltar al ejemplo terminado, les dejo el archivo de mi repositorio de prácticas en Python. Ahora pasemos a la implementación paso a paso.

Empecé generando una clase Hero, la cual se compone de los típicos atributos de un juego RPG: nombre, ataque, defensa, dinero y vida.

class Hero:
    def __init__(self, name):
        self.name = name
        self.hp = 100
        self.money = 10
        self.attack = 0
        self.defense = 0

    def __str__(self):
        return f"Hero: {self.name} | 💰{self.money}{self.attack} 🛡{self.defense}"
Enter fullscreen mode Exit fullscreen mode

Luego construí una clase HeroBuilder, la cual dentro de su constructor tiene una instancia vacía de Hero, pero debe pasar obligatoriamente el parámetro name por obvias razones.

class HeroBuilder:
    def __init__(self, name):
        self.hero = Hero(name)
Enter fullscreen mode Exit fullscreen mode

Acá es donde la cosa se pone interesante: para definir el ataque de nuestro heroe, vamos a crear un setter dentro del mismo builder:

def set_attack(self, attack):
    self.hero.attack = max(attack, 0)
    return self
Enter fullscreen mode Exit fullscreen mode

Defino el attack del hero llamando a self.hero porque es el objeto que inicializé en mi constructor. Pero retorno self, lo cual puede resultar confuso para muchos.

La idea es retornar el objeto mismo, entonces esto nos permite seguir trabajando en la instancia. Muchos me pueden decir: pero podría hacer lo mismo con un objeto hero y sus respectivos setters. Algo como esto:

hero = Hero("Tomas")
hero.set_attack(10)
hero.set_defense(5)
Enter fullscreen mode Exit fullscreen mode

Y es cierto, pero no podrías encadenarlos así:

hero.set_attack(10).set_defense(5)
Enter fullscreen mode Exit fullscreen mode

Y por esto necesitamos retornar el self, que en nuestro caso nos devolvería el hero mismo. Pero continuemos con el ejemplo:

def add_money(self, money):
    self.hero.money += max(money, 0)
    return self
Enter fullscreen mode Exit fullscreen mode

Este método es un setter con incremento, porque podemos llamarlo y aumentar el dinero con cada llamada (siempre y cuando sea un valor mayor a cero).

def get_hero(self):
    return self.hero
Enter fullscreen mode Exit fullscreen mode

Y finalmente este método nos retornará el objeto hero, el cual con nuestro builder estuvimos modificando. La pregunta es: ¿cómo implementamos esto?

builder = HeroBuilder("Tomas")
builder.set_attack(5).set_defense(10)
builder.add_money(10).add_money(20).add_money(30)
hero = builder.get_hero()

print(hero)
Enter fullscreen mode Exit fullscreen mode

En primer lugar armamos nuestro objeto builder. Luego llamamos tantos métodos como queramos. Pueden notar que llamo a add_money unas tres veces, lo cual demuestra la flexibilidad de este patrón. Por otro lado, noten como encadeno los métodos: seguramente vieron este tipo de implementaciones en muchas librerías que han utilizado. El patrón Builder está en todas partes, y es uno de los más poderosos que existen a la hora de personalizar objetos.

Finalmente pueden observar como declaro mi objeto hero y luego lo imprimo en pantalla. Esto da como resultado lo siguiente:

Hero: Tomas | 💰70 ⚔5 🛡10
Enter fullscreen mode Exit fullscreen mode

Conclusiones

Me resulta muy entretenido jugar con los objetos y darles mi forma. Este patrón de diseño me lo permite sin ninguna limitación, porque como dije anteriormente, es un modo eficiente de trabajar con setters.

Espero que hayan disfrutado esta entrega, ¡no olviden darle like y compartirlo para alardear de que ahora conocen otro interesante patrón de diseño!

Top comments (0)