Affichage des articles dont le libellé est Développement. Afficher tous les articles
Affichage des articles dont le libellé est Développement. Afficher tous les articles

En Scala toute classe peut avoir son compagnon!

Au commencement était le mot clé static...

Il arrive lors de la définition d’une classe d’avoir besoin de membres (champs ou méthodes) qui ne soient pas liés à une instance particulière de cette classe. En Java le mot clé static est utilisé pour qualifier ce genre de membres. Cela donne la chose suivante dans le code :

package com.nouhoum.tutorials;
 
import java.util.regex.Pattern;
 
public class PhoneNumber {
   private static Pattern PHONE_REGEX = Pattern.compile("0[1-9]([ .-]?[0-9]{2}){4}");
    
   private final String value;
    
   public PhoneNumber(String value) {
       this.value = value;
   }
    
   public String getValue() {
       return value;
   }
    
   public String toString() {
    return value;
   }
    
   public static boolean validate(PhoneNumber phoneNumber) {
       if(PHONE_REGEX.matcher(phoneNumber.value).matches()) 
return true;
else
         return false;
   }
}

Voici un exemple d’utilisation de la classe. Notez la syntaxe utilisée pour accéder aux membres statiques : le nom de la classe (ici PhoneNumber) un point (.) puis le membre statique. Nous verrons la syntaxe est similaire en Scala.
package com.nouhoum.tutorials;
 
import java.util.Arrays;
import java.util.List;
 
public class Main {
   public static void main(String[] args) {
      List<PhoneNumber> numbers = Arrays.asList(
            new PhoneNumber("12345"), new PhoneNumber("0102030405")
         );
          
      for(PhoneNumber number : numbers) {
         if(PhoneNumber.validate(number))
            System.out.println(number + " is valid");
         else
            System.out.println(number + " is not valid");
      }
   }
}

Les membres statiques n’étant liés à aucune instance particulière de la classe on les appelle membres de classe, ils sont plutôt liés à la classe elle-même.


Ouvrons une parenthèse (importante ;-)

Avoir des membres qui ne sont liés à aucune instance d’une classe n’est pas très orienté objet au goût de Scala (et du mien d’ailleurs ;-). Pour remédier à cette entorse aux règles de l’orienté objet Scala introduit un mot-clé object. Ce mot-clé permet de créer un singleton c’est-à-dire un objet dont une seule instance peut exister. Et c’est dans un objet singleton que vont tous les membres statiques (au sens Java) d’un type (class ou trait). Ainsi les membres autrefois statiques sont maintenant membres d’un objet singleton. Ouf l’O.O. est sauvé!!!

object Hello {
  def sayHello(name: String) = println("Hello " + name)
}
 
Hello.sayHello("Toto")

Remarquez que ce n’est pas vous qui créez l’instance de l’objet singleton, le runtime de Scala s’en occupe.


C’est quoi donc un objet compagnon?

Un objet compagnon est d’abord un object (c’est-à-dire un objet singleton). En outre il est associé à un autre type qui soit une classe soit un trait. Non cela ne veut pas dire que la classe va devoir se marier! Il s’agit en fait d’un objet qui a les caractéristiques suivantes:

  • il a le même nom que le type
  • il est défini dans le même fichier source que le type
  • il a accès aux membres privés du type qu’il accompagne

Maintenant voici l’implémentation du premier exemple en utilisant l’objet compagnon:
//File: PhoneNumber.scala
class PhoneNumber(val value: String) {
  override def toString = value
}
 
object PhoneNumber {
  val PhoneRegex = "0[1-9]([ .-]?[0-9]{2}){4}".r
 
  def validate(phone: PhoneNumber) = PhoneRegex.findFirstIn(phone.value) match {
 case Some(s) => true
 case None => false
  }
}

La méthode statique validate() ainsi que la chaîne statique représentant l’expression régulière ont été déplacées dans l’objet compagnon. Voici rapidement un exemple d’utilisation:
//File: Main.scala
val numbers = List(new PhoneNumber("123456789"), new PhoneNumber("0102030405"))
 
numbers.foreach { p: PhoneNumber =>
  if(PhoneNumber.validate(p))
 println(p + " is a valid french phone number")
  else {
    println(p + " is not a valid french phone number")
  }
}


Quelques usages de l’objet compagnon

Création d’objets
Les objets compagnons ont un autre usage assez intéressant : c’est la définition de “factory method” se débarrassant ainsi de l’écriture de new à chaque création d’objet.

val totoNumber = PhoneNumber("123456789")
Cette ligne est sémantiquement équivalente à la suivante :
val totoNumber = PhoneNumber.apply("123456789")

La méthode apply() est implémentée comme suit :
object PhoneNumber {
  def apply(value: String) = new PhoneNumber(value)
}

L'usage d'apply() dans les collections rend leur usage très agréable.

