{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Programmation objet en Scala\n", "\n", "Nous avons vu les bases de la programmation objet en Scala : les classes, les singletons, les paramètres...\n", "Nous abordons la programmation objet plus en détail, notamment les mécanismes d'héritage et de composition de classes.\n", "\n", "## Visibilité des membres\n", "\n", "Les membres d'une classe peuvent être `public`, `protected` ou `private`, avec la même signification qu'en Java. \n", "Notez que les classes et objets ont accès aux membres (même `private`) de leurs compagnons s'ils en ont un." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "baz = 0\n", "baz = 1\n" ] } ], "source": [ "class Foo {\n", " val baz = Foo.bar\n", " Foo.bar += 1\n", "\n", " def print_baz() = println(\"baz = \" + baz)\n", "}\n", "object Foo {\n", " private var bar = 0\n", "}\n", "\n", "val f = new Foo()\n", "f.print_baz()\n", "val g = new Foo()\n", "g.print_baz()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Héritage\n", "\n", "Lors de la définition d'une classe, le mot-clef `extends` permet de spécifier une super-classe de laquelle dériver.\n", "Une sous-classe peut redéfinir des méthodes héritées de sa classe parente, auquel case il faut indiquer la surcharge avec le mot-clef `override`." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bonjour\n" ] } ], "source": [ "class Person {\n", " // Use English by default\n", " def sayHello() { println(\"Hello\") }\n", "}\n", "class FrenchMan extends Person {\n", " override def sayHello() { println(\"Bonjour\") }\n", "}\n", "val f = new FrenchMan()\n", "f.sayHello()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On peut aussi définir des classes abstraites, qui ne fournissent pas d'implémentation pour certains (voire tous) de ses membres.\n", "Les classes abstraites correspondent à peu près aux interfaces de Java. \n", "Les classes abstraites doivent être marquées par le mot-clef `abstract`." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [], "source": [ "// abstract class: 'draw' and 'position' are both abstract\n", "// Note that abstract methods do not need to have types\n", "abstract class Shape {\n", " def draw()\n", " val position: Int\n", "}\n", "// abstract class: provides an implementation for 'draw' but not for 'position'\n", "abstract class Square extends Shape {\n", " def draw() { println(\"I am a square\") }\n", "}\n", "// concrete class\n", "class CenteredSquare(c: Int) extends Square {\n", " val center: Int = c\n", " val position = center\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Traits\n", "\n", "Un _trait_ est une interface.\n", "Contrairement à une classe (abstraite ou concrète), un trait ne peut pas avoir de paramètres.\n", "En dehors de cette particularité, un trait est quasiment équivalent à une classe abstraite.\n", "Même s'il fournit une implémentation pour tous ces membres, un trait est considéré comme abstrait : on ne peut pas l'instancier.\n", "\n", "Les traits peuvent hériter de classes et/ou d'autres traits.\n", "La redéfinition d'un membre d'un trait parent doit être marqué `abstract override`." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [], "source": [ "// reimplement the previous example with traits rather than abstract classes\n", "// this is straight-forward because the abstract classes have no parameters\n", "trait Shape {\n", " def draw() { println(\"I am a shape\") }\n", " val position: Int\n", "}\n", "trait Square extends Shape {\n", " abstract override def draw() { println(\"I am a square\") }\n", "}\n", "class CenteredSquare(c: Int) extends Square {\n", " val center: Int = c\n", " val position = center\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Composition\n", "\n", "Scala, comme Java, n'autorise pas l'héritage multiple : on ne peut dériver que d'une classe à la fois.\n", "Les traits permettent cependant un mécanisme de _composition_ (_mixin_ en anglais), qui s'approche des fonctionnalités offertes par l'héritage multiple en évitant ses inconvénients (notamment le problème du diamant). \n", "Une classe ou un trait peut dériver d'_au plus une classe_, mais de _plusieurs traits_. \n", "Il est courant de dériver d'une classe et d'un ou plusieurs traits : la classe parente permet de définir des paramètres de classe (que les traits n'autorisent pas), tandis que les traits décrivent les interfaces implémentées par la classe. \n", "Par rapport aux interfaces Java, les traits peuvent fournir une implémentation : ils ne décrivent pas seulement des interfaces mais apporte aussi des fonctionnalités." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hi! My name is Jean.\n", "I am an employee.\n", "I am French.\n" ] } ], "source": [ "trait Person {\n", " val name: String\n", " def sayHi() { println(\"Hi! My name is \" + name + \".\") } \n", "}\n", "trait Employee extends Person {\n", " abstract override def sayHi() { super.sayHi(); println(\"I am an employee.\") }\n", "}\n", "trait French extends Person {\n", " abstract override def sayHi() { super.sayHi(); println(\"I am French.\") }\n", "}\n", "\n", "class FrenchEmployee(n: String) extends Employee with French {\n", " override val name = n\n", "}\n", "val jean = new FrenchEmployee(\"Jean\")\n", "jean.sayHi()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`class A extends B with C with D ... with Y with Z { ... }` définit une hiérarchie dont la racine est `B`, `C` dérive de `B`, ...`Z` dérive de `Y` et `A` dérive de `Z`. \n", "Pour vous convaincre de l'ordre, essayer d'échanger l'ordre de `French` et `Employee` dans l'exemple ci-dessus.\n", "\n", "# Constructeurs\n", "\n", "Rappelons que le constructeur d'une classe est tout le corps de la classe.\n", "Rappelons aussi qu'une classe (mais pas les traits) peut avoir des paramètres, qui se comportent comme les arguments de son constructeur." ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n", "(4,3)\n", "5\n" ] }, { "data": { "text/plain": [ "Name: Compile Error\n", "Message: :23: error: value z is not a member of C\n", " println(c.z) // error\n", " ^\n", "StackTrace: " ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class A(var x: Int) // x is a class parameter and is declared as a mutable attribute\n", "val a = new A(2)\n", "println(a.x)\n", "\n", "class B(var y: Int) extends A(y+1) // y is a class parameter of B declared as a mutable attribute\n", "val b = new B(3)\n", "println(b.x, b.y)\n", "\n", "class C(z: Int) extends A(z+1) // z is a class parameter of C, and is NOT an attribute\n", "val c = new C(4)\n", "println(c.x)\n", "println(c.z) // error" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercice\n", "\n", "On va utiliser des traits pour définir des fonctions arithmétiques sur les entiers.\n", "\n", "1. Définissez une classe `IntFun` qui a une méthode `run`, de type `Int => Int` et qui à tout `x` associe `x`.\n", "2. Définissez un trait `Incr` étendant `IntFun`, dont la méthode `run` renvoie le résultat de `run` de sa super-classe incrémenté de `1`. Pensez au mot-clef `override`.\n", "3. De même, définissez un trait `Double` dont la méthode `run` double le résultat de `run` de sa super-classe.\n", "4. Définissez, par héritage uniquement, une classe `DoubleIncr` dont la méthode `run(x)` renvoie `2x+1`.\n", "5. Définissez, par héritage uniquement, une classe `IncrDouble` dont la méthode `run(x)` renvoie `2(x+1)`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercice\n", "\n", "Pour implémenter un jeu vidéo, on a besoin de représenter les classes de personnages ainsi que les pouvoirs afférents. Tous les personnages ont des caractéristiques communes : nom, sexe, taille, points de vie, force, dextérité. Les races ont des caractéristiques propres : \n", "- les nains ont une taille inférieure à la moyenne, mais sont plus forts et plus résistants (ce qui se traduit par une force et un nombre de points de vie augmentés) ;\n", "- les elfes ont une taille normale, mais sont plus agiles que la moyenne. En outre, ils sont nyctalopes ;\n", "- les humains n'ont rien de particulier.\n", "De même, les classes ont des caractéristiques propres :\n", "- les guerriers ont une force plus élevée que la moyenne ;\n", "- les magiciens disposent de points de magie, ce qui leur ouvre la possibilité de lancer des sorts ;\n", "- les rôdeurs sont plus agiles que la moyenne.\n", "\n", "Il faut refléter tout cela dans un système de classes.\n", "1. Définissez une classe `Personnage`, avec ses attributs.\n", "2. Un personnage a forcément une race et une classe. Vaut-il mieux implémenter les classes et les races comme des nouvelles classes ou comme des traits ?\n", "2. Définissez les races.\n", "3. Définissez les classes.\n", "4. Créez une instance de guerrier nain, et vérifiez que ses bonus de force (de race et de classe) se sont bien cumulés.\n", "5. Les personnages peuvent être incarnés par un joueur (PJ) ou contrôlés par le jeu (PNJ). Cette distinction est importante car les interactions avec les PNJ sont plus restreintes qu'avec les PJ (ce qui se traduit par des interfaces différentes). Comment implémentez la distinction PJ/PNJ ?\n", "6. Certains personnages disposent de pouvoirs exceptionnels, indépendamment de leur classe ou race. Implémentez un de ces pouvoirs (par exemple force surhumaine)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Apache Toree - Scala", "language": "scala", "name": "apache_toree_scala" }, "language_info": { "name": "scala", "version": "2.10.4" } }, "nbformat": 4, "nbformat_minor": 2 }