{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'> Equipo Docente IIC2233 2024, 2026-1</font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Diagrama de Clases](#Diagrama-de-Clases)\n",
    "    1. [Elementos de un diagrama de clases](#Elementos-de-un-diagrama-de-clases)\n",
    "        1. [Clases](#Clases)\n",
    "            1. [Ejemplo Auto](#Ejemplo-Auto)\n",
    "            2. [Ejemplo Mario](#Ejemplo-Mario)\n",
    "        3. [Relaciones](#Relaciones)\n",
    "            1. [Contención](#Contención)\n",
    "            2. [Herencia](#Herencia)\n",
    "2. [Ejemplo Aplicado](#Ejemplo-Aplicado)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Diagrama de Clases\n",
    "\n",
    "El **diagrama de clases** es una herramienta muy útil que permite visualizar fácilmente las clases que componen un sistema, así como también sus atributos, métodos y las interacciones que existen entre ellas. Este tipo de diagrama pertenece al conjunto de herramientas provistas por el *Lenguaje de Modelado Unificado* (UML, *Unified Modeling Language*, en inglés). Aunque UML permite incorporar otros elementos y herramientas para modelar sistemas más complejos, en este curso sólo consideraremos el modelamiento de las clases y sus relaciones más básicas, junto con algunas alteraciones para simplificar este proceso. \n",
    "\n",
    "## Elementos de un diagrama de clases\n",
    "\n",
    "Un diagrama de clases se compone de **clases** y **relaciones**.\n",
    "\n",
    "### Clases\n",
    "\n",
    "Las clases se representan con un rectángulo dividido en tres niveles. El primer nivel contiene el nombre de la clase; el segundo contiene los atributos o variables propias de la clase; y el tercero contiene los métodos propios de la clase. \n",
    "\n",
    "![](img/UML_class.png)\n",
    "\n",
    "**Importante:** originalmente, UML no tiene incorporado la modelación de _properties_, por lo cual, la forma que mostraremos en este curso solo es una convención para poder diferenciar una _propertie_ de un atributo.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Ejemplo Auto\n",
    "\n",
    "Dado el siguiente código que modela un `Auto`:\n",
    "\n",
    "```python\n",
    "class Auto:\n",
    "    \n",
    "    def __init__(self, marca: str, modelo: str, km: int):\n",
    "        self.dueño = None\n",
    "        self.marca = marca\n",
    "        self.modelo = modelo\n",
    "        self._kilometraje = km\n",
    "\n",
    "    @property\n",
    "    def kilometraje(self) -> int:\n",
    "        return self._kilometraje\n",
    "\n",
    "    @kilometraje.setter\n",
    "    def kilometraje(self, nuevo_km: int) -> None:\n",
    "        if nuevo_km > 0:\n",
    "            self._kilometraje = nuevo_km\n",
    "        else:\n",
    "            self._kilometraje = 0\n",
    "        \n",
    "    def conducir(self, kms: int) -> None:\n",
    "        self._kilometraje += kms\n",
    "\n",
    "    def vender(self, nuevo_dueño) -> str:\n",
    "        self.dueño = nuevo_dueño\n",
    "        return self.dueño\n",
    "```\n",
    "\n",
    "Su diagrama de clases se vería así: \n",
    "\n",
    "\n",
    "<img src=\"img/UML_class_ejemplo.png\" width=\"400\">\n",
    "\n",
    "\n",
    "Podemos notar la siguiente información del diagrama:\n",
    "1. Tenemos 4 atributos y 1 _property_.\n",
    "    1. El primer atributo (`dueño`) puede ser un `string`  o bien `None`\n",
    "    2. La _property_ `kilometraje` es del tipo `int` y tiene tanto el `getter` como su `setter`\n",
    "2. Tenemos 2 métodos:\n",
    "    1. El primer método recibe 1 parámetro y no retorna nada.\n",
    "    2. El segundo método recibe 1 parámetro y retorna un `string`\n",
    "  \n",
    "De esta forma, el diagrama de clases nos presenta un esqueleto de lo que es la clase `Auto`.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Ejemplo Mario\n",
    "\n",
    "consideremos que usando OOP queremos modelar el famoso juego [Super Mario Bros](https://es.wikipedia.org/wiki/Super_Mario_Bros.).\n",
    "\n",
    "<img src=\"img/OOP_mario.png\" width=\"600\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Primero, listemos las principales clases involucradas en este juego:\n",
    "\n",
    "1. Juego\n",
    "1. Mario Bros\n",
    "1. Goomba (villano)\n",
    "1. Champiñón (superpoder)\n",
    "1. Ladrillo monedas\n",
    "1. Ladrillo móvil\n",
    "1. Ladrillo estático\n",
    "\n",
    "Por otro lado, tenemos otros personajes que no aparecen en la imagen:\n",
    "\n",
    "1. Bowser (el archienemigo de Mario)\n",
    "1. Princesa\n",
    "\n",
    "Usando los diagramas de clases podemos modelar algunas de estas clases como se muestran a continuación:\n",
    "\n",
    "<img src=\"img/UML_mario_01.png\" width=\"600\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Podemos observar que, para los atributos, se debe especificar su nombre y tipo de variable. Por otro lado, para los métodos se debe especificar su nombre, los parámetros que recibe y el tipo de variable esperado para su valor de retorno. Pueden existir clases sin atributos o sin métodos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Relaciones\n",
    "\n",
    "Los diagramas de clases explican cómo ocurre la interacción entre las clases dentro del sistema que modelamos, las cuales representamos como relaciones. Las más comunes son: **contención** y **herencia**.\n",
    "\n",
    "\n",
    "#### Contención\n",
    "\n",
    "En este tipo de relación, un objeto contiene a otro clase como atributo. Por ejemplo, la clase `Juego` tiene al protagonista como un atributo, y ese protagonista es de la clase `Mario`. \n",
    "\n",
    "Dentro de esta relación, existe la \"composición\" y \"agregación\":\n",
    "\n",
    "* **Composición**:  el tiempo de vida del objeto que componemos *está **condicionado** por el tiempo de vida del objeto que lo incluye*. En otras palabras, **la existencia de los objetos incluidos depende de la existencia del objeto que los incluye**. Consideremos el caso del juego que hemos descrito anteriormente una instancia de Mario solo existe como parte de una instancia del juego, y no tienen sentido en nuestro modelo como clases independientes. Lo mismo pasa con los ladrillos, goombas y champiñones. Si eliminamos el objeto `Juego`, también deberíamos eliminar a `Mario`, y su conjunto de ladrillos.\n",
    "\n",
    "* **Agregación**: el tiempo de vida del objeto que agregamos es **independiente** del tiempo de vida del objeto que lo incluye. Consideremos el caso del juego, digamos que tenemos un objeto de clase `Bowser`, el archienemigo de Mario, y este archienemigo puede tener a su mando muchos objetos de la clase `Goomba` para que sean sus aliados. Por lo tanto, es posible que `Bowser` tenga un atributo llamado `self.goombas = []` que será una lista que irá llenando con objetos de clase `Goomba`. En este caso, que `Bowser` sea destruido no implica que los `Goomba` también deban desaparecer, por cual, `Bowser` contiene a objetos de instancia `Goomba`, pero la vida de estos no dependen de si `Bowser` vive o no.\n",
    "\n",
    "Para efectos de UML, se ocupa un rombo (⧫/◊) para indicar esta relación. Este rombo es **pintado de negro** (⧫) para indicar composición, mientras que solo es un rombo con borde negro y relleno blanco (◊) para agregación. No obstante, para efectos del curso, no haremos distinción entre composición y agregación. Por lo tanto, mientras se ocupe un rombo (ya sea pintado o no), se interpretará como que una clase contiene a la otra, pero sin entrar en el detalle si el tiempo de vida de dicha clase depende de la otra o no.\n",
    "\n",
    "A continuación se muestra un ejemplo de contención aplicado al caso anterior del juego. En este, indicamos que todas las clases están contenidas dentro de `Juego`.\n",
    "\n",
    "<img src=\"img/UML_mario_03.png\" width=\"600\">\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Herencia\n",
    "\n",
    "Recordemos que la herencia es una relación de **especialización y generalización**, donde una **subclase** *hereda* atributos y métodos desde una **superclase**. La subclase posee todos los atributos y métodos de la superclase, pero además puede tener sus propios métodos y atributos específicos.\n",
    "\n",
    "Continuando con el ejemplo anterior, es posible ver que existen muchos tipos de `Ladrillo`: objetos definidos porque el personaje Mario no puede atravesarlos ya que son barreras, y por lo tanto comparten ese **mismo comportamiento** general. Además de este comportamiento general en la interacción, todos tienen posiciones. Podemos pensar entonces que tiene sentido crear una superclase `Ladrillo` y subclases `LadrilloMoneda` y `LadrilloMovil` (`LadrilloEstatico` sería completamente reemplazado por `Ladrillo`). Ahora cuando `Mario` choque con un objeto, solo preguntaremos si es de la superclase `Ladrillo`, en vez de preguntar por todos los tipos posibles de ladrillos. \n",
    "\n",
    "Esta relación de herencia se define gráficamente con una flecha (ᐅ) que apunta hacia la superclase, como muestra la siguiente figura.\n",
    "\n",
    "<img src=\"img/UML_mario_05.png\" width=\"600\">\n",
    "\n",
    "Como notarás, los atributos heredados desde la superclase no se repiten en la representación de las subclases ya que, al tratarse de una relación de herencia, esta repetición o traspaso de atributos está implícita. Lo mismo ocurre con los métodos de la subclase que son heredados: no se escriben nuevamente, sino que se infieren."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Modelo integrado\n",
    "\n",
    "Podemos entonces unir todo el modelamiento descrito anteriormente usando diagramas de clases como se muestra a continuación:\n",
    "\n",
    "<img src=\"img/UML_mario_final.png\" width=\"800\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una vez que está construido el diagrama de clases, tenemos un esqueleto inicial de nuestro programa. A partir del diagrama es fácil construir la primera versión de nuestro código."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Juego:\n",
    "\n",
    "    def __init__(self):\n",
    "        self.timestamp_inicio = 0\n",
    "        self.tiempo_actual = 0\n",
    "        # Al ser relación de contención, creamos a Mario dentro de juego\n",
    "        self.personaje = Mario()\n",
    "        self.puntaje = 0\n",
    "        # Aquí incluiremos goombas que crearemos durante la inicialización del juego\n",
    "        self.goombas = list()\n",
    "        # Aquí incluiremos champiñones que crearemos durante la inicialización del juego\n",
    "        self.champinones = list()\n",
    "        # Aquí incluiremos ladrillos que crearemos durante la inicialización del juego\n",
    "        self.ladrillos = list()\n",
    "\n",
    "    def iniciar_juego():\n",
    "        pass\n",
    "\n",
    "    def finalizar_juego():\n",
    "        pass\n",
    "\n",
    "\n",
    "class Mario:\n",
    "\n",
    "    def __init__(self):\n",
    "        self.posicion_x = 0\n",
    "        self.posicion_y = 0\n",
    "        self.cantidad_de_vidas = 5\n",
    "        # Aquí incluiremos champiñones que Mario obtendrá durante el juego\n",
    "        self.poderes = list()\n",
    "\n",
    "    def avanzar():\n",
    "        pass\n",
    "\n",
    "    def retroceder():\n",
    "        pass\n",
    "\n",
    "    def saltar():\n",
    "        pass\n",
    "\n",
    "    def disparar(poder):\n",
    "        pass\n",
    "\n",
    "\n",
    "class Goomba:\n",
    "\n",
    "    def __init__(self):\n",
    "        self.posicion_x = 0\n",
    "        self.posicion_y = 0\n",
    "        self.vivo = True\n",
    "\n",
    "class Champinon:\n",
    "\n",
    "    def __init__(self, x, y):\n",
    "        self.posicion_x = x\n",
    "        self.posicion_y = y\n",
    "\n",
    "\n",
    "class Ladrillo:\n",
    "\n",
    "    def __init__(self, x, y):\n",
    "        self.posicion_x = x\n",
    "        self.posicion_y = y\n",
    "\n",
    "\n",
    "class LadrilloMoneda(Ladrillo):\n",
    "\n",
    "    def __init__(self, x, y):\n",
    "        super().__init__(x, y)\n",
    "        self.valor_moneda = 10\n",
    "\n",
    "\n",
    "class LadrilloMovil(Ladrillo):\n",
    "\n",
    "    def __init__(self, x, y):\n",
    "        super().__init__(x, y)\n",
    "        self.esconde_champinon = False\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
