2010-04-22 6 views
9

J'ai un cache de données qui est actualisé à partir d'une source externe, et je veux limiter mon accès à son cache (en lecture seule) à l'intérieur de mon application. Je ne veux pas avoir à actualiser la source de données chaque fois que j'ai besoin d'y accéder (c'est-à-dire instancier et extraire toutes les données dont j'ai besoin, car il y a beaucoup de données qui sont mises à jour). Je suppose que c'est l'un des pièges de l'implémentation d'un projet et de l'apprentissage de la langue en même temps. Je sais que la logique doit êtreComment implémenter Singleton Pattern (syntaxe)

if instance is null 
    synchronize 
    if instance is null 
     instance = new MySingleton() 

mais l'absence de null me lance pour une boucle. Je pense que je peux utiliser un type d'option, etc, mais il est de me jeter pour une boucle

type MySingleton = 

     [<DefaultValue>] 
     static val mutable private instance: MySingleton option 

     static member GetInstance() = 
      match instance with 
       | Some(i) -> i 
       | None -> 
          *MySingleton.instance = new MySingleton() 
          MySingleton.instance* 

cette logique est faux selon le compilateur ...

 if Helper.notExists MySingleton.instance then 
      MySingleton.instance <- Some(new MySingleton())   
     MySingleton.instance 

dois-je utiliser les instructions IF au lieu? Y a-t-il un modèle préféré pour cette syntaxe dans f #?

+1

commentaire sérieux cette fois-ci . Juste pour vous assurer que vous demandez de l'aide sur la bonne chose, un singleton est une classe spécialement conçue que vous pouvez essayer de créer autant de fois que vous voulez, mais après le premier, chaque fois que vous obtenez cette première instance. donc si vous avez essayé de créer un tableau de singletons, vous obtenez juste un tableau de ce même objet. En lisant votre question, il est difficile de dire si vous voulez vraiment ce comportement. – thecoshman

+0

Oui, oui; Le singleton contiendra un grand cache de données et fournira l'accès aux données chaque fois que nécessaire sans que chaque utilisation de la classe ait besoin d'actualiser/extraire les données (ce qui est un long processus). – akaphenom

Répondre

4

Le type Lazy comme Brian mentionné est un bon endroit pour commencer. Il vous permet de vous assurer qu'un calcul sera exécuté lorsque la valeur est nécessaire et garantit la sécurité du thread, ce qui signifie que le calcul ne s'exécutera qu'une fois (bien que, dans certains cas, vous pouvez également utiliser PublicationOnly option pour spécifier que plusieurs threads peuvent commencer à initialiser le cache et seul le premier résultat sera utilisé).

Toutefois, vous aurez probablement également besoin d'un mécanisme pour marquer le cache comme invalide (par exemple après un certain temps) et forcer la réinitialisation du cache. Notez que ce n'est pas vraiment un modèle Singleton. Quoi qu'il en soit, vous pouvez toujours le faire dans un fil de façon sûre à l'aide Lazy, mais vous aurez besoin de structurer le code comme ceci:

module Cache = 
    // returns a lazy value that initializes the cache when 
    // accessed for the first time (safely) 
    let private createCacheInitialization() = 
    lazy(// some code to calculate cache 
      cache) 
    // current cache represented as lazy value 
    let mutable private currentCache = createCacheInitialization() 

    // Returns the current cache 
    let GetCache() = currentCache.Value 
    // Reset - cache will be re-initialized next time it is accessed 
    // (this doesn't actually initialize a cache - just creates a lazy value) 
    let Reset() = currentCache <- createCacheInitialization() 

Bien sûr, vous pourriez transformer ce code dans une classe Cache qui ne prend que la fonction d'initialisation et encapsule le reste du code dans une pièce réutilisable (si vous avez besoin de mettre en cache plusieurs valeurs, par exemple).

14

deux .NET 4.0 et F # ont Lazy, donc je pense que vous voulez

module MySingleton = 
    let private x = Lazy.Create(fun() -> 42) 
    let GetInstance() = x.Value 

(où 42 pourrait être un new WhateverType() ou quel que soit l'initialisation est coûteuse).

http://msdn.microsoft.com/en-us/library/dd997286.aspx

(Commentaire: Il est 2010, et fait rare d'avoir à traiter explicitement des primitives de synchronisation, les langues et les bibliothèques sont encapsulant tous les modèles communs.)

+0

Y a-t-il une différence entre 'Lazy.Create (fun() -> 42)' et 'lazy (42)' ou ces syntaxes ne sont-elles pas différentes pour la même chose? Si '42' était un appel de constructeur, serait-il retardé dans les deux versions? –

+0

Même chose; paresseux (expr) signifie Lazy.Create (fun() -> expr) – Brian

+1

@ Brian - le développement F # de base n'est-il pas fondamentalement un singleton? Je veux dire sérieusement - vous assignez une fois et lisez ensuite de cette valeur encore et encore. Je ne suis pas sûr de comprendre la différence entre ceci et le motif singleton. –

6

La question était comment mettre en œuvre le modèle Singleton, pas comment mettre en œuvre le modèle Lazy-charge. Un singleton peut être mis en œuvre de manière sécurisée par thread de plusieurs manières, par ex.:

// Standard approach in F# 2.0: using an implicit constructor. 
type Singleton private() = 
    static let instance = new Singleton() 
    static member Instance = instance 

// Abbreviated approach in F# 3.0: using an implicit constructor with auto property. 
type Singleton private() = 
    static member val Instance = Singleton() 

// Alternative example: When you have to use an explicit ctor, 
// and also want to check instanciation upon each access of the property. 

/// This type is intended for private use within Singleton only. 
type private SyncRoot = class end 

type Singleton = 
    [<DefaultValue>] 
    static val mutable private instance: Singleton 

    private new() = { } 

    static member Instance = 
     lock typeof<SyncRoot> (fun() -> 
      if box Singleton.instance = null then 
       Singleton.instance <- Singleton()) 
     Singleton.instance  

Modifier
Ajout d'un exemple de F # 2.0 simplifiée avec cteur privé implicite, et l'exemple avec cteur explicite utilise maintenant un type privé séparé en tant que root de synchronisation. Merci à kvb pour les conseils.

Édition 2 Ajout de la syntaxe de propriété automatique F # 3.0.

+2

1. Vous pouvez rendre un constructeur par défaut privé (comme dans '' PersonSingleton private() = ... '), auquel cas vous peut utiliser 'static let' dans le type. 2. Ne pas verrouiller sur une instance de type; parce que le type est public autre code pourrait également verrouiller dessus, provoquant un blocage. – kvb

+0

Merci de ne jamais effacer cette réponse. Le modificateur d'accès privé pour un constructeur implicite était exactement la pièce qui me manquait! – JDB

+0

Pour quelle raison enfermez-vous le Singleton.instance ici? – Snake

9

Désolé pour réanimer une vieille question, je voulais juste signaler que certains pourraient essayer d'exposer Instance dans une propriété publique, auquel cas le morceau de code suivant pourrait être utile:

type MyType() = 
    inherit SomeParent() 

    static let mutable instance = lazy(new MyType()) 
    static member Instance with get() = instance.Value 
+2

Et, le modèle singleton devrait avoir un constructeur privé, donc: 'type public MyType private() =' –