mardi 28 avril 2015

Scala self-type : Expression de dépendances entre les types

Problématique de dépendance entre les types 

Imaginons qu’on ait une voiture à modéliser. Nous allons définir une structure Car. La voiture a un moteur (Engine), quatre roues, un volant, quatre portes, etc. Vous allez définir, à l’intérieur de la structure Voiture d’autres structures pour les différentes parties. Intéressons-nous au moteur de la voiture. Comment exprimons la dépendance entre un moteur et une voiture ?

Solution 1 : Héritage (IS-A)

Par définition, une classe A hérite d’une classe B si A est un type de B (en anglais IS-A, A is a B). La sous-classe A peut redéfinir des méthodes de sa super-classe B afin de les spécialiser. L’héritage représente la relation : EST-UN.

  object FuelType extends Enumeration{
    type FuelType = Value
    val Diesel, Essence, Electric = Value
  }
  
  trait Engine {
    private var running = false
    def start: Unit = {
      if (!running) println("Engine started")
      running = true
    }
    def stop: Unit = {
      if (running) println("Engine stopped")
      running = false
    }
    def isRunning: Boolean = running
    def fuelType: FuelType.FuelType
  }

  trait CarByInheritance extends Engine{
    def drive {
      start
      println("Vroom vroom")
    }
    def park {
      if (isRunning ) println("Break!")
      stop
    }
  }

  trait DieselEngine  extends Engine{
    override def fuelType = FuelType.Diesel
  }

  //Main
  def main(args: Array[String]) {
    //par héritage
    val myCar = new CarByInheritance with DieselEngine
    myCar.drive;
  }
Au niveau conception, c'est étrange de faire hériter un Car à un Engine. C'est l’inconvénient majeur de cette solution : un Car n’est pas un Engine !

Solution 2 : Composition (HAS-A)

La relation de composition est caractérisée par une relation du type : Has a, A un, Possède un, Est composé de, Contient

 trait CarByComposition {
    def engine : Engine
    def drive {
      engine.start
      println("Vroom vroom")
    }
    def park {
      if (engine.isRunning ) println("Break!")
      engine.stop
    }
  }

  //Main
  def main(args: Array[String]) {
    //par composition
    val myEngine = new Engine with DieselEngine
    val myCar = new CarByComposition {
      override val engine = myEngine
    }
    myCar.drive;
 }

Nous remarquons pour cette solution le manque de garantie de l’unicité d’un moteur par voiture. Un même moteur peut être utilisé par plusieurs voitures.

Solution 3 : self-type (REQUIRES-A)

La notion de self-type de scala permet d’exprimer qu’un type voiture (Car) a besoin d’un type moteur (Engine) pour être instancié. Les membres visibles du moteur sont accessibles depuis la voiture. Une voiture n'est pas pour autant un moteur (Engine) (comme dans le cas d’un héritage) mais a juste besoin d'être mixé avec un moteur (Engine). Il est donc impossible d’instancier une voiture sans moteur. Le nouveau design en utilisant self-type :

 trait CarBySelfType {
    this: Engine => // self-type
    def drive {
      start
      println("Vroom vroom")
    }
    def park {
      println("Break!")
      stop
    }
  }
  
  //Main
  def main(args: Array[String]) {
    //par self type
    val myCar = new CarBySelfType with DieselEngine
    myCar.drive;
}

En plus de pouvoir exprimer une dépendance entre les types (Car dépend Engine), scala nous offre également la possibilité d’exprimer la dépendance d’un type à un bloc de code. Dans l’exemple suivant, nous exprimons que le type Car nécessite d’être mixé avec deux méthode start et stop (tout type qui définit ces deux méthodes peut être mixé) :


trait CarBySelfType {
    this: 
    { def start: Unit
      def stop: Unit
    } => // self-type
    def drive {
      start
      println("Vroom vroom")
    }
    def park {
      println("Break!")
      stop
    }
  }
  //Main
  def main(args: Array[String]) {
    //par self type
    val myCar = new CarBySelfType with DieselEngine
    myCar.drive;
}

Pour finir, un type peut dépendre de plusieurs autres types (les types sont séparés par le mot clé with). Par exemple une voiture a besoin d’un moteur (Engine) et une boite de vitesse (GearBox)  :


  trait GearBox {
    def move = println("move")
    def gearBoxType : String
  }


  trait CarBySelfType {
    this: Engine with GearBox=> // self-type
    def drive {
      start
      move
      println("Vroom vroom")
    }
    def park {
      println("Break!")
      stop
    }
  }

  trait AutomaticGearBox  extends GearBox{
    override def gearBoxType = "Automatic"
  }

  //Main
  def main(args: Array[String]) {
    //par self type bloc
    val myCar = new CarBySelfType with DieselEngine with AutomaticGearBox
    myCar.drive;
  }

