Je parcourais les diapositives de scala efficaces et il est mentionné à la diapositive 10 de ne jamais utiliser val
dans un trait
pour les membres abstraits et d’utiliser def
place. La diapositive ne mentionne pas en détail pourquoi l’utilisation de la valeur abstraite dans un trait
est un anti-modèle. J’apprécierais que quelqu’un puisse expliquer les meilleures pratiques d’utilisation de val vs def dans un trait pour les méthodes abstraites
Un def
peut être implémenté soit par un def
, un val
, un lazy val
ou un object
. C’est donc la forme la plus abstraite de définition d’un membre. Puisque les traits sont généralement des interfaces abstraites, dire que vous voulez un val
c’est dire comment l’implémentation doit faire. Si vous demandez un val
, une classe d’implémentation ne peut pas utiliser de def
.
Une valeur n’est requirejse que si vous avez besoin d’un identifiant stable, par exemple pour un type dépendant du chemin. C’est quelque chose dont vous n’avez généralement pas besoin.
Comparer:
trait Foo { def bar: Int } object F1 extends Foo { def bar = util.Random.nextInt(33) } // ok class F2(val bar: Int) extends Foo // ok object F3 extends Foo { lazy val bar = { // ok Thread.sleep(5000) // really heavy number crunching 42 } }
Si tu avais
trait Foo { val bar: Int }
vous ne pourriez pas définir F1
ou F3
.
Ok, et pour vous confondre et répondre @ om-nom-nom, l’utilisation de val
abstraites peut entraîner des problèmes d’initialisation:
trait Foo { val bar: Int val schoko = bar + bar } object Fail extends Foo { val bar = 33 } Fail.schoko // zero!!
C’est un problème moche qui, à mon avis, devrait disparaître dans les futures versions de Scala en le fixant dans le compilateur, mais oui, actuellement, c’est aussi une des raisons pour lesquelles il ne faut pas utiliser de val
abstraites.
Edit (janv. 2016): vous êtes autorisé à remplacer une déclaration de valeur abstraite par une implémentation lazy val
, afin d’éviter également l’échec de l’initialisation.
Je préfère ne pas utiliser val
dans traits car la déclaration val a un ordre d’initialisation peu clair et non intuitif. Vous pouvez append un trait à une hiérarchie qui fonctionne déjà et casser tout ce qui a fonctionné auparavant, voir mon sujet: pourquoi utiliser plain val dans les classes non finales
Vous devez garder à l’esprit tout ce qui concerne l’utilisation de cette déclaration de val qui finira par vous conduire à une erreur.
Mais il y a des moments où vous ne pouviez pas éviter d’utiliser val
. Comme @ 0__ l’a mentionné, vous avez parfois besoin d’un identifiant stable et def
n’est pas un.
Je donnerais un exemple pour montrer de quoi il parlait:
trait Holder { type Inner val init : Inner } class Access(val holder : Holder) { val access : holder.Inner = holder.init } trait Access2 { def holder : Holder def access : holder.Inner = holder.init }
Ce code génère l’erreur:
StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found. def access : holder.Inner =
Si vous prenez une minute pour penser que vous comprendriez que le compilateur a une raison de se plaindre. Dans le cas de Access2.access
, il ne pouvait pas dériver le type de retour par quelque moyen que ce soit. def holder
signifie qu’il pourrait être mis en œuvre de manière large. Il pourrait renvoyer différents détenteurs pour chaque appel et que les détenteurs incorporeraient différents types Inner
. Mais la machine virtuelle Java s’attend à ce que le même type soit renvoyé.
Toujours utiliser def semble un peu gênant car quelque chose comme ça ne fonctionnera pas:
trait Entity { def id:Int} object Table { def create(e:Entity) = {e.id = 1 } }
Vous obtiendrez l’erreur suivante:
error: value id_= is not a member of Entity