2016-06-15 2 views
1

Nous avons plusieurs types de ressources et nous voulions faire une méthode pour vérifier si une ressource est en bonne santé ou non. Étant donné que le type de ressources sont très hétérogènes, nous ne voulions pas utiliser subclassing standard et nous avons décidé d'utiliser une classe de types:Scala typeclass et la covariance

trait CanHealthCheck[T] { 
    def isHealthy(t: T): Boolean 
} 

Nous avons aussi une méthode utilitaire pour être en mesure de vérifier si une ressource donnée est vivant/sain ou pas

object LivenessChecker { 
    def isAlive[T](t: T)(implicit canHealthCheck: CanHealthCheck[T]): Boolean = { 
    canHealthCheck.isHealthy(t) 
    } 
} 

Nous avons une couche de référentiel pour accéder aux données. Nous tenons à exprimer l'idée qu'un référentiel abstrait donné doit être « checkable de santé », mais de laisser les détails de mise en œuvre des sous-classes mise en œuvre du trait:

trait UserRepository { 
    def findSomeUser(): User = ??? 

    implicit def isHealthCheckable: CanHealthCheck[UserRepository] 
} 

Le problème se pose quand on veut sous-classe UserRepository avec un particulier mise en œuvre, étant donné que CanHealthCheck n'est pas covariant sur le type T.

class DbUserRepository extends UserRepository { 
    def ping: Boolean = ??? 

    override implicit val isHealthCheckable: CanHealthCheck[UserRepository] = 
    new CanHealthCheck[DbUserRepository] { 
     def isHealthy(db: DbUserRepository) = db.ping 
    } 
} 

Et ceci est un exemple d'une fonction factice qui agit sur le dépôt abstrait tout en essayant de vérifier si le dépôt est vivant:

def someDummyFunction(userRepository: UserRepository) = { 
    if(LivenessChecker.isAlive(userRepository)) // This won't compile 
    userRepository.findSomeUser() 
} 

L'idée est que notre application utilise la UserRepository trait et non l'implémentation, et donc nous ne pouvons pas vérifier si le référentiel est vivant ou non. Comment pouvons-nous continuer à utiliser la couche d'abstraction du référentiel et être en mesure de vérifier si un référentiel (abstrait) donné est vivant? Le modèle typeclass est-il le modèle correct à utiliser ici?

Répondre

0

Utilisez "type bounds".

je ne pouvais pas tester, mais pour obtenir le code à compiler, vous pouvez faire quelque chose comme:

class DbUserRepository[U <: UserRepository] extends UserRepository { 
    def ping: Boolean = ??? 

    implicit val isHealthCheckable: CanHealthCheck[U] = 
    new CanHealthCheck[U] { 
     def isHealthy(db: U) = db.ping 
    } 
} 
+0

Je ne vois pas comment cela résoudra le problème.Nous avons besoin d'un 'CanHealthCheck [UserRepository]' alors que dans notre exemple nous obtenons un 'CanHealthCheck [U]' avec 'U <: UserRepository'. Étant donné que CanHealthCheck n'est pas ** covariant ** sur T, cela ne fonctionnera pas –

+0

Je suis désolé, j'ai raté "CanHealthCheck n'est pas covariant sur le type T" dans votre question. Je vais laisser la réponse comme une référence quant à la façon de compiler le code ... pour l'instant: - \ –

1

Il y a quelque chose d'un peu louche avec isHealthCheckable intérieur UserRespository. La méthode isHealthy, une fois appelée, aurait deux instances de UserRepository disponibles: bien sûr, celle passée comme l'argument t, mais aussi, l'UserRepository.this de l'instance englobante.

Ceci est un signe de quelque chose de mal. Soit la méthode doit être écrite ailleurs, de sorte qu'elle n'obture pas ceci, sinon elle ne devrait pas avoir l'argument

Cette deuxième option est cohérente avec l'utilisation de UserRepository comme sous-typage orienté objet. En outre, il est cohérent avec votre idée que chaque UserRepository doit être vérifiable. Juste faire

trait UserRepository { 
    ... 
    def isHealty: Boolean 
} 

Il est bien d'appeler cela directement, userDirectory.isHealthy. Mais vous pouvez aussi implémenter facilement la classe de type:

object UserRepository { 
    implicit val canHealthCheck = new CanHealthCheck[UserRepository] { 
    def isHealthy(repository: UserRepository) = repository.IsHealthy 
    } 
} 

noter également qu'il n'a pas été du tout clair comment la méthode d'instance implicite serait venu dans le champ d'application implicite. Avec l'objet compagnon, cela fonctionne bien.

+0

Je suis d'accord que c'est probablement un signe de quelque chose de mal. La question est de savoir si nous pouvons utiliser le modèle typeclass et si les clients ne dépendent que du trait, ou si nous devons utiliser l'approche OO classique. BTW J'ai mis à jour la question pour refléter comment un client potentiel utiliserait le userRepository abstrait –