dimanche 26 avril 2015

Scala & Trait : un concept à clarifier !

La notion de Trait est inspirée des Mix-In du langage Ruby. Un Trait peut être vu comme une interface permettant de définir des méthodes et variables abstraites mais aussi des méthodes concrètes. Le corps d’un trait peut contenir tout ce qu'une classe abstraite peut contenir, comme des attributs et des méthodes :

trait Danse {
    var nature:String
    val nom:String
    
    def danser:Unit
  }

  trait Chante {
    def chanter  = println("Chantons !")
  }

Une classe peut « hériter » (on dit plutôt mix in), de plusieurs traits mais ne peut hériter que d’une seule classe.  Chaque trait représente un service ou un comportement encapsulé qu'il est possible de greffer dynamiquement ou statiquement aux classes héritières.

Les traits peuvent être mixés avec des objets, c’est-à-dire qu’il n’est pas nécessaire qu’une classe hérite d’un trait pour qu’une instance de cette classe puisse recevoir le comportement de ce trait.  Par exemple, on peut créer un objet quelconque et injecter le comportement du trait Chanter :

 val monChanteur = new Object with Chante
   monChanteur.chanter //affiche : Chantons !

Lorsqu’une classe étend/mixe plusieurs trait, on utilise le mot clé  extends pour le premier et le mot clé with pour les autres :


class Rap extends Chante with Danse {
    var nature = "RAP"
    val nom = "rappeur"
    override def danser  = println("YO YO !")
  }

Les traits sont traduits en bytecode par une interface, et le code du trait est ajouté à la classe :


L’exemple suivant nous montre le bytecode décompilé de la classe Rap mixée avec les trait Danse et Chante :
 
//Danse interface
public abstract interface Danse
{
  public abstract String nature();
  public abstract void nature_$eq(String paramString);
  public abstract String nom(); 
  public abstract void danser();
}

//Chante interface
public abstract interface Chante
{
  public abstract void chanter();
}

//Rap class

public class Rap
  implements Chante, Monde.Danse
{
  private String nature;
  private final String nom;
  
  public void chanter()
  {
    Chante.class.chanter(this);
  }
  
  public void nature_$eq(String x$1)
  {
    this.nature = x$1;
  }
  
  public String nature()
  {
    return this.nature;
  }
  
  public Rap()
  {
    Chante.class.$init$(this);
    this.nature = "RAP";
    this.nom = "rappeur";
  }
  
  public String nom()
  {
    return this.nom;
  }
  
  public void danser()
  {
    Predef..MODULE$.println("YO YO !");
  }
}

Contrairement à Java 8 avec le nouveau concept interface et default méthode, un trait peut implémenter une méthode abstraite d’un autre trait :


  trait Bar {
    def bar:Unit
  }

  trait Foo extends Bar{
    def bar = println("implements bar method")
  }

  class FooBar extends Foo with Bar

  def main(args: Array[String]) {
    (new FooBar).bar //affiche : implements bar method
  }

Avec Java 8, une erreur de conflit est générée :


interface Bar {
void bar();
}
interface Foo {
default void bar() {}
}
class FooBar implements Foo, Bar {
// the method bar is considered in conflict and needs to resolved
}


En cas d'un réel conflit (une même méthode avec la même signature définit dans plusieurs traits mixés par une classe), il est possible d'effectuer le choix de la méthode à appeler grâce au mot clé : super[T].method . L'exemple suivant nous montre une classe qui implémente deux traits : Foo et Bar contenant l'implémentation de la méthode bar :


 trait Bar {
    def bar = println("Bar implements bar method")
  }

  trait Foo {
    def bar = println("Foo implements bar method")
  }

  class FooBar extends Foo with Bar{
    def fooBar = super[Foo].bar
    def barBar = super[Bar].bar
    override def bar = super[Bar].bar
  }

  def main(args: Array[String]) {
    (new FooBar).bar //Bar implements bar method
  }


Pour finir, notez que l’ordre de l’apparence d’un trait dans le mixage (héritage) peut avoir un impact lors de l’exécution. La classe concrète est exécutée en dernier …


trait T1 {
println("Trait 1 mixin")
 def m1 = println("in method m1 of T1")
}

trait T2 {
println("Trait 2 mixin")
}

class B {
println("Class B constructor")
}

class A(p:String) extends B with T1 with T2{
println("Class A constructor")
}

//Main
/**
 * Created by mtoure on 18/04/15.
 */
object Main {

  def main(args: Array[String]) {
    val a = new A("A")
  }
}

//Affiche :

Class B constructor
Trait 1 mixin
Trait 2 mixin
Class A constructor

Process finished with exit code 0

dimanche 19 avril 2015

