2017-02-07 5 views
1

J'ai un modèle de générateur dans lequel je prends quelques paramètres du client et base sur ce que je construis ma classe de constructeur, puis cette classe de constructeur est passée à notre bibliothèque sous-jacente et ma bibliothèque l'utilisera.Comment initialiser une variable dans le constructeur une seule fois?

public final class KeyHolder { 
    private final String clientId; 
    private final String deviceId; 
    private final int processId; 
    private final Cache<String, List<Response>> userCache; 
    private static final long MAXIMUM_CACHE_SIZE = 5000000; 
    private static final long EXPIRE_AFTER_WRITE = 120; // this is in seconds 

    private KeyHolder(Builder builder) { 
    this.clientId = builder.clientId; 
    this.deviceId = builder.deviceId; 
    this.processId = builder.processId; 
    this.maximumCacheSize = builder.maximumCacheSize; 
    this.expireAfterWrite = builder.expireAfterWrite; 

    // how to execute this line only once 
    this.userCache = 
     CacheBuilder 
      .newBuilder() 
      .maximumSize(maximumCacheSize) 
      .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS) 
      .removalListener(
       RemovalListeners.asynchronous(new CustomListener(), 
        Executors.newSingleThreadScheduledExecutor())).build(); 

    } 

    public static class Builder { 
    protected final int processId; 
    protected String clientId = null; 
    protected String deviceId = null; 
    protected long maximumCacheSize = MAXIMUM_CACHE_SIZE; 
    protected long expireAfterWrite = EXPIRE_AFTER_WRITE; 


    public Builder(int processId) { 
     this.processId = processId; 
    } 

    public Builder setClientId(String clientId) { 
     this.clientId = clientId; 
     return this; 
    } 

    public Builder setDeviceId(String deviceId) { 
     this.deviceId = deviceId; 
     return this; 
    } 

    public Builder setMaximumCacheSize(long size) { 
     this.maximumCacheSize = size; 
     return this; 
    } 

    public Builder setExpiryTimeAfterWrite(long duration) { 
     this.expireAfterWrite = duration; 
     return this; 
    } 

    public KeyHolder build() { 
     return new KeyHolder(this); 
    } 
    } 

// getters here 
} 

Pour chaque appel à notre bibliothèque, ils créent une nouvelle classe à chaque fois constructeur KeyHolder et le transmettre à notre bibliothèque. processId, clientId, deviceId va changer à chaque appel mais maximumCacheSize et expireAfterWrite restera le même qu'à chaque appel. Comme vous pouvez le voir ci-dessus, j'utilise le cache de goyave ici et depuis qu'ils créent KeyHolder classe de constructeur à chaque fois comment puis-je m'assurer que la ligne ci-dessous n'est exécutée qu'une seule fois dans mon constructeur?

this.userCache = 
     CacheBuilder 
      .newBuilder() 
      .maximumSize(maximumCacheSize) 
      .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS) 
      .removalListener(
       RemovalListeners.asynchronous(new CustomListener(), 
        Executors.newSingleThreadScheduledExecutor())).build(); 

Depuis avec le code actuel en ce moment, il va s'exécuter à chaque appel et je vais obtenir un nouveau cache de goyave chaque fois dans ma bibliothèque pour tout ce qui a été mis en mémoire cache plus tôt l'entrée au sein de ma bibliothèque en utilisant ce cache goyave sera Foutez le camp.

Comment initialiser une variable particulière une seule fois et après cela, il devrait ignorer la valeur tout ce qui lui est passé?

Mise à jour: -

public class DataClient implements Client { 
    private final ExecutorService executor = Executors.newFixedThreadPool(10); 

    // for synchronous call 
    @Override 
    public List<Response> executeSync(KeyHolder key) { 
     Cache<String, List<Response>> userCache = key.getUserCache(); 
     List<Response> response = userCache.getIfPresent(key.getUUID()); 
     if (CollectionUtils.isNotEmpty(response)) { 
      return response; 
     } 
     // if not in cache, then normally call the flow and populate the cache 
     List<Response> dataResponse = null; 
     Future<List<Response>> future = null; 
     try { 
      future = executeAsync(key); 
      dataResponse = future.get(key.getTimeout(), TimeUnit.MILLISECONDS); 
      userCache.put(key.getUUID(), dataResponse); 
     } catch (TimeoutException ex) { 
      // log error and return DataResponse 
     } catch (Exception ex) { 
      // log error and return DataResponse 
     } 

     return dataResponse; 
    } 
} 
+0

