Diario de Python | #16. Patrón Builder

Dario de Python (18 Part Series)

1 Diario de Python | #1. Bajada a tierra
2 Diario de Python | #2. Mi propio plan Full Stack
14 more parts…
3 Diario de Python | #3. Pycharm, el mejor IDE para Python
4 Diario de Python | #4. Probando FastAPI para desarrollar APIs
5 Diario de Python | #5. Proyecto: Color Choice
6 Diario de Python | #6. Primeros pasos con Color Choice
7 Diario de Python | #7. Color Choice: armando las rutas
8 Diario de Python | #8. Issuu PDF Downloader
9 Diario de Python | #9. Color Choice: Volantazo
10 Diario de Python | #10. Color Choice: Los colores
11 Diario de Python | #11. Color Choice: Flujo de la API
12 Diario de Python | #12. Color Choice: Autenticación por Token
13 Diario de Python | #13. Color Choice: emitir votos
14 Diario de Python | #14. Color Choice: día productivo
15 Diario de Python | #15. Combinando Django y React
16 Diario de Python | #16. Patrón Builder
17 Diario de Python | #17. un paseo por Flake8
18 Diario de Python | #18. Un curso de Automatizar cosas aburridas

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.

<span>class</span> <span>Hero</span><span>:</span>
<span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>):</span>
<span>self</span><span>.</span><span>name</span> <span>=</span> <span>name</span>
<span>self</span><span>.</span><span>hp</span> <span>=</span> <span>100</span>
<span>self</span><span>.</span><span>money</span> <span>=</span> <span>10</span>
<span>self</span><span>.</span><span>attack</span> <span>=</span> <span>0</span>
<span>self</span><span>.</span><span>defense</span> <span>=</span> <span>0</span>
<span>def</span> <span>__str__</span><span>(</span><span>self</span><span>):</span>
<span>return</span> <span>f</span><span>"Hero: </span><span>{</span><span>self</span><span>.</span><span>name</span><span>}</span><span> | </span><span>{</span><span>self</span><span>.</span><span>money</span><span>}</span><span> </span><span>{</span><span>self</span><span>.</span><span>attack</span><span>}</span><span> </span><span>{</span><span>self</span><span>.</span><span>defense</span><span>}</span><span>"</span>
<span>class</span> <span>Hero</span><span>:</span>
    <span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>):</span>
        <span>self</span><span>.</span><span>name</span> <span>=</span> <span>name</span>
        <span>self</span><span>.</span><span>hp</span> <span>=</span> <span>100</span>
        <span>self</span><span>.</span><span>money</span> <span>=</span> <span>10</span>
        <span>self</span><span>.</span><span>attack</span> <span>=</span> <span>0</span>
        <span>self</span><span>.</span><span>defense</span> <span>=</span> <span>0</span>

    <span>def</span> <span>__str__</span><span>(</span><span>self</span><span>):</span>
        <span>return</span> <span>f</span><span>"Hero: </span><span>{</span><span>self</span><span>.</span><span>name</span><span>}</span><span> | </span><span>{</span><span>self</span><span>.</span><span>money</span><span>}</span><span> </span><span>{</span><span>self</span><span>.</span><span>attack</span><span>}</span><span> </span><span>{</span><span>self</span><span>.</span><span>defense</span><span>}</span><span>"</span>
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.

<span>class</span> <span>HeroBuilder</span><span>:</span>
<span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>):</span>
<span>self</span><span>.</span><span>hero</span> <span>=</span> <span>Hero</span><span>(</span><span>name</span><span>)</span>
<span>class</span> <span>HeroBuilder</span><span>:</span>
    <span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>):</span>
        <span>self</span><span>.</span><span>hero</span> <span>=</span> <span>Hero</span><span>(</span><span>name</span><span>)</span>
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:

<span>def</span> <span>set_attack</span><span>(</span><span>self</span><span>,</span> <span>attack</span><span>):</span>
<span>self</span><span>.</span><span>hero</span><span>.</span><span>attack</span> <span>=</span> <span>max</span><span>(</span><span>attack</span><span>,</span> <span>0</span><span>)</span>
<span>return</span> <span>self</span>
<span>def</span> <span>set_attack</span><span>(</span><span>self</span><span>,</span> <span>attack</span><span>):</span>
    <span>self</span><span>.</span><span>hero</span><span>.</span><span>attack</span> <span>=</span> <span>max</span><span>(</span><span>attack</span><span>,</span> <span>0</span><span>)</span>
    <span>return</span> <span>self</span>
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:

<span>hero</span> <span>=</span> <span>Hero</span><span>(</span><span>"Tomas"</span><span>)</span>
<span>hero</span><span>.</span><span>set_attack</span><span>(</span><span>10</span><span>)</span>
<span>hero</span><span>.</span><span>set_defense</span><span>(</span><span>5</span><span>)</span>
<span>hero</span> <span>=</span> <span>Hero</span><span>(</span><span>"Tomas"</span><span>)</span>
<span>hero</span><span>.</span><span>set_attack</span><span>(</span><span>10</span><span>)</span>
<span>hero</span><span>.</span><span>set_defense</span><span>(</span><span>5</span><span>)</span>
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í:

<span>hero</span><span>.</span><span>set_attack</span><span>(</span><span>10</span><span>).</span><span>set_defense</span><span>(</span><span>5</span><span>)</span>
<span>hero</span><span>.</span><span>set_attack</span><span>(</span><span>10</span><span>).</span><span>set_defense</span><span>(</span><span>5</span><span>)</span>
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:

<span>def</span> <span>add_money</span><span>(</span><span>self</span><span>,</span> <span>money</span><span>):</span>
<span>self</span><span>.</span><span>hero</span><span>.</span><span>money</span> <span>+=</span> <span>max</span><span>(</span><span>money</span><span>,</span> <span>0</span><span>)</span>
<span>return</span> <span>self</span>
<span>def</span> <span>add_money</span><span>(</span><span>self</span><span>,</span> <span>money</span><span>):</span>
    <span>self</span><span>.</span><span>hero</span><span>.</span><span>money</span> <span>+=</span> <span>max</span><span>(</span><span>money</span><span>,</span> <span>0</span><span>)</span>
    <span>return</span> <span>self</span>
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).

<span>def</span> <span>get_hero</span><span>(</span><span>self</span><span>):</span>
<span>return</span> <span>self</span><span>.</span><span>hero</span>
<span>def</span> <span>get_hero</span><span>(</span><span>self</span><span>):</span>
    <span>return</span> <span>self</span><span>.</span><span>hero</span>
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?

<span>builder</span> <span>=</span> <span>HeroBuilder</span><span>(</span><span>"Tomas"</span><span>)</span>
<span>builder</span><span>.</span><span>set_attack</span><span>(</span><span>5</span><span>).</span><span>set_defense</span><span>(</span><span>10</span><span>)</span>
<span>builder</span><span>.</span><span>add_money</span><span>(</span><span>10</span><span>).</span><span>add_money</span><span>(</span><span>20</span><span>).</span><span>add_money</span><span>(</span><span>30</span><span>)</span>
<span>hero</span> <span>=</span> <span>builder</span><span>.</span><span>get_hero</span><span>()</span>
<span>print</span><span>(</span><span>hero</span><span>)</span>
<span>builder</span> <span>=</span> <span>HeroBuilder</span><span>(</span><span>"Tomas"</span><span>)</span>
<span>builder</span><span>.</span><span>set_attack</span><span>(</span><span>5</span><span>).</span><span>set_defense</span><span>(</span><span>10</span><span>)</span>
<span>builder</span><span>.</span><span>add_money</span><span>(</span><span>10</span><span>).</span><span>add_money</span><span>(</span><span>20</span><span>).</span><span>add_money</span><span>(</span><span>30</span><span>)</span>
<span>hero</span> <span>=</span> <span>builder</span><span>.</span><span>get_hero</span><span>()</span>

<span>print</span><span>(</span><span>hero</span><span>)</span>
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
Hero: Tomas | 70 5 10
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!

Dario de Python (18 Part Series)

1 Diario de Python | #1. Bajada a tierra
2 Diario de Python | #2. Mi propio plan Full Stack
14 more parts…
3 Diario de Python | #3. Pycharm, el mejor IDE para Python
4 Diario de Python | #4. Probando FastAPI para desarrollar APIs
5 Diario de Python | #5. Proyecto: Color Choice
6 Diario de Python | #6. Primeros pasos con Color Choice
7 Diario de Python | #7. Color Choice: armando las rutas
8 Diario de Python | #8. Issuu PDF Downloader
9 Diario de Python | #9. Color Choice: Volantazo
10 Diario de Python | #10. Color Choice: Los colores
11 Diario de Python | #11. Color Choice: Flujo de la API
12 Diario de Python | #12. Color Choice: Autenticación por Token
13 Diario de Python | #13. Color Choice: emitir votos
14 Diario de Python | #14. Color Choice: día productivo
15 Diario de Python | #15. Combinando Django y React
16 Diario de Python | #16. Patrón Builder
17 Diario de Python | #17. un paseo por Flake8
18 Diario de Python | #18. Un curso de Automatizar cosas aburridas

原文链接:Diario de Python | #16. Patrón Builder

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
Life is never easier, we just get stronger.
生活从未变得容易,只是我们变得更加坚强
评论 抢沙发

请登录后发表评论

    暂无评论内容