En Scala toute classe peut avoir son compagnon!

Leave a Comment

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.

© Nouhoum TRAORE.. Fourni par Blogger.