Scala sealed

En Java, le mot-clé final peut être utilisé lors de la déclaration d’un attribut, d’une méthode, ou d’une classe. Les trois cas ont des significations différentes, mais une idée commune : empêcher que quelque chose soit modifié.

Une classe final est une classe qui ne peut pas avoir de filles. Une classe final ne peut pas être étendue : le mécanisme d'héritage est strictement bloqué. Aucune autre possibilité ne peut nous permettre d'hériter d'une classe déclarée final. Un exemple d'une classe Employee qui peut hériter de la classe Person et qui génère une erreur lorsque la classe Person est déclarée final :



Voyons ce que nous propose Scala ...

Scala sealed

Scala nous offre un peu plus de souplesse avec le mot clé sealed. La documentation Scala nous donne une idée sur son utilisation :

« A sealed class may not be directly inherited, except if the inheriting template is defined in the same source file as the inherited class. However, subclasses of a sealed class can inherited anywhere »

sealed peut être utilisé à la fois sur une classe et sur un trait et annonce au compilateur que les classes filles sont toutes dans le même fichier. Le mécanise d'héritage n'est donc pas bloqué. On n'exige simplement que les classe filles soient déclarées dans le même fichier. Un exemple pour illustrer mes propos :


La classe Employee peut, en effet, hériter de la classe Person déclarée sealed, lorsque les deux classes sont dans le même fichier. Une erreur est générée lorsqu'une classe d’un autre fichier tente d’hériter de la classe Person …(exemple de la classe Employee déclarée cette fois-ci dans un fichier autre que le fichier de la classe Person) ....

samedi 18 avril 2015

Scala : Décorez vos classes avec le trait

L'ajout de fonctionnalité à un objet sonne évidemment comme un héritage. C'est en effet, le moyen le plus évident de lui fournir des responsabilités supplémentaires. Prenons un exemple sur un logger avec le diagramme suivant :



Imaginons que nous voulions effectuer de log sous les formats HTML et Json. L’objectif est de pouvoir logger vers la console ainsi que vers de fichiers, le message formaté HTML/Json. La première idée simple à implémenter est d’étendre les classes ConsoleLogger et FileLogger :



Cette solution peut vite exploser le nombre de classes et manque de souplesse. L'héritage n'est pas toujours possible. Vous n'avez en effet pas toujours moyen d'accéder à la classe mère, vous ne l'avez pas nécessairement développée vous-même et elle peut être définie comme étant finale. Vous pouvez aussi remarquer que les responsabilités supplémentaires ne sont pas dynamiques. Le pattern decorator vous aide à ajouter de fonctionnalité dynamiquement à une classe sans la modifier.

Le pattern decorator permet d'ajouter des fonctionnalités à un objet sans avoir à modifier son code source et de façon dynamique. Avec le pattern decorator, notre nouveau diagramme :


Les classes :

Logger :


package com.larbotech.logger;

public interface Logger {
 void log(String msg);
}

ConsoleLogger :


package com.larbotech.logger.impl;

import com.larbotech.logger.Logger;

public class ConsoleLogger implements Logger {

 public void log(String msg) {
  System.out.println(msg);
 }

}

FileLogger :


package com.larbotech.logger.impl;

import com.larbotech.logger.Logger;

public class FileLogger implements Logger {

 public void log(String msg) {
  System.out.println("log in file "+msg);
 }

}

Les décorateurs :

LoggerDecorator :


package com.larbotech.logger;

public class LoggerDecorator implements Logger {

 protected Logger logger;
 
 
 public LoggerDecorator(Logger logger) {
  super();
  this.logger = logger;
 }


 public Logger getLogger() {
  return logger;
 }


 public void setLogger(Logger logger) {
  this.logger = logger;
 }


 public void log(String msg) {
 logger.log(msg);

 }

}

HTMLLogger :


package com.larbotech.logger.impl;

import com.larbotech.logger.Logger;
import com.larbotech.logger.LoggerDecorator;

public class HTMLLogger extends LoggerDecorator{

 public HTMLLogger(Logger logger) {
  super(logger);
 }

 
 @Override
 public void log(String msg) {
  
  logger.log(makeHTML(msg));

  }
 
 public String makeHTML(String dataLine){
  return "<HTML><BODY>" + "<b>" + dataLine + "</b>" + "</BODY></HTML>";
 }
}

JsonLogger :


package com.larbotech.logger.impl;


import com.larbotech.logger.Logger;
import com.larbotech.logger.LoggerDecorator;

public class JsonLogger extends LoggerDecorator{

 public JsonLogger(Logger logger) {
  super(logger);
 }
 
 public String makeJson (String dataline){
  return "{logger.info : "+dataline+"}";
 }
 
