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....


Aucun commentaire:

Enregistrer un commentaire