diff --git a/2024/objetos/clase-20/index.html b/2024/objetos/clase-20/index.html index 6d60c1c6..da070b55 100644 --- a/2024/objetos/clase-20/index.html +++ b/2024/objetos/clase-20/index.html @@ -173,7 +173,7 @@

  • En común, va en una superclase.
  • En específico, va en la clase.
  • -
  • En común con la superclase pero que hace _alguito más, va redefinido con super en la clase.
  • +
  • En común con la superclase pero que hace alguito más, va redefinido con super en la clase.
  • Que no me dice qué hacer la superclase, lo defino como método abstracto en la superclase y luego lo redefino en clase.
  • Si tengo clases que:

    diff --git a/page-data/2024/objetos/clase-20/page-data.json b/page-data/2024/objetos/clase-20/page-data.json index 3721364b..17d0de8a 100644 --- a/page-data/2024/objetos/clase-20/page-data.json +++ b/page-data/2024/objetos/clase-20/page-data.json @@ -1,5 +1,5 @@ { "componentChunkName": "component---src-templates-blog-post-js", "path": "/2024/objetos/clase-20/", - "result": {"data":{"site":{"siteMetadata":{"title":"Bitácora","author":"pdep"}},"markdownRemark":{"id":"74f4ff7f-5eb4-5ace-8d13-8bd78caf373e","excerpt":"Tarea para la clase que viene: Comenzar la segunda entrega del TP de objetos. Realizar los tests correspondientes. Se debe entregar en tiempo y forma el lunes…","html":"

    Tarea para la clase que viene:

    \n\n

    Herencia

    \n

    Anteriormente vimos que cuando dos objetos repiten lógica, crear una clase puede que sea nuestra solución. Pero, ¿qué hacemos cuando dos clases repiten lógica? Esto es un trabajo para… ¡la superclase! 🦸‍♀️🦸‍♂️

    \n

    Al tener lógica repetida entre clases podemos crear una nueva clase con esa lógica, dejando en cada una de las clases iniciales sólo lo particular de cada una.

    \n

    Por ejemplo:

    \n

    Los perros y los gatos al jugar pierden unidades de energía según el tiempo que reciben por parámetro. Al pedirles que emitan un sonido los perros hacen guau (sí, todo muy original) y los gatos… ¡MUUU! 😲 (no, mentira, hacen miau pero casi se la creen 😂).\nPero al llegar su dueño o dueña a casa actúan distinto. Los gatos 🐈 actúan con indiferencia, es decir, no hacen nada. Los perros 🐕 en cambio aumentan en 100 su energía.

    \n

    Un código posible podría ser:

    \n
    class Gato {\n\tvar energia\n\t\n\tmethod jugar(unTiempo) {\n\t\tenergia -= unTiempo\n\t}\n\n\tmethod emitirSonido() {\n\t\treturn \"miau\"\n}\n\nmethod recibirDueño() {\n}\n}\n\nclass Perro {\n\tvar energia\n\t\n\tmethod jugar(unTiempo) {\n\t\tenergia -= unTiempo\n\t}\n\n\tmethod emitirSonido() {\n\t\treturn \"guau\"\n}\n\nmethod recibirDueño() {\n\tenergia += 100\n}\n}
    \n

    ¿Esa lógica repetida no les hizo doler los ojos? 😵

    \n

    Una solución sería crear una clase Animal (no es una frase onda “CREA UNA CLASE ANIMAL!!! MÁQUINA!!! 💪”) que contenga la lógica repetida:

    \n
    class Animal {\n\tvar energia\n\tconst sonido\n\t\n\tmethod jugar(unTiempo) {\n\t\tenergia -= unTiempo\n\t}\n\n\tmethod emitirSonido() {\n\t\treturn sonido\n}\n}
    \n

    Lo único que faltaría es establecer una relación entre esta nueva clase y las originales definiendo herencia de la siguiente manera: 👇

    \n
    class Perro inherits Animal(sonido = \"guau\") {\n\tmethod recibirDueño() {\n\tenergia += 100\n}\n}\n\nclass Gato inherits Animal(sonido = \"miau\") {\n\tmethod recibirDueño() { }\n}
    \n

    Listo, ¡problema solucionado! 🙌 Ahora vamos a decir que Animal es la superclase de Perro y Gato o, de otra manera, que Perro y Gato son subclases de Animal. Hay que tener en cuenta que cada clase solo puede heredar de una y solo una clase.

    \n

    Super

    \n

    Siguiendo con nuestro ejemplo, imaginémonos que aparece la clase Gallina 🐔, cada Gallina emite el sonido “A River lo sigo a donde va” y cuando juegan también pierde energia, peeero también ponen un huevo 🐣. Entonces tendríamos que redefinir el método jugar, pero teniendo en cuenta que una parte de la lógica ya está definida en la superclase Animal. Para hacer esto vamos a combinar override (para redefinir un método de la superclase) con super (para ver que hace la superclase):

    \n
    class Gallina inherits Animal(sonido = \"A River lo sigo a donde va\") {\n\tvar huevosPuestos = 0\n\n\toverride method jugar(unTiempo) {\n\tsuper(unTiempo)\n\thuevosPuestos ++\n}\n}
    \n

    Podemos aprovechar super tanto para métodos que retornan algo (para obtener ese algo) como para métodos que no retornan nada (para ejecutar su comportamiento).

    \n

    Redefinición

    \n

    Acá vemos que la Gallina no tiene el método recibirDueño, ¿debería? 🤔

    \n

    Esto es una decisión de nuestro diseño, si creemos que todos los animales deberían poder recibir dueños (que los animales deban obligatoriamente tener dueños pertenece a un debate que no vamos a tener, recordemos que esto es meramente un ejemplo), debería estar presente en nuestro código. ¿Pero qué hace un animal cualquiera al recibir a su dueño? ¿Hay alguna lógica en común entre todos los animales? ¿Qué escribo en la superclase?

    \n

    Claramente no conocemos un comportamiento genérico para todos los animales, pero si queremos que todos los animales sepan recibir a su dueño sin especificar una lógica podemos crear un método abstracto escribiendo solo la firma de la siguiente manera:

    \n
    class Animal {\n\t….\n\tmethod recibirDueño()\n\t….\n}
    \n

    ❗ ❗ Es importante diferenciar recibirDueño() de recibirDueño() { }. El segundo no es un método abstracto sino un método vacío. Aquellas clases que no tiene sentido instanciarlas en nuestro dominio son llamadas clases abstractas. Adicionalmente, si una clase tiene un método abstracto, esta no puede ser instanciada. En nuestro ejemplo podemos tener gallinas, gatos y perros pero no animales a secas.

    \n

    Luego de hacer esto es importante redefinir el método en cada subclase con la palabra override:

    \n
    class Perro inherits Animal(sonido = \"guau\") {\n\toverride method recibirDueño() {\n\tenergia += 100\n}\n}\n\nclass Gato inherits Animal(sonido = \"miau\") {\n\toverride method recibirDueño() { }\n}\n\nclass Gallina inherits Animal(sonido = \"A River lo sigo a donde va\") {\nvar huevosPuestos = 0\n\n\toverride method jugar(unTiempo) {\n\tsuper(unTiempo)\n\thuevosPuestos ++\n}\n\n\toverride method recibirDueño() { \n\thuevosPuestos = 0 /* el dueño llega y le roba los huevos*/\n}\n}
    \n

    Method lookup

    \n

    Hasta ahora vimos que cuando le enviamos un mensaje a un well known object, este busca el método en la definición de ese objeto 🔎. Si se lo enviamos a una instancia, lo busca en la clase a la que pertenece. Esto sigue siendo correcto, pero también aprendimos que si el método no está definido en la clase de la cual el objeto es instancia buscará en la superclase, y en caso que no esté seguirá buscando “para arriba” en la jerarquía de clases 🕵️‍♀️🕵️‍♂️. En caso que la superclase más super de todas, es decir, la clase Object no defina ese método, obtendremos el famoso error wollok.lang.MessageNotUnderstoodException.

    \n

    Interfaces

    \n

    Una interfaz es un contrato que cumplen dos o más clases u objetos la cual obliga a que estos cumplan con la implementación de un conjunto de métodos.

    \n

    Cuando dos objetos o clases tienen una interfaz en común, es posible para un tercero utilizar sus instancias de forma polimórfica, aunque recordemos que el polimorfismo solo está si efectivamente un tercero interactúa indistintamente con ellos.

    \n

    Las interfaces nos sirven para explicitar conjunto de mensajes que deben entender quienes las implementen. En algunos lenguajes, las interfaces se escriben en código y existen validaciones de compilación para asegurar su cumplimiento. En Wollok no se escriben en el código, pero en la materia siempre vamos a explicitar las interfaces en el diagrama de clases.

    \n

    ¿Cuál es la diferencia entre una interfaz y una clase abstracta? Si bien son similares, la clase abstracta tiene como objetivo reutilizar comportamiento, ya que siempre la tenemos en código y la idea es que otras clases puedan heredar de la misma implementando sus métodos. En cambio, el concepto de la interfaz es demostrar partes comunes entre clases u objetos para que puedan ser utilizados polimórficamente.

    \n

    Diagrama de clases

    \n

    Como vimos la clase pasada, el diagrama de clases es una herramienta que nos permite modelar nuestra solución a partir de un esquema. En el mismo encontraremos las clases, objetos e interfaces, sus atributos, sus métodos y cómo se relacionan estos componentes. Es una manera de representar nuestras soluciones más allá del código.

    \n

    En esta clase repasamos cómo representar clases abstractas, interfaces y las flechas de “hereda de”, “usa”, “conoce” e “implementa”.

    \n

    Mini machete

    \n

    Todo el comportamiento:

    \n\n

    Si tengo clases que:

    \n\n

    Si tengo una clase que:

    \n\n

    Se puede heredar tanto de clases abstractas como de clases concretas.

    \n\n","frontmatter":{"title":"Herencia","date":"23-09-2024","description":"Vigésima clase de PdeP","tags":["objetos","herencia","super","clases abstractas","redefinición","diagrama de clases","interfaces"]}}},"pageContext":{"slug":"/2024/objetos/clase-20/","previous":{"fields":{"slug":"/2024/objetos/clase-19/"},"frontmatter":{"title":"Clases y diagrama de clases","date":"16-09-2024"}},"next":null}}, + "result": {"data":{"site":{"siteMetadata":{"title":"Bitácora","author":"pdep"}},"markdownRemark":{"id":"74f4ff7f-5eb4-5ace-8d13-8bd78caf373e","excerpt":"Tarea para la clase que viene: Comenzar la segunda entrega del TP de objetos. Realizar los tests correspondientes. Se debe entregar en tiempo y forma el lunes…","html":"

    Tarea para la clase que viene:

    \n\n

    Herencia

    \n

    Anteriormente vimos que cuando dos objetos repiten lógica, crear una clase puede que sea nuestra solución. Pero, ¿qué hacemos cuando dos clases repiten lógica? Esto es un trabajo para… ¡la superclase! 🦸‍♀️🦸‍♂️

    \n

    Al tener lógica repetida entre clases podemos crear una nueva clase con esa lógica, dejando en cada una de las clases iniciales sólo lo particular de cada una.

    \n

    Por ejemplo:

    \n

    Los perros y los gatos al jugar pierden unidades de energía según el tiempo que reciben por parámetro. Al pedirles que emitan un sonido los perros hacen guau (sí, todo muy original) y los gatos… ¡MUUU! 😲 (no, mentira, hacen miau pero casi se la creen 😂).\nPero al llegar su dueño o dueña a casa actúan distinto. Los gatos 🐈 actúan con indiferencia, es decir, no hacen nada. Los perros 🐕 en cambio aumentan en 100 su energía.

    \n

    Un código posible podría ser:

    \n
    class Gato {\n\tvar energia\n\t\n\tmethod jugar(unTiempo) {\n\t\tenergia -= unTiempo\n\t}\n\n\tmethod emitirSonido() {\n\t\treturn \"miau\"\n}\n\nmethod recibirDueño() {\n}\n}\n\nclass Perro {\n\tvar energia\n\t\n\tmethod jugar(unTiempo) {\n\t\tenergia -= unTiempo\n\t}\n\n\tmethod emitirSonido() {\n\t\treturn \"guau\"\n}\n\nmethod recibirDueño() {\n\tenergia += 100\n}\n}
    \n

    ¿Esa lógica repetida no les hizo doler los ojos? 😵

    \n

    Una solución sería crear una clase Animal (no es una frase onda “CREA UNA CLASE ANIMAL!!! MÁQUINA!!! 💪”) que contenga la lógica repetida:

    \n
    class Animal {\n\tvar energia\n\tconst sonido\n\t\n\tmethod jugar(unTiempo) {\n\t\tenergia -= unTiempo\n\t}\n\n\tmethod emitirSonido() {\n\t\treturn sonido\n}\n}
    \n

    Lo único que faltaría es establecer una relación entre esta nueva clase y las originales definiendo herencia de la siguiente manera: 👇

    \n
    class Perro inherits Animal(sonido = \"guau\") {\n\tmethod recibirDueño() {\n\tenergia += 100\n}\n}\n\nclass Gato inherits Animal(sonido = \"miau\") {\n\tmethod recibirDueño() { }\n}
    \n

    Listo, ¡problema solucionado! 🙌 Ahora vamos a decir que Animal es la superclase de Perro y Gato o, de otra manera, que Perro y Gato son subclases de Animal. Hay que tener en cuenta que cada clase solo puede heredar de una y solo una clase.

    \n

    Super

    \n

    Siguiendo con nuestro ejemplo, imaginémonos que aparece la clase Gallina 🐔, cada Gallina emite el sonido “A River lo sigo a donde va” y cuando juegan también pierde energia, peeero también ponen un huevo 🐣. Entonces tendríamos que redefinir el método jugar, pero teniendo en cuenta que una parte de la lógica ya está definida en la superclase Animal. Para hacer esto vamos a combinar override (para redefinir un método de la superclase) con super (para ver que hace la superclase):

    \n
    class Gallina inherits Animal(sonido = \"A River lo sigo a donde va\") {\n\tvar huevosPuestos = 0\n\n\toverride method jugar(unTiempo) {\n\tsuper(unTiempo)\n\thuevosPuestos ++\n}\n}
    \n

    Podemos aprovechar super tanto para métodos que retornan algo (para obtener ese algo) como para métodos que no retornan nada (para ejecutar su comportamiento).

    \n

    Redefinición

    \n

    Acá vemos que la Gallina no tiene el método recibirDueño, ¿debería? 🤔

    \n

    Esto es una decisión de nuestro diseño, si creemos que todos los animales deberían poder recibir dueños (que los animales deban obligatoriamente tener dueños pertenece a un debate que no vamos a tener, recordemos que esto es meramente un ejemplo), debería estar presente en nuestro código. ¿Pero qué hace un animal cualquiera al recibir a su dueño? ¿Hay alguna lógica en común entre todos los animales? ¿Qué escribo en la superclase?

    \n

    Claramente no conocemos un comportamiento genérico para todos los animales, pero si queremos que todos los animales sepan recibir a su dueño sin especificar una lógica podemos crear un método abstracto escribiendo solo la firma de la siguiente manera:

    \n
    class Animal {\n\t….\n\tmethod recibirDueño()\n\t….\n}
    \n

    ❗ ❗ Es importante diferenciar recibirDueño() de recibirDueño() { }. El segundo no es un método abstracto sino un método vacío. Aquellas clases que no tiene sentido instanciarlas en nuestro dominio son llamadas clases abstractas. Adicionalmente, si una clase tiene un método abstracto, esta no puede ser instanciada. En nuestro ejemplo podemos tener gallinas, gatos y perros pero no animales a secas.

    \n

    Luego de hacer esto es importante redefinir el método en cada subclase con la palabra override:

    \n
    class Perro inherits Animal(sonido = \"guau\") {\n\toverride method recibirDueño() {\n\tenergia += 100\n}\n}\n\nclass Gato inherits Animal(sonido = \"miau\") {\n\toverride method recibirDueño() { }\n}\n\nclass Gallina inherits Animal(sonido = \"A River lo sigo a donde va\") {\nvar huevosPuestos = 0\n\n\toverride method jugar(unTiempo) {\n\tsuper(unTiempo)\n\thuevosPuestos ++\n}\n\n\toverride method recibirDueño() { \n\thuevosPuestos = 0 /* el dueño llega y le roba los huevos*/\n}\n}
    \n

    Method lookup

    \n

    Hasta ahora vimos que cuando le enviamos un mensaje a un well known object, este busca el método en la definición de ese objeto 🔎. Si se lo enviamos a una instancia, lo busca en la clase a la que pertenece. Esto sigue siendo correcto, pero también aprendimos que si el método no está definido en la clase de la cual el objeto es instancia buscará en la superclase, y en caso que no esté seguirá buscando “para arriba” en la jerarquía de clases 🕵️‍♀️🕵️‍♂️. En caso que la superclase más super de todas, es decir, la clase Object no defina ese método, obtendremos el famoso error wollok.lang.MessageNotUnderstoodException.

    \n

    Interfaces

    \n

    Una interfaz es un contrato que cumplen dos o más clases u objetos la cual obliga a que estos cumplan con la implementación de un conjunto de métodos.

    \n

    Cuando dos objetos o clases tienen una interfaz en común, es posible para un tercero utilizar sus instancias de forma polimórfica, aunque recordemos que el polimorfismo solo está si efectivamente un tercero interactúa indistintamente con ellos.

    \n

    Las interfaces nos sirven para explicitar conjunto de mensajes que deben entender quienes las implementen. En algunos lenguajes, las interfaces se escriben en código y existen validaciones de compilación para asegurar su cumplimiento. En Wollok no se escriben en el código, pero en la materia siempre vamos a explicitar las interfaces en el diagrama de clases.

    \n

    ¿Cuál es la diferencia entre una interfaz y una clase abstracta? Si bien son similares, la clase abstracta tiene como objetivo reutilizar comportamiento, ya que siempre la tenemos en código y la idea es que otras clases puedan heredar de la misma implementando sus métodos. En cambio, el concepto de la interfaz es demostrar partes comunes entre clases u objetos para que puedan ser utilizados polimórficamente.

    \n

    Diagrama de clases

    \n

    Como vimos la clase pasada, el diagrama de clases es una herramienta que nos permite modelar nuestra solución a partir de un esquema. En el mismo encontraremos las clases, objetos e interfaces, sus atributos, sus métodos y cómo se relacionan estos componentes. Es una manera de representar nuestras soluciones más allá del código.

    \n

    En esta clase repasamos cómo representar clases abstractas, interfaces y las flechas de “hereda de”, “usa”, “conoce” e “implementa”.

    \n

    Mini machete

    \n

    Todo el comportamiento:

    \n\n

    Si tengo clases que:

    \n\n

    Si tengo una clase que:

    \n\n

    Se puede heredar tanto de clases abstractas como de clases concretas.

    \n\n","frontmatter":{"title":"Herencia","date":"23-09-2024","description":"Vigésima clase de PdeP","tags":["objetos","herencia","super","clases abstractas","redefinición","diagrama de clases","interfaces"]}}},"pageContext":{"slug":"/2024/objetos/clase-20/","previous":{"fields":{"slug":"/2024/objetos/clase-19/"},"frontmatter":{"title":"Clases y diagrama de clases","date":"16-09-2024"}},"next":null}}, "staticQueryHashes": ["452372368","63159454"]} \ No newline at end of file