 @Override
 public void log(String msg) {
  logger.log(makeJson(msg));

  }

}

Une classe Factory pour instancier nos loggers  LoggerFactory :


package com.larbotech.logger;

import com.larbotech.logger.impl.ConsoleLogger;
import com.larbotech.logger.impl.FileLogger;

public class LoggerFactory {
 
 static Logger toConsoleLogger(){
  return new ConsoleLogger();
 }
 
 static Logger toFileLogger(){
  return new FileLogger();
 }

}

Une classe main pour tester :


package com.larbotech.logger;

import com.larbotech.logger.impl.HTMLLogger;
import com.larbotech.logger.impl.JsonLogger;

public class Main {

 public static void main(String[] args) {
  Logger consoleLogger = LoggerFactory.toConsoleLogger();
  
  HTMLLogger htmlLogger = new HTMLLogger(consoleLogger);
  htmlLogger.log("A message to log");
  
  JsonLogger jsonLogger = new JsonLogger(consoleLogger);
  jsonLogger.log("A message to log");

 }

}


Décorons avec le Trait de Scala
Une utilisation possible de trait en Scala est de pouvoir décorer une classe en ajoutant de nouvelles fonctionnalités ou modifiant les fonctionnalités existantes.
Pour illustrer mes propos, commençons par définir un trait représentant un logger :
package com.larbotech.logger

/**
 * Created by mtoure on 18/04/15.
 */
trait Logger {
  def log(msg:String):Unit
}

Nous définissons ensuite nos décorateurs : HTMLLogger, JsonLogger


package com.larbotech.logger

/**
 * Created by mtoure on 18/04/15.
 */
trait HTMLLogger extends Logger{
 abstract override def log(msg: String): Unit = super.log(makeHTML(msg))
  def makeHTML (dataLine: String) = "<HTML><BODY>" + "<b>" + dataLine + "</b>" + "</BODY></HTML>"
}


package com.larbotech.logger

/**
  * Created by mtoure on 18/04/15.
  */
trait JsonLogger extends Logger{
  abstract override def log(msg: String): Unit = super.log(makeJson(msg))
   def makeJson (dataLine: String) = "{logger.info : "+dataLine+"}"
 }


Un décorateur se définit comme un trait ordinaire. En plus, il doit hériter du trait définissant le log (Logger) et implémenter la méthode du trait parent, tout en déclarant cette implémentation comme abstraite, pour s’assurer que la classe d’implémentation concrète définisse bel et bien l’action réelle. Enfin comme pouvez remarquer, l’implémentation réelle du logger peut être référencée par super.log(…) dans le trait de décoration.


Pour appliquer un décorateur à une classe concrète, il suffit de l’instancier suivie du mot clé with et le trait du décorateur :


package com.larbotech.logger

/**
 * Created by mtoure on 18/04/15.
 */
class ConsoleLogger extends Logger{
  def log(msg: String): Unit = println(msg)
}


package com.larbotech.logger

/**
  * Created by mtoure on 18/04/15.
  */
class FileLogger extends Logger{
   def log(msg: String): Unit = println("log in file "+msg)
 }


package com.larbotech.logger

/**
 * Created by mtoure on 18/04/15.
 */
object Main {

  def main(args: Array[String]) {
   val htmlLogger = new ConsoleLogger() with HTMLLogger
    htmlLogger.log("A message to log")
    val jsonlLogger = new ConsoleLogger() with JsonLogger
    jsonlLogger.log("A message to log")
  }
}

L’appel de la méthode log, fait d’abord appel à la fonction log du décorateur (HTMLLogger, JsonLogger) puis celle de la classe décorée (ConsoleLogger, FileLogger) ....

Vous pouvez même enchaîner plusieurs décorateurs. Imagions qu'on ait besoin d'un autre décorateur permettant d'ajouter des informations du serveur :


package com.larbotech.logger

import java.net.InetAddress

/**
 * Created by mtoure on 18/04/15.
 */
trait ServerInformationLogger extends Logger{
 abstract override def log(msg: String): Unit = super.log(makeServerInformation(msg))
  def makeServerInformation (dataLine: String) = InetAddress.getLocalHost().getHostAddress()+" : " + dataLine
}

Appliquons ce décorateur avec le décorateur HTMLLogger :


package com.larbotech.logger

/**
 * Created by mtoure on 18/04/15.
 */
object Main {

  def main(args: Array[String]) {
   val htmlLogger = new ConsoleLogger() with HTMLLogger with ServerInformationLogger
    htmlLogger.log("A message to log")
  }
}


Notez que l’ordre de l’apparence d’un décorateur dans l’instanciation influe sur son ordre à l’exécution : le premier décorateur qui apparaît est le dernier à être exécuté, et ainsi de suite; la classe concrète est exécutée en dernier....