[{"data":1,"prerenderedAt":671},["ShallowReactive",2],{"blog:es:object-oriented-without-classes":3},{"id":4,"title":5,"author":6,"body":7,"date":657,"description":658,"extension":659,"meta":660,"navigation":356,"path":661,"publishAt":662,"seo":663,"stem":664,"tags":665,"__hash__":670},"blog_es/blog/object-oriented-without-classes/es.md","La pregunta que nos hizo un desarrollador de Java","Aptli",{"type":8,"value":9,"toc":646},"minimark",[10,15,19,22,25,28,32,35,38,41,44,48,51,54,57,60,63,67,70,73,76,172,175,178,181,184,187,190,193,197,200,203,499,518,521,525,528,531,566,569,572,576,579,582,585,588,592,595,598,601,604,607,611,642],[11,12,14],"h2",{"id":13},"la-pregunta","La pregunta",[16,17,18],"p",{},"Un desarrollador que postulaba para trabajar con nosotros venía de Java. Estábamos hablando de estilo de código, y preguntó qué patrones de diseño orientados a objetos usamos.",[16,20,21],{},"Usamos la mayoría todo el tiempo, le dijimos. Strategy para una familia de comportamientos intercambiables. Observer para un cambio que necesita llegar a todo lo que esté interesado en él. Composición sobre herencia, más como costumbre que como regla.",[16,23,24],{},"Después vino una pregunta más aguda. ¿Cómo, si nuestro frontend casi no declara clases ni interfaces? Java expresa esos patrones a través de un tipo base y las cosas que lo implementan. Nuestro código no tiene ese vocabulario disponible, así que tiene que estar haciendo el mismo trabajo por otro camino.",[16,26,27],{},"Y lo hace. Un componente de Vue ya contiene lo que contiene un objeto: sus propios datos, sus propios métodos, y la parte que un objeto de Java suele entregarle a otra cosa, cómo se presenta. Un composable es la forma en que un componente obtiene un comportamiento de otro, la misma relación de composición que le da una clase, sin la clase base. Vocabulario distinto, mismo argumento, y se sostuvo mejor de lo que esperábamos una vez que nos sentamos a escribir exactamente cómo.",[11,29,31],{"id":30},"el-problema-de-negocio-detrás-de-todo-esto","El problema de negocio detrás de todo esto",[16,33,34],{},"Vendemos software que nuestros propios clientes configuran. Un departamento de obras públicas construye sus propios formularios de solicitud de servicio. Una empresa de servicios públicos define sus propias categorías de activos. Un municipio escribe sus propias reglas de automatización para lo que ocurre cuando llega un informe.",[16,36,37],{},"Eso significa que las solicitudes llegan todo el tiempo, y son pequeñas. ¿Podemos agregar un campo de firma a este formulario? ¿Podemos traer datos de este otro sistema? ¿Podemos enviar esa alerta también por mensaje de texto además de correo?",[16,39,40],{},"Cada una de esas es fácil de aceptar una vez. La pregunta interesante es cuánto cuesta la décima. Si cada tipo de campo nuevo implica editar los mismos tres archivos, la décima solicitud es más lenta que la primera, y la quincuagésima es un proyecto entero. Si cada una es una simple adición, la quincuagésima cuesta más o menos lo que costó la primera.",[16,42,43],{},"Nada de eso se ve en una demostración. Se decide años antes, según cómo está construido el código. Por eso le dedicamos atención real a esa forma.",[11,45,47],{"id":46},"ponerle-nombre-a-la-forma","Ponerle nombre a la forma",[16,49,50],{},"La mayoría de los consejos sobre calidad de código terminan en dos ideas. No te repitas. Prefiere la composición sobre la herencia. Ambas son ciertas, y ninguna resuelve mucho a las cuatro de la tarde de un jueves cuando alguien está decidiendo dónde va una función nueva.",[16,52,53],{},"Lo que lo resuelve es un nombre compartido.",[16,55,56],{},"Cuando alguien del equipo dice \"hagamos eso como un registro,\" todos saben lo que va a llegar en la revisión: una tabla con una fila por tipo, una función por fila, y una sola línea que busca la fila correcta. Nadie lo dibuja en una pizarra. Nadie discute al respecto. La revisión toma un minuto, porque la forma ya se acordó antes de escribir el código.",[16,58,59],{},"Ese vocabulario es lo que realmente sacamos de estudiar patrones. Vive entre las personas, y se gana su lugar dos veces. Hace que una buena decisión sea rápida, y hace que una mala decisión sea visible mientras todavía es una frase, antes de convertirse en tres archivos.",[16,61,62],{},"Mantenemos una lista escrita de las formas que usamos y dónde va cada una. Todo trabajo nuevo empieza nombrando cuál de ellas es. Esa única pregunta resuelve la mayor parte del diseño, y el resto es escribir código.",[11,64,66],{"id":65},"lo-que-eso-compra-en-concreto","Lo que eso compra, en concreto",[16,68,69],{},"Tomemos las notificaciones. Cuando ocurre algo que una persona necesita saber, se lo enviamos. Correo, mensaje de texto, una notificación push al teléfono.",[16,71,72],{},"Un cliente nos pidió agregar mensajes de texto para una alerta en particular. Es una solicitud pequeña, y es exactamente el tipo que recibimos cada semana.",[16,74,75],{},"Mantenemos una sola tabla con lo que enviamos, con una entrada por notificación. Cada entrada responde una sola pregunta en código sencillo: ¿quién debería enterarse de esto?",[77,78,83],"pre",{"className":79,"code":80,"language":81,"meta":82,"style":82},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","'report.submitted': {\n  recipients: (args) => [args.report.assignee, ...args.report.watchers],\n},\n","ts","",[84,85,86,108,166],"code",{"__ignoreMap":82},[87,88,91,95,99,101,105],"span",{"class":89,"line":90},"line",1,[87,92,94],{"class":93},"sMK4o","'",[87,96,98],{"class":97},"sfazB","report.submitted",[87,100,94],{"class":93},[87,102,104],{"class":103},"sTEyZ",": ",[87,106,107],{"class":93},"{\n",[87,109,111,115,118,121,125,128,132,135,138,141,143,146,149,152,154,156,158,160,163],{"class":89,"line":110},2,[87,112,114],{"class":113},"s2Zo4","  recipients",[87,116,117],{"class":93},":",[87,119,120],{"class":93}," (",[87,122,124],{"class":123},"sHdIc","args",[87,126,127],{"class":93},")",[87,129,131],{"class":130},"spNyl"," =>",[87,133,134],{"class":103}," [args",[87,136,137],{"class":93},".",[87,139,140],{"class":103},"report",[87,142,137],{"class":93},[87,144,145],{"class":103},"assignee",[87,147,148],{"class":93},",",[87,150,151],{"class":93}," ...",[87,153,124],{"class":103},[87,155,137],{"class":93},[87,157,140],{"class":103},[87,159,137],{"class":93},[87,161,162],{"class":103},"watchers]",[87,164,165],{"class":93},",\n",[87,167,169],{"class":89,"line":168},3,[87,170,171],{"class":93},"},\n",[16,173,174],{},"Agregar una notificación es una entrada. Elegir los canales depende de qué plantillas de mensaje existen para ella, así que un mensaje de texto llega escribiendo la versión en texto del mensaje. El motor que hace el envío se queda como está. Tiene unas cuarenta líneas, y se ha mantenido en unas cuarenta líneas a través de cada notificación que hemos agregado.",[16,176,177],{},"No empezó viéndose así. La primera notificación que construimos fue un toast, ese mensaje pequeño que se desliza en la esquina de la pantalla para decir que lo que acabas de hacer funcionó. Un toast es de lo más simple que existe, y había una versión del trabajo donde lo escribíamos como un toast y seguíamos adelante.",[16,179,180],{},"En vez de eso escribimos la tabla, porque un toast ya está respondiendo las dos preguntas que responderá todo lo demás. Algo ocurrió. ¿Quién necesita saberlo, y qué debería ver?",[16,182,183],{},"Las respuestas crecieron bastante después de eso. Algunas notificaciones tenían que llegar a una persona que había cerrado la pestaña, así que se volvieron correo. Algunas tenían que llegar a una cuadrilla sin laptop, así que se volvieron mensajes de texto. Algunas necesitaban llegar a un teléfono a las dos de la mañana, así que se volvieron push. Cada una de esas trajo trabajo real consigo, incluida una interfaz nueva para que la gente elija qué quiere escuchar, y nada de eso cambió la respuesta a las dos preguntas originales.",[16,185,186],{},"La regla que mantenemos es que cualquier cosa inusual sobre una notificación vive dentro de la entrada de esa notificación. En el momento en que un caso especial se muda al motor, el motor empieza a acumular una marca por cada caso, y la siguiente persona tiene que leerlas todas para agregar algo.",[16,188,189],{},"Existe una versión de esto donde el código de envío pregunta \"¿esto es un correo, un texto o un push?\" y crece una rama cada vez. Tenemos las dos formas en nuestro código, porque el software escrito a lo largo de los años es honesto sobre su propia historia. La versión con ramas es la que programamos para convertir, y la razón no es prolijidad. Una fila es un martes. Una rama es una conversación.",[16,191,192],{},"El desarrollador de Java llamaría a la primera versión Strategy. No hay una interfaz ni una clase en ninguna parte, y hace exactamente lo que promete el patrón.",[11,194,196],{"id":195},"el-objeto-ya-estaba-ahí","El objeto ya estaba ahí",[16,198,199],{},"Empecemos por la mitad de la respuesta que menos nos sorprendió una vez que la miramos de frente. Java expresa un objeto como una clase: un paquete de datos, los métodos que actúan sobre ellos, y nada sobre cómo aparece en pantalla, porque eso es un asunto aparte que se maneja en otro lugar.",[16,201,202],{},"Un componente de Vue empaqueta esas mismas tres cosas, más la tercera pieza que Java suele entregarle a otro:",[77,204,208],{"className":205,"code":206,"language":207,"meta":82,"style":82},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cscript setup lang=\"ts\">\nconst props = defineProps\u003C{ userId: string }>()\nconst user = ref\u003CUser | null>(null)\nconst isAdmin = computed(() => user.value?.role === 'admin')\n\nasync function load() { user.value = await getFetch(`/api/users/${props.userId}`) }\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>{{ user?.name }} \u003Cspan v-if=\"isAdmin\">Admin\u003C/span>\u003C/div>\n\u003C/template>\n","vue",[84,209,210,238,269,304,351,358,419,429,434,444,490],{"__ignoreMap":82},[87,211,212,215,219,222,225,228,231,233,235],{"class":89,"line":90},[87,213,214],{"class":93},"\u003C",[87,216,218],{"class":217},"swJcz","script",[87,220,221],{"class":130}," setup",[87,223,224],{"class":130}," lang",[87,226,227],{"class":93},"=",[87,229,230],{"class":93},"\"",[87,232,81],{"class":97},[87,234,230],{"class":93},[87,236,237],{"class":93},">\n",[87,239,240,243,246,248,251,254,257,259,263,266],{"class":89,"line":110},[87,241,242],{"class":130},"const",[87,244,245],{"class":103}," props ",[87,247,227],{"class":93},[87,249,250],{"class":113}," defineProps",[87,252,253],{"class":93},"\u003C{",[87,255,256],{"class":217}," userId",[87,258,117],{"class":93},[87,260,262],{"class":261},"sBMFI"," string",[87,264,265],{"class":93}," }>",[87,267,268],{"class":103},"()\n",[87,270,271,273,276,278,281,283,286,289,292,295,298,301],{"class":89,"line":168},[87,272,242],{"class":130},[87,274,275],{"class":103}," user ",[87,277,227],{"class":93},[87,279,280],{"class":113}," ref",[87,282,214],{"class":93},[87,284,285],{"class":261},"User",[87,287,288],{"class":93}," |",[87,290,291],{"class":261}," null",[87,293,294],{"class":93},">",[87,296,297],{"class":103},"(",[87,299,300],{"class":93},"null",[87,302,303],{"class":103},")\n",[87,305,307,309,312,314,317,319,322,324,327,329,332,335,338,341,344,347,349],{"class":89,"line":306},4,[87,308,242],{"class":130},[87,310,311],{"class":103}," isAdmin ",[87,313,227],{"class":93},[87,315,316],{"class":113}," computed",[87,318,297],{"class":103},[87,320,321],{"class":93},"()",[87,323,131],{"class":130},[87,325,326],{"class":103}," user",[87,328,137],{"class":93},[87,330,331],{"class":103},"value",[87,333,334],{"class":93},"?.",[87,336,337],{"class":103},"role ",[87,339,340],{"class":93},"===",[87,342,343],{"class":93}," '",[87,345,346],{"class":97},"admin",[87,348,94],{"class":93},[87,350,303],{"class":103},[87,352,354],{"class":89,"line":353},5,[87,355,357],{"emptyLinePlaceholder":356},true,"\n",[87,359,361,364,367,370,372,375,377,379,381,384,388,391,393,396,399,402,405,407,410,413,416],{"class":89,"line":360},6,[87,362,363],{"class":130},"async",[87,365,366],{"class":130}," function",[87,368,369],{"class":113}," load",[87,371,321],{"class":93},[87,373,374],{"class":93}," {",[87,376,326],{"class":103},[87,378,137],{"class":93},[87,380,331],{"class":103},[87,382,383],{"class":93}," =",[87,385,387],{"class":386},"s7zQu"," await",[87,389,390],{"class":113}," getFetch",[87,392,297],{"class":217},[87,394,395],{"class":93},"`",[87,397,398],{"class":97},"/api/users/",[87,400,401],{"class":93},"${",[87,403,404],{"class":103},"props",[87,406,137],{"class":93},[87,408,409],{"class":103},"userId",[87,411,412],{"class":93},"}`",[87,414,415],{"class":217},") ",[87,417,418],{"class":93},"}\n",[87,420,422,425,427],{"class":89,"line":421},7,[87,423,424],{"class":93},"\u003C/",[87,426,218],{"class":217},[87,428,237],{"class":93},[87,430,432],{"class":89,"line":431},8,[87,433,357],{"emptyLinePlaceholder":356},[87,435,437,439,442],{"class":89,"line":436},9,[87,438,214],{"class":93},[87,440,441],{"class":217},"template",[87,443,237],{"class":93},[87,445,447,450,453,455,458,460,462,465,467,469,472,474,476,479,481,483,486,488],{"class":89,"line":446},10,[87,448,449],{"class":93},"  \u003C",[87,451,452],{"class":217},"div",[87,454,294],{"class":93},[87,456,457],{"class":103},"{{ user?.name }} ",[87,459,214],{"class":93},[87,461,87],{"class":217},[87,463,464],{"class":130}," v-if",[87,466,227],{"class":93},[87,468,230],{"class":93},[87,470,471],{"class":97},"isAdmin",[87,473,230],{"class":93},[87,475,294],{"class":93},[87,477,478],{"class":103},"Admin",[87,480,424],{"class":93},[87,482,87],{"class":217},[87,484,485],{"class":93},">\u003C/",[87,487,452],{"class":217},[87,489,237],{"class":93},[87,491,493,495,497],{"class":89,"line":492},11,[87,494,424],{"class":93},[87,496,441],{"class":217},[87,498,237],{"class":93},[16,500,501,503,504,507,508,510,511,514,515,517],{},[84,502,404],{}," es un argumento de constructor. ",[84,505,506],{},"user"," e ",[84,509,471],{}," son variables de instancia. ",[84,512,513],{},"load"," es un método. La plantilla es la parte que Java suele dejarle a otra cosa: la que decide cómo se presenta el objeto. Pon dos de estos en la misma pantalla para dos usuarios distintos, y cada uno guarda su propio ",[84,516,506],{},", privado para sí mismo, sin ver nunca al otro. Eso son dos objetos.",[16,519,520],{},"Nada de eso necesitó la palabra clase. En los patrones en los que nos apoyamos, el objeto nunca fue realmente lo que estaba en duda. Lo que varía es cómo se relacionan los objetos entre sí: si uno hereda de otro, si una familia de comportamientos se puede intercambiar, si un cambio en uno debería llegar a los demás. Para el tipo de aplicación que construimos, sobre el framework en el que la construimos, esa relación resultó funcionar mejor como composables y tablas que como clases. Eso es una afirmación sobre lo que nos resulta práctico a nosotros, no una afirmación de que las clases dejen de ser útiles en otros lugares. Las nuestras siguen existiendo, en el único lugar donde el lenguaje las exige, y de eso trata la siguiente sección.",[11,522,524],{"id":523},"a-dónde-se-fue-la-herencia","A dónde se fue la herencia",[16,526,527],{},"Por curiosidad, buscamos cada lugar donde nuestro código usa herencia. Hay un puñado, y todos son tipos de error, donde el lenguaje la exige.",[16,529,530],{},"Lo que usamos en su lugar es composición, que en nuestro mundo se ve como pequeñas funciones que devuelven las piezas que pediste.",[77,532,534],{"className":79,"code":533,"language":81,"meta":82,"style":82},"const { record, canEdit, save } = useRecordEditor(recordId)\n",[84,535,536],{"__ignoreMap":82},[87,537,538,540,542,545,547,550,552,555,558,560,563],{"class":89,"line":90},[87,539,242],{"class":130},[87,541,374],{"class":93},[87,543,544],{"class":103}," record",[87,546,148],{"class":93},[87,548,549],{"class":103}," canEdit",[87,551,148],{"class":93},[87,553,554],{"class":103}," save ",[87,556,557],{"class":93},"}",[87,559,383],{"class":93},[87,561,562],{"class":113}," useRecordEditor",[87,564,565],{"class":103},"(recordId)\n",[16,567,568],{},"Esa línea produce su propio objeto de trabajo con su propio estado. Dos editores abiertos al mismo tiempo sostienen dos registros y nunca se ven entre sí. Un desarrollador de Java lee eso y reconoce un objeto con métodos públicos, con el resto guardado en privado.",[16,570,571],{},"El libro que le enseñó a la mayoría de nosotros estos patrones lo plantea como un principio: preferir la composición sobre la herencia. La herencia te ata a un padre y te entrega todo lo que tiene. La composición toma las tres cosas que necesitas. El propio estilo de composables de Vue ya se ajusta de cerca a ese principio, así que nuestro código se asentó ahí sin que nadie tuviera que convocar una reunión. Un equipo que construyera el mismo producto en Java seguiría teniendo razón al recurrir a la herencia en lugares donde nosotros no lo hacemos, porque el ajuste depende tanto del framework como del patrón.",[11,573,575],{"id":574},"cuando-son-tus-clientes-quienes-lo-extienden","Cuando son tus clientes quienes lo extienden",[16,577,578],{},"El patrón en el que más nos apoyamos es aquel donde un cambio se anuncia solo y cualquier cosa interesada reacciona.",[16,580,581],{},"Nuestras reglas de automatización funcionan así, y lo interesante es quién las escribe. Un cliente abre la interfaz y construye una regla: cuando llega un informe para este tipo de activo, notifica a esta cuadrilla y abre una orden de trabajo. Esa regla es una fila en su propia base de datos. Cuando el evento se dispara, el motor encuentra cada regla que lo está observando y ejecuta cada una, manteniendo los fallos separados para que una regla rota deje a las demás funcionando.",[16,583,584],{},"El Observer de los libros de texto tiene a programadores registrando a los observadores. Aquí lo hace el cliente, a través de un formulario, un martes por la tarde, sin ningún despliegue de por medio.",[16,586,587],{},"Ese cambio trae problemas que el libro de texto nunca tuvo que resolver. Una regla puede disparar un evento que activa otra regla, así que cada ejecución lleva un presupuesto para cuánto puede viajar una cadena. Las reglas también llevan sus propios límites de frecuencia, así que un sensor ruidoso deja la cola disponible para todos los demás. Esas dos protecciones existen porque los clientes, con toda razón, construyen cosas que no anticipamos.",[11,589,591],{"id":590},"por-qué-hablamos-de-esto","Por qué hablamos de esto",[16,593,594],{},"La pregunta con la que empezamos era sobre patrones, y la respuesta real es sobre cómo trabajamos.",[16,596,597],{},"Usamos estas ideas todo el tiempo y no las vas a encontrar buscando las palabras clave, porque nuestro lenguaje las escribe distinto. Más útil todavía, tenemos un vocabulario compartido para ellas, así que una decisión de diseño suele ser una conversación corta.",[16,599,600],{},"El toast es el ejemplo más claro. Se escribió en un lenguaje y un framework que apenas se parecen a lo que hoy ejecuta ese código, por personas que no habían leído los mismos libros. La forma debajo de él se sostuvo de todas formas, y sigue sosteniéndose ahora que la misma notificación puede llegar como correo, mensaje de texto o push a las dos de la mañana.",[16,602,603],{},"Eso es lo que vale la pena llevarse. Los patrones sobreviven al lenguaje que se usa para describirlos. Java llama a una cosa una interfaz con clases que la implementan, nosotros la llamamos una tabla de funciones, y el argumento que hace cada uno es idéntico. Aprende el argumento y la sintaxis deja de importar.",[16,605,606],{},"Si estás evaluando si trabajar con nosotros, de cualquiera de los dos lados de la mesa, eso es lo que quisiéramos que sepas. Pensamos en la forma de algo antes de construirlo, escribimos lo que aprendemos, y volvemos a arreglar los lugares donde nos equivocamos. Lo que ven nuestros clientes es que las solicitudes pequeñas siguen siendo pequeñas.",[11,608,610],{"id":609},"resumen","Resumen",[612,613,614,618,621,624,627,630,633,636,639],"ul",{},[615,616,617],"li",{},"Nuestros clientes configuran el producto ellos mismos, así que las solicitudes pequeñas llegan constantemente. El costo de la quincuagésima lo decide la forma del código, años antes de que se pida.",[615,619,620],{},"El valor duradero de los patrones de diseño es un vocabulario compartido. Acordar la forma antes de que exista el código convierte un debate de diseño en una revisión corta.",[615,622,623],{},"Un componente de Vue ya empaqueta lo que empaqueta un objeto: sus propios datos, sus propios métodos, y cómo se presenta. Eso no necesitó la palabra clase, y no es una afirmación de que las clases dejen de importar. Seguimos recurriendo a una exactamente donde el lenguaje lo exige, y un equipo que construyera el mismo producto en Java tendría razón en recurrir a la herencia en lugares donde nosotros no lo hacemos.",[615,625,626],{},"Guardamos el comportamiento en tablas, así que una capacidad nueva es una fila y el motor que lee la tabla se queda tal como está escrito. Nuestro motor de notificaciones se ha mantenido en unas cuarenta líneas a través de cada notificación que hemos agregado.",[615,628,629],{},"Ese motor empezó como un toast en la esquina de una pantalla. El correo, el mensaje de texto y el push trajeron cada uno trabajo real, incluida una interfaz nueva para elegir qué quieres escuchar, y nada de eso cambió la forma de fondo.",[615,631,632],{},"La herencia está prácticamente ausente de nuestro frontend. Pequeñas funciones componibles devuelven las piezas que necesita una pantalla, cada una con su propio estado.",[615,634,635],{},"Nuestras reglas de automatización son los propios observadores del cliente, guardados como datos. Eso necesita protecciones que una versión de libro de texto no tiene: un límite a cuánto puede viajar una cadena de reglas, y un límite de frecuencia por regla.",[615,637,638],{},"Mantenemos una lista escrita de las formas que usamos, y volvemos a los lugares que son anteriores a ella.",[615,640,641],{},"Los patrones sobreviven al lenguaje que se usa para describirlos. La sintaxis difiere entre Java y JavaScript. El argumento que hace cada patrón no.",[643,644,645],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":82,"searchDepth":110,"depth":110,"links":647},[648,649,650,651,652,653,654,655,656],{"id":13,"depth":110,"text":14},{"id":30,"depth":110,"text":31},{"id":46,"depth":110,"text":47},{"id":65,"depth":110,"text":66},{"id":195,"depth":110,"text":196},{"id":523,"depth":110,"text":524},{"id":574,"depth":110,"text":575},{"id":590,"depth":110,"text":591},{"id":609,"depth":110,"text":610},"2026-08-30","¿Qué patrones de diseño usan? Nuestro frontend casi no tiene clases, así que la respuesta honesta tomó un rato. Terminó siendo una buena descripción de cómo decidimos qué construir y cómo hablamos entre nosotros al respecto.","md",{},"/blog/object-oriented-without-classes/es","2026-09-01",{"title":5,"description":658},"blog/object-oriented-without-classes/es",[666,667,668,669],"engineering","how-we-work","design-patterns","product","7VNHqvJzfBJbu006p5YGN_Wl0ZHzBGKGtNiLRX8L_D0",1788431620815]