Je suis confus, c'est déjà 'final' alors qu'entendez-vous par" * ignorez la valeur qui lui est transmise * "? Vous ne pouvez pas réinitialiser une variable 'final'. Pourriez-vous élaborer? –

+0

@VinceEmigh It (userCache final) est une variable d'instance dans le code OP, pas statique –

+0

@VinceEmigh mon cas d'utilisation est, client créer 'KeyHolder' classe chaque fois avec chaque appel, puis ils appellent notre bibliothèque en passant cela. Et maintenant ma bibliothèque utilise cette classe de construction et fait des bases supplémentaires sur les paramètres. J'ai édité ma question pour clarifier ceci plus. Maintenant que j'utilise le cache de goyave ici, je veux intiialzie ma base de cache de guava sur les valeurs transmises par le client mais je veux l'initialiser une seule fois. Et puis j'utilise ce cache de goyave dans ma bibliothèque comme indiqué dans mon code ci-dessus. – john

Répondre

1

Si vous voulez seulement définir le cache une fois, pourquoi toute tentative d'objet KeuHolder pour le construire? En fait, même KeyHolder#Builder expose des méthodes pour aider à la construction du cache, qui ne serait utile qu'une seule fois.

Ceci est très discutable. Que faire si le premier KeyHolder ne spécifie pas les détails du cache? Je veux dire, il n'est pas obligé de (vous n'utilisez pas le modèle de constructeur correctement, plus sur cela à la fin). La première étape pour résoudre ce problème consiste à s'assurer que le cache est défini avant de commencer à créer des objets KeyHolder. Vous pouvez le faire en créant une usine statique et en faisant userCache statique:

class KeyHolder { 
    private static Map<String, List<Response>> userCache; 

    public static KeyHolder.Builder newBuilder(int id) { 
     if(userCache == null) { 
      userCache = ...; 
     } 

     return new Builder(id); 
    } 
} 

Mais comme vous l'avez probablement lu de mes commentaires, cela est tout simplement un rafistolage pour la question. Cela vérifie userCache chaque fois que nous voulons créer un nouveau KeyHolder, ce qui ne devrait pas avoir lieu.Au lieu de cela, vous devez découpler le cache de KeyHolder tous ensemble. Pourquoi a-t-il besoin de connaître la mise en cache de toute façon?

Votre cache appartient à DataClient:

class DataClient { 
    private Map<String, List<Response>> userCache; 

    public List<Response> executeSync(KeyHolder key) { 
     List<Response> response = userCache.getIfPresent(key.getUUID()); 
     //... 
    } 
} 

Vous pouvez accepter les paramètres par constructeur DataClient, ou passer le cache en DataClient avec les paramètres déjà spécifiés. En ce qui concerne votre utilisation du modèle générateur, gardez à l'esprit pourquoi nous l'utilisons: Java manque de paramètres optionnels.

C'est pourquoi les constructeurs sont courants: ils nous permettent de spécifier des données optionnelles via des méthodes.

Vous spécifiez des informations critiques, telles que les paramètres de cache, en tant que paramètres facultatifs (méthodes du générateur). Vous ne devriez utiliser les méthodes de construction que si vous n'avez pas besoin des informations, et les informations de cache sont certainement quelque chose qui devrait être requis. Je demanderais comment optionnel deviceId et clientId sont également, voyant comment les seules données requises sont productId.

+0

Après avoir parcouru tous les commentaires et réponses, je me suis rendu compte que le cache n'appartient qu'à la classe 'DataClient'. Maintenant, si j'utilise cette approche, comment puis-je initialiser le cache une seule fois dans la classe 'DataClient'? Le client appelle généralement notre bibliothèque de la manière suivante: 'DataResponse response = DataClientFactory.getInstance(). ExecuteSync (clé);' et ensuite appelera la méthode 'executeSync' dans la classe' DataClient'. – john

