Résumé:
Vous pouvez modifier la valeur d'une classe de cas de paramètre avant d'être stocké dans le cas de la classe assez simplement tout en restant valide(auf s i indiqué) ADT (Type Abstrait de Données). Alors que la solution est relativement simple, en découvrant les détails a été un peu plus difficile.
Détails:
Si vous voulez vous assurer valable uniquement les instances de votre classe de cas peut jamais être instancié qui est un postulat essentiel derrière un ADT (Type Abstrait de Données), il existe un certain nombre de choses que vous devez faire.
Par exemple, un générés par le compilateur copy
méthode est fournie par défaut sur une classe de cas. Donc, même si vous êtes très prudent afin de s'assurer que seules les instances ont été créées par le biais de l'explicite compagnon de l'objet apply
méthode qui garantit qu'ils ne pourrait jamais contenir supérieure aux valeurs, le code suivant devrait produire un cas instance de classe avec un bas de cas de la valeur:
val a1 = A("Hi There") //contains "HI THERE"
val a2 = a1.copy(s = "gotcha") //contains "gotcha"
En outre, des classes de cas de mettre en oeuvre java.io.Serializable
. Cela signifie que votre œuvre d'une stratégie prudente d'avoir seulement majuscules instances peuvent être subverti avec un simple éditeur de texte et de désérialisation.
Ainsi, par tous les moyens divers de votre classe de cas peut être utilisé (avec bienveillance et/ou malveillante), voici les actions que vous devez prendre:
- Pour votre explicite compagnon de l'objet:
- Créer en utilisant exactement le même nom que la classe de cas de
- Cela a accès à la classe de cas de ses parties intimes
- Créer un
apply
méthode avec exactement la même signature que le principal constructeur de votre classe de cas de
- Cela permettra de compiler avec succès une fois l'étape 2.1 est terminé
- Fournir une implémentation de l'obtention d'une instance de la classe de cas à l'aide de l'
new
de l'opérateur et de fournir un vide de la mise en œuvre {}
- Ce sera désormais instancier la classe de cas strictement à vos conditions
- Le vide de la mise en œuvre
{}
doivent être fournis en raison de l'affaire de la classe est déclarée abstract
(voir l'étape 2.1)
- Pour votre cas de la classe:
- Déclarer
abstract
- Empêche la Scala compilateur de générer un
apply
méthode dans le compagnon de l'objet qui est ce qui a provoqué le "la méthode est définie deux fois..." erreur de compilation (étape 1.2 ci-dessus)
- Marquer le principal constructeur comme
private[A]
- Le principal constructeur est maintenant disponible uniquement pour le cas de la classe elle-même et à son compagnon, l'objet (celui que nous avons défini ci-dessus dans l'étape 1.1)
- Créer un
readResolve
méthode
- Fournir une implémentation à l'aide de la méthode apply (étape 1.2 ci-dessus)
- Créer un
copy
méthode
- Définir exactement la même signature que le cas de la classe primaire constructeur
- Pour chaque paramètre, ajouter une valeur par défaut en utilisant le même nom de paramètre (ex:
s: String = s
)
- Fournir une implémentation à l'aide de la méthode apply (étape 1.2 ci-dessous)
Voici ton code modifié avec les actions ci-dessus:
object A {
def apply(s: String, i: Int): A =
new A(s.toUpperCase, i) {} //abstract class implementation intentionally empty
}
abstract case class A private[A] (s: String, i: Int) {
private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
A.apply(s, i)
def copy(s: String = s, i: Int = i): A =
A.apply(s, i)
}
Et voici votre code après la mise en œuvre de l'exiger (suggéré dans le @ollekullberg réponse) et aussi d'identifier l'endroit idéal pour mettre toute sorte de mise en cache:
object A {
def apply(s: String, i: Int): A = {
require(s.forall(_.isUpper), s"Bad String: $s")
//TODO: Insert normal instance caching mechanism here
new A(s, i) {} //abstract class implementation intentionally empty
}
}
abstract case class A private[A] (s: String, i: Int) {
private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
A.apply(s, i)
def copy(s: String = s, i: Int = i): A =
A.apply(s, i)
}
Et cette version est plus sécurisé et robuste si ce code sera utilisé par Java interop (cache le cas de la classe comme une mise en place et crée une classe finale qui l'empêche de dérivations):
object A {
private[A] abstract case class AImpl private[A] (s: String, i: Int)
def apply(s: String, i: Int): A = {
require(s.forall(_.isUpper), s"Bad String: $s")
//TODO: Insert normal instance caching mechanism here
new A(s, i)
}
}
final class A private[A] (s: String, i: Int) extends A.AImpl(s, i) {
private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
A.apply(s, i)
def copy(s: String = s, i: Int = i): A =
A.apply(s, i)
}
Bien que cela répond directement à votre question, il y a encore plus de moyens pour développer cette voie autour des classes de cas au-delà de l'instance de mise en cache. Pour mes propres besoins du projet, j'ai créé un système solution que j'ai documenté sur CodeReview (un StackOverflow site de soeur). Si vous êtes à la recherche là-dessus, à l'aide de ou de mettre à profit ma solution, merci d'envisager de me laisser des commentaires, des suggestions ou des questions et dans la raison, je vais faire de mon mieux pour répondre dans un délai de un jour.