{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Chapitre 8 : \"La programmation orientée objet\"\n",
    "\n",
    "### <span class=\"bg-info\">« *On place son rêve si loin, tellement loin, tellement hors des possibilités de la vie, qu’on ne pourrait rien trouver dans la réalité qui le satisfasse ; alors, on se fabrique, de toutes pièces, un objet imaginaire !* »</span>\n",
    "\n",
    "Roger Martin du Gard $-$ Jean Barois"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <span class=\"bg-success\">CE QU'IL FAUT RETENIR</span>\n",
    "\n",
    "- En Python, toutes les données manipulées sont des *instances* de **classes**, des objets. Une classe se définit ainsi :\n",
    "\n",
    "```python\n",
    "class NomDeLaClasse:\n",
    "    <définition de la classe>\n",
    "```\n",
    "\n",
    "- Une classe permet de définir une famille d'objets, avec les *attributs* qui les constituent ainsi que les *méthodes* que l'on peut leur appliquer. On accède aux attributs et aux méthodes d'un objet par une notation pointée, les objets constituant des espaces de noms.\n",
    "\n",
    "- Une classe peut *hériter* de classes parentes, auquel cas elle récupère leurs caractéristiques (attributs, méthodes), peut y ajouter ses caractéristiques propres, et peut éventuellement redéfinir certaines méthodes pour les adapter à son usage. L'héritage est spécifié en indiquant les classes parentes entre parenthèses juste après le nom de la classe : `class Enfant(Parent1, Parent2):`.\n",
    "\n",
    "- Les **méthodes** (ou méthodes d'instance) se définissent comme des fonctions, mais indentées dans la définition de la classe et avec un premier paramètre `self` correspondant à l'objet de la classe qui est manipulé.\n",
    "\n",
    "- Les **méthodes statiques** ne sont pas liées à un objet particulier (pas de paramètre `self`), elles sont définies en les préfixant par un décorateur `@staticmethod` et on y accède en utilisant une notation pointée passant par la classe elle-même ou bien par un objet de la classe. \n",
    "\n",
    "- Les **attributs de classe**, communs à tous les objets de cette classe, se définissent directement au niveau de la classe.\n",
    "\n",
    "- Les **attributs d'instance**, qui ont une valeur propre pour chaque objet de cette classe, se définissent généralement dans une *méthode spéciale d'initialisation* `__init__` en utilisant l'espace de noms `self` correspondant à l'objet.\n",
    "\n",
    "- Des **méthodes spéciales**, de la forme `__xxxx__`, peuvent être définies pour offrir à l'objet des comportements correspondant aux opérateurs et à certaines opérations de Python (cf. [Special method names](https://docs.python.org/3/reference/datamodel.html#special-method-names)).\n",
    "\n",
    "Un exemple pour illustrer tout cela :\n",
    "\n",
    "```python\n",
    "class Personne:\n",
    "    cpt_personnes = 0       # attribut de classe, compteur du nombre d'objets créés\n",
    "    def __init__(self, nom, prenom, politesse):\n",
    "        \"Méthode d'initialisation de l'objet, avec trois paramètres de construction.\"\n",
    "        self.nom = nom                # stockage attribut d'objet\n",
    "        self.prenom = prenom          # idem\n",
    "        self.poli = politesse         # idem\n",
    "        Personne.cpt_personnes += 1   # Modification attribut de classe\n",
    "    def identite(self):\n",
    "        \"Méthode fournissant l'identité de la personne.\"\n",
    "        return self.poli + ' ' + self.prenom + ' ' + self.nom.upper()\n",
    "    @staticmethod\n",
    "    def nombre():\n",
    "        \"Méthode présentant le nombre de personnes.\"\n",
    "        return \"Il y a {} personnes.\".format(Personne.cpt_personnes)\n",
    "    def __str__(self):\n",
    "        \"Méthode de conversion en texte str(objet).\"\n",
    "        # utilisée intrinsèquement par print\n",
    "        return self.nom.upper() + ' ' + self.prenom\n",
    "    def __lt__(self, other):\n",
    "        \"Méthode réalisant l'opérateur self < other.\"\n",
    "        return str(self) < str(other)   # Comparaison avec les chaînes définies\n",
    "                                        # dans __str__.\n",
    "```\n",
    "\n",
    "- Il est possible de connaître l'identité d'un objet avec la fonction standard `id()`, ainsi que son type avec la fonction standard `type()`.\n",
    "\n",
    "- La création d'un objet instance d'une classe se fait simplement en utilisant la classe comme une fonction à laquelle on fournit des arguments correspondant aux paramètres attendus par la méthode `__init__()` de la classe (excepté pour le self).\n",
    "\n",
    "```python\n",
    "p1 = Personne(\"Cordeau\", \"Bob\", \"M.\") \n",
    "p2 = Personne(\"Pointal\", \"Laurent\", \"M.\")\n",
    "print(Personne.nombre())            # Il y a 2 personnes.\n",
    "print(\"Bonjour,\", p1.prenom)        # Bonjour,  Bob\n",
    "print(p2.identite())                # M. Laurent POINTAL\n",
    "print(\"Nous avons\", p1, \"et\", p2)   # Nous avons CORDEAU Bob et POINTAL Laurent\n",
    "```\n",
    "***"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### exo08_01 ✓\n",
    "Créer une classe `Point` contenant deux attributs de coordonnées, `x` et `y`, et initialisée en fournissant les valeurs de ces deux attributs. Prévoir la [méthode standard de conversion en texte](https://docs.python.org/3/reference/datamodel.html#object.__str__) afin de représenter les deux coordonnées entre parenthèses et séparées par une virgule. \n",
    "\n",
    "Ajouter une méthode `distance_origine` qui retourne la distance du point par rapport à l'origine  $\\sqrt{x^2 + y^2}$ (on rappelle l'existence des fonctions racine carrée `sqrt()` et hypothénuse `hypot()` dans le module `math`).\n",
    "\n",
    "Tester cette classe en créant quelques points et en les manipulant."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# exo08_01"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### exo08_02 ✓✓\n",
    "**Rappel de géométrie : ** En géométrie plane, un polygone est une ligne brisée fermée. Elle est composée de segments de droites reliant des sommets. Chaque sommet se situe en un point du plan, repéré par ses coordonnées (x, y), que l'on représentera ici par un objet de la classe `Point` définie à l'exercice précédent.\n",
    "\n",
    "Étendre la classe `Point` précédente en y ajoutant le support de l'[opérateur `-`](https://docs.python.org/3/reference/datamodel.html#object.__sub__) (l'expression \"`p1 - p2`\" appelle la méthode `Point.__sub__(p1, p2)`), en calculant et en retournant la distance entre les deux points. On ajoute également le support de la [méthode spéciale de représentation](https://docs.python.org/3/reference/datamodel.html#object.__repr__) (que l'on définira comme pour le support de conversion en texte).\n",
    "\n",
    "Créer une classe `Polygone` contenant la liste des sommets définissant les limites du polygone. Cette classe est initialisée à partir d'une liste de `Point` fournie en paramètre, correspondant à ces sommets. L'initialisation doit vérifier qu'il y a bien au minimum 3 sommets, et lever une exception `ValueError` si ce nest pas le cas.\n",
    "\n",
    "Ajouter une méthode `perimetre` qui retourne la longueur du périmètre du polygone, c'est-à-dire la somme des distances entre les points consécutifs qui le composent, en pensant à bien refermer le circuit.\n",
    "\n",
    "Ajouter une méthode `limites` qui retourne deux `Point`, le premier localisé limites minimales en abscisse et en ordonnée, le second localisé aux limites maximales en abscisse et en ordonnée.\n",
    "\n",
    "Ajouter deux méthodes `hauteur` et `largeur` qui retournent respectivement la hauteur et la largeur du polygone en considérant ses sommets les plus extrêmes (on peut s'aider de la méthode déjà écrite pour les limites).\n",
    "\n",
    "**Note :** On rappelle le calcul de la distance entre deux points A et B : $\\sqrt{(A_x - B_x)^2 + (A_y - B_y)^2}$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# exo08_02"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### exo08_03 ✓\n",
    "\n",
    "*Cet exercice est plus un exercice de conception (découpage en classes) que de programmation ou d'algorithmique.*\n",
    "\n",
    "Une bibliothèque gère des livres, qui peuvent être soit rangés à leur place sur les étagères suivant la classification adoptée, soit prêtés à des usagers. Il peut arriver que la bibliothèque dispose de plusieurs exemplaires du même ouvrage. Les usagers veulent pouvoir retrouver les livres disponibles, en effectuant des recherches sur les auteurs ou sur les titres, et les localiser. Les usagers peuvent bien sûr emprunter des livres, avec une date limite de retour.\n",
    "\n",
    "Définir une série de classes, avec leurs attributs et leurs méthodes, qui permettent de représenter ces différentes informations ainsi que leurs relations, et de répondre aux besoins de gestion exprimés dans le paragraphe. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# exo08_03"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### exo08_04 ✓✓✓\n",
    "\n",
    "Écrire une classe `Intervalle` qui représente un intervalle entier fermé. Par exemple $[7, 10]$ ou $[-9, -5]$.\n",
    "\n",
    "Pour instancier un intervalle, on doit donc fournir deux bornes entières, inférieure et supérieure :\n",
    "\n",
    "```python\n",
    "i0 = Intervalle(5, 5)\n",
    "i1 = Intervalle(7, 10)\n",
    "i2 = Intervalle(-9, -5)\n",
    "i3 = Intervalle(7, 10)\n",
    "i4 = Intervalle(12, 20)\n",
    "```\n",
    "\n",
    "Afin de sécuriser un peu cette classe, on imposera, dans l'initialiseur (la méthode spéciale `__init__()`), les conditions suivantes :\n",
    "\n",
    "- la borne inférieure sera la plus petite des deux ;\n",
    "- la borne supérieure sera la plus grande des deux ;\n",
    "- si les bornes ne sont pas entières, ou si on ne peut pas les transtyper en `int`, on affiche le problème et on arrête le programme ;\n",
    "- si les deux bornes sont égales, on a un *intervalle vide* défini conventionnellement avec des bornes nulles.\n",
    "\n",
    "Ajouter une méthode spéciale d'affichage permettant de tester :\n",
    "\n",
    "```python\n",
    "print(i0)\n",
    "print(i1)\n",
    "print(i2)\n",
    "print(i3)\n",
    "print(i4)\n",
    "```\n",
    "ainsi que l'affichage d'un intervalle vide avec la notation `<vide>`.\n",
    "\n",
    "Pour pouvoir écrire :\n",
    "```python\n",
    "print(7 in i1)\n",
    "print(7 in i2)\n",
    "print(i1 & i3)\n",
    "print(i1 | i3)\n",
    "print(i1 & i4)\n",
    "```\n",
    "nous avons besoin d'ajouter des *opérations* sur la classe `Intervalle` :\n",
    "\n",
    "- une méthode spéciale [`__contains__`](https://docs.python.org/3/reference/datamodel.html#object.__contains__) qui surcharge l'opérateur `in`. Elle renvoie **`True`** si l'élément est dans l'intervalle, **`False`** sinon ;\n",
    "- une méthode spéciale [`__and__`](https://docs.python.org/3/reference/datamodel.html#object.__and__) qui surcharge l'opérateur `&`. Elle renvoie l'intersection de deux intervalles **quand c'est possible** (sinon elle lève une exception `ValueError`) ;\n",
    "- une méthode spéciale [`__or__`](https://docs.python.org/3/reference/datamodel.html#object.__or__) qui surcharge l'opérateur `|`. Elle renvoie l'union de deux intervalles **quand c'est possible** (sinon elle lève une exception `ValueError`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# exo08_04        "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.7.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