+0

Je dois permettre au client de passer ces deux valeurs 'maximumCacheSize' et' expireAfterWrite' en utilisant la classe KeyHolder puisque c'est l'exigence que j'ai. Si elles ne le transmettent pas, j'ai déjà des valeurs par défaut pour ces deux, alors comment puis-je utiliser ces deux valeurs de la classe 'KeyHolder' et initialiser le cache dans DataClient une seule fois? – john

+0

C'est pourquoi les singletons peuvent être douloureux. En l'état actuel, vous devez exposer 'setCache (...)' et implémenter un modèle de stratégie pour pouvoir basculer entre un algorithme 'executeSync' non mis en cache et un algorithme de mise en cache. Ou si vous n'avez pas besoin de paramètres dynamiques, vous pouvez les charger via IO permettant au client de les spécifier dans un fichier de paramètres. Vos 2 dernières options, d'après ce que je peux voir, coderaient durement les paramètres (pas bon) ou utiliser DI pour éviter le besoin d'un singleton. –

0

premier, make it static

private static Cache<String, List<Response>> userCache; 

alors, lsinitialisez que si elle n'a pas été initialisé

private KeyHolder(Builder builder) { 
    ... 

    if (userCache == null) { 
     userCache = CacheBuilder 
      .newBuilder() 
      .maximumSize(maximumCacheSize) 
      .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS) 
      .removalListener(
       RemovalListeners.asynchronous(new CustomListener(), Executors.newSingleThreadScheduledExecutor()) 
      ).build(); 
    } 
} 

si vous ne vraiment besoin que le cache soit personnalisé au moment de l'exécution (en utilisant ed dans les paramètres, je suggère d'aller avec quelque chose comme

// initialize the cache while defining it 
// replace maximumCacheSize and expireAfterWrite with constants 
private static final Cache... = CacheBuilder.newBuilder()...; 

et le retirer du constructeur.

+0

Im pas downvoter, mais pourquoi statique ici? – haifzhan

+0

@HaifengZhang statique où? –

+0

Qu'y a-t-il de mal à le rendre «final» et à l'initialiser avec empressement? –

0

Vous pouvez rendre la variable statique et ne l'initialiser que lors du premier appel, si elle est nulle. Quelque chose comme ceci:

public final class KeyHolder { 
    private final String clientId; 
    private final String deviceId; 
    private final int processId; 

    //this var is now static, so it is shared across all instances 
    private static Cache<String, List<Response>> userCache = null; 

    private static final long MAXIMUM_CACHE_SIZE = 5000000; 
    private static final long EXPIRE_AFTER_WRITE = 120; // this is in seconds 

    private KeyHolder(Builder builder) { 
    this.clientId = builder.clientId; 
    this.deviceId = builder.deviceId; 
    this.processId = builder.processId; 
    this.maximumCacheSize = builder.maximumCacheSize; 
    this.expireAfterWrite = builder.expireAfterWrite; 

    //this will be executed only the first time, when the var is null 
    if (userCache == null) { 
     userCache = 
     CacheBuilder 
      .newBuilder() 
      .maximumSize(maximumCacheSize) 
      .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS) 
      .removalListener(
       RemovalListeners.asynchronous(new CustomListener(), 
        Executors.newSingleThreadScheduledExecutor())).build(); 

    } 

    //rest of your class below 
+0

Pourquoi était-ce downvoted? –

+0

convenu, ce serait une solution à l'aide d'un singleton. il manque juste la dernière parenthèse –

+0

@VinceEmigh Non, ce ne sera pas le cas, car la variable est statique. Lorsque la première instance de l'objet est créée, la variable statique sera nulle. Chaque fois par la suite, il aura une valeur. En savoir plus sur la façon dont le mot-clé 'static' fonctionne ici: http://stackoverflow.com/questions/413898/what-does-the-static-keyword-do-in-a-class mais, fondamentalement, cela signifie que la variable sera partagé à travers toutes les instances de la classe. – nhouser9