Les extracteurs
Pour faire vite les extracteurs vous donnent un moyen de faire du pattern sur des objets quelconques (c’est-à-dire qui ne sont pas des instances de case classes). C'est ici pour en savoir plus.


Conclusion

Les case classes tirent pleinement partie des objets compagnons car même si vous ne le voyez pas lorsque vous définissons une case classe le compilateur lui crée un objet compagnon avec une méthode unapply() (un extracteur très utile pour le pattern matching), une méthode factory apply() entre autres.

Pour faire simple, si vous êtes un java-iste, mettez vos méthodes et champs statiques dans l’objet compagnon.

Les extracteurs en Scala

Les extracteurs sont une fonctionnalité de Scala permettant de faire du pattern matching sur un objet et d’en extraire des propriétés. On a cette fonctionnalité gratuitement avec les case classes mais pas pour les autres types de classes.

Voici rapidement comment cela se passe avec les case classes. Supposons que nous avons une classe Person définie comme suit:

case class Person(firstName: String, lastName: String)

Nous avons une liste de personnes et souhaitons afficher seulement celles dont le nom est “Cohen” (une simple opération de filtrage quoi ;-)). Voici une solution avec le pattern matching et le fait que Person est une case classe.

val persons = List(Person("Elie", "Cohen"), Person("John", "Dupont"), 
                   Person("Steve", "Woz"), Person("Daniel", "Cohen"))
       
persons foreach { person: Person =>
  person match {
    case Person(_, "Cohen") => println(person)
    case _ => ()
 }
}

Cool tout ça! Mais si notre classe n’est pas une case classe que faisons-nous alors? Les extracteurs viennent à notre secours. Pour illustrer mes propos je vais prendre un exemple volontairement simpliste: une application qui affiche des e-mails.

trait Message

class EmailMessage(
  val from: String,
  val to: String,
  val subject: String,
  val content: String
 ) extends Message

Le composant chargé de l’affichage est défini comme suit:

trait MessageDisplayer {
  def display(messages: Seq[Message])
}

Voici une implémentation du trait MessageDisplayer:

class PrintStreamMessageDisplayer(out: PrintStream) extends MessageDisplayer {
  def display(messages: Seq[Message]) {
    messages foreach { message: Message =>
      message match {
        case EmailMessage(from, to, subject, content) =>  
          out.println("============= EMAIL =============")
          out.println("Subject: " + subject)
          out.println("From : " + from)  
          out.println(content)  
        case _ @ unknownMessageType =>
          out.println("Unkown message type" + unknownMessageType)
      }
    }
  }
}

Cette implémentation utilise un objet PrintStream pour afficher les messages. Notez l’usage du pattern matching sur EmailMessage. Ceci est possible grâce à la définition de la méthode unapply() dans l’objet compagnon (campanion object) de la classe EmailMessage.
Cette méthode est définie comme suit:

def unapply(email: EmailMessage) =
  Some(email.from, email.to, email.subject, email.content)

Elle prend en paramètre un objet de type EmailMessage et retourne objet Some contenant les propriété du message EmailMessage. Cette méthode est invoquée lorsque nous faisons :

message match {
        case EmailMessage(from, to, subject, content) =>  …
        ….
} 

Il existe une autre méthode apply() que l’on peut définir dans l’objet compagnon. Celle-ci permet de créer un objet EmailMessage avec la syntaxe :

val email = EmailMessage("tota@toto.com", 
                         "tata@toto.com", 
                         "Hello!", 
                         "Hi! How do you do?")

Remarquez l’absence de new dans le snippet précédent, il invoque la méthode apply() de l’objet compagnon avec les arguments fournis et celle-ci s’occupe des details de création de l’objet.
Le bout de code précédent est équivalent à

val email = EmailMessage.apply("tota@toto.com",
                         "tata@toto.com",
                         "Hello!",
                         "Hi! How do you do?") 

Code final d’EmailMessage:

class EmailMessage(
 val from: String,
 val to: String,
 val subject: String,
 val content: String
  ) extends Message

object EmailMessage {
  def apply(from: String, to: String, subject: String, content: String) =
 new EmailMessage(from, to, subject, content)
  def unapply(email: EmailMessage) =
 Some(email.from, email.to, email.subject, email.content)
}

PrintStreamMessageDisplayer et son campanion object:

class PrintStreamMessageDisplayer(out: PrintStream) 
     extends MessageDisplayer {
  def display(messages: Seq[Message]) {
    messages foreach { message: Message =>
      message match {
        case EmailMessage(from, to, subject, content) =>  
          out.println("============= EMAIL =============")
          out.println("Subject: " + subject)
          out.println("From : " + from)  
          out.println(content)  
        case _ @ unknownMessageType =>
          out.println("Unkown message type" + unknownMessageType)
      }
    }
  }
}

object PrintStreamMessageDisplayer {
  def apply(out: PrintStream) = new PrintStreamMessageDisplayer(out)
}

Enfin le code pour tester tout ça:
object ExtractorsTutorial extends App {
  val emails = Seq(
    EmailMessage("tota@toto.com", "tata@toto.com", "Hello!", "Hi! How do you do?"),
    EmailMessage("john.doe@noname.com", "jane@who.com", "Hello!", "Dear Jane! Who am I ?"),
    EmailMessage("madmax@domain.com", "borat@example.com", "Go", "Get out the way."))

    val displayer = PrintStreamMessageDisplayer(Console.out)
    displayer.display(emails)
}

Son exécution donne:
============= EMAIL =============
Subject: Hello!
From : tota@toto.com
Hi! How do you do?
============= EMAIL =============
Subject: Hello!
From : john.doe@example.com
Dear Jane! Who am I ?
============= EMAIL =============
Subject: Go
From : madmax@domain.com
Get out the way. 

Fin de voyage pour cette première rencontre avec les extracteurs.

Introduction à GWT - Partie 1 - Présentation de GWT

Cet article est le premier d’une série d’articles d’introduction à Google Web Toolkit – GWT. Je vous propose lors de cette série d’explorer le développement d’applications Ajax avec GWT. Dans un premier temps nous verrons des exemples simples qui nous permettrons de prendre en main l’outil. Ensuite nous verrons des cas plus complexes se rapprochant de ce qu’on peut rencontrer dans le « monde réel ».

Développement d’applications Ajax

Le monde avant GWT


Les applications Ajax ont connu une grande popularité depuis quelques années. Elles constituent une rupture avec les applications Web traditionnels et offrent une meilleure expérience aux utilisateurs. Le socle technologique d’Ajax est composé de (X)HTML, de DOM, de Javascript et de XML.
Afin de faciliter le développement d’applications Ajax de nombreux frameworks Javascript ont vu le jour. Malgré tous les apports de ces frameworks les caractéristiques du langage Javascript (typage dynamique, ….) et les incompatibilités des navigateurs quant au support de ce langage font que développer une application Ajax est, pour les développeurs, un véritable parcours du combattant.

En résumé cela donne:
  • Javascript est un langage puissant mais mal maîtrisé
  • Typage dynamique en Javascript (difficile de détecter les erreurs telles que des fautes de frappe avant l’exécution du script de l’application)
  • Incompatibilité des navigateurs : Le développeur doit traiter à la main les incompatibilités entre les différents navigateurs lors du développement de son application.
L’approche GWT

L’approche de GWT pour développer des applications est pour le moins originale et peut se résumer en résumer en une phrase : Traiter le Javascript comme l’assembleur l’est par les langages de programmation haut niveau tels que le C++. Un autre langage, en occurrence Java, est utilisé pour écrire le code applicatif et GWT se charge de générer le code Javascript pour les plateformes cibles : les navigateurs. Quelles sont les implications d’un tel choix ? C’est ce que nous allons voir sans tarder.

  • Apports du langage Java
Java est un langage très populaire (peut-être le plus populaire) disposant d’une très large communauté et d’un écosystème riche d’outils et de méthodologies éprouvées rendant ainsi le développement d’applications Ajax très productif.
En écrivant son application Ajax directement en langage Java le développeur tire pleinement profit des avantages de la programmation orientée objet : conception modulaire, réutilisation de codes. L’utilisation d’un IDE Java tel qu’Eclipse et les possibilités offertes par celui-ci, à savoir l’auto-complétion, le débogage, le refactoring, et d’outils de tests unitaires (JUnit notamment) permettent de détecter un grand nombre d’erreurs dès la phase de développement de l’application et de gagner en productivité.

  • Apports du compilateur de GWT

En plus du Java et de son écosystème GWT tire indéniablement sa force de son compilateur chargé de générer le code Javascript de l’application à partir de classes Java.
  1. Génération du code Javascript de l’application : Le framework GWT arrive avec un compilateur qui est chargé de générer le code Javascript de l’application à partir des classes Java écrites par le développeur.
  2. Prise en charge des incompatibilités entre navigateurs : Le développeur n’écrivant pas le code de son application en Javascript il ne s’occupe plus du traitement des incompatibilités des navigateurs quant au support de Javascript. La gestion de ces incompatibilités est laissée à la charge du compilateur Java-Javascript de GWT. Ce compilateur génère le code Javascript adéquats pour les principaux navigateurs du marché : Firefox, Chrome, Safari, IE, Opera.
  3. Optimisation du code Javascript généré : Le compilateur GWT ne limite pas à une simple génération de code Javascript mais il opère des optimisations importantes sur le code généré.

L’ensemble de ces éléments contribue à rendre productif et moins complexe le développement d’applications Ajax performantes.

Nous arrivons à la fin de ce premier article. Dans le prochain article nous préparerons notre environnement de travail et créerons notre première application GWT.


Article suivant: Préparation de l'environnement de travail 
© Nouhoum TRAORE.. Fourni par Blogger.