4

I have a builder pattern in which I am taking few parameters from the customer and basis on that I am building my builder class and then that builder class is passed to our underlying library and then my library will use it.

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
}

For each and every call to our library they create a new KeyHolder builder class everytime and pass it to our library. processId, clientId, deviceId will change with every call but maximumCacheSize and expireAfterWrite will stay same as it with every call. As you can see above, I am using guava cache here and since they are creating KeyHolder builder class everytime how can I make sure that the below line is executed only once in my constructor?

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

Since with the current code right now, it will get executed with every call and I will get a new guava cache every time in my library so whatever entry was cached earlier within my library by using this guava cache will get lost.

How to initialize a particular variable only once and after that it should ignore the value whatever is being passed to it?

Update:

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;
    }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
john
  • 11,311
  • 40
  • 131
  • 251
  • I'm confused, it's already `final` so what do you mean by "*ignore whatever value is passed to it*"? You cannot reinitialize a `final` variable. Could you elaborate? – Vince Feb 07 '17 at 23:18
  • @VinceEmigh It(final userCache) is an instance variable in OP's code, not static – boxed__l Feb 07 '17 at 23:24
  • @VinceEmigh my use case is, customer create `KeyHolder` class everytime with each and every call and then they call our library by passing this. And now my library uses this builder class and does extra stuff basis on parameters is passed. I have edited my question to clarify this more. Now since I am using guava cache here, I want to intiialzie my guava cache basis on the values passed from the customer but I want to initialize it only once. And then I use this guava cache in my library as shown in my above code. – john Feb 07 '17 at 23:35
  • @Abhijith What's your point? It doesn't need to be `static`. You could use a factory, store a reference to `userCache` in the factory, then pass it to every `KeyHolder`. I was trying to get some clarification on the situation. – Vince Feb 07 '17 at 23:36
  • @VinceEmigh any thoughts how should I tackle this problem? – john Feb 07 '17 at 23:51
  • @VinceEmigh My point being that in OP's code `userCache` is instance based & final so he can create multiple objects of the class and each of those objects would have different `userCache`s which are not shared. Now to correct this he can use an enum/make the variable static/use a dedicated class/or a factory, these being the implementation detail. IMO the one which takes minimal code change is the `static` approach but whether its ideal in this scenario is debatable. – boxed__l Feb 08 '17 at 00:16
  • @Abhijith Check out my answer, hopefully it'll clear the air. The problem was deeper, and using `static` would have contributed to the problem. `KeyHolder` has no reason to be coupled to the response cache. It was only coupled so people can specify the cache settings through `KeyHolder`, which isn't logical and created a tight couple. – Vince Feb 08 '17 at 00:19

3 Answers3

2

If you only want to set the cache once, why does every KeuHolder object attempt to build it? In fact, even KeyHolder#Builder exposes methods to help the construction of the cache, which would only be useful once.

This is highly questionable. What if the first KeyHolder doesn't specify the cache details? I mean, it's not forced to (you aren't using the builder pattern correctly, more on that at the end).

The first step to solving this issue would be to ensure the cache is set before you start creating KeyHolder object's. You can do this by creating a static factory and making userCache static:

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

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

        return new Builder(id);
    }
}

But as you've probably read from my comments, this is simply a band-aid for the issue. This checks the userCache every time we want to create a new KeyHolder, which shouldn't need to happen.

Instead, you should decouple the cache from KeyHolder all together. Why does it need to know about caching anyways?

Your cache belongs in DataClient:

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

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

You could accept the settings via DataClient constructor, or pass the cache into DataClient with the settings already specified.


As for your use of the builder pattern, keep in mind why we use it: Java lacks optional parameters.

That's why builders are common: they allow us to specify optional data via methods.

You are specifying critical info, such as cache settings, as optional parameters (builder methods). You should only use builder methods if you do not require the information, and cache information is definitely something that should be required. I'd question how optional deviceId and clientId are aswell, seeing how the only required data is productId.

Vince
  • 14,470
  • 7
  • 39
  • 84
  • After going throug all the comments and answers, I figured it out that cache belongs in `DataClient` class only. Now if I go this approach, how do I initialize cache only once in `DataClient` class then? Customer generally calls our library like this `DataResponse response = DataClientFactory.getInstance().executeSync(key);` and then call will come to `executeSync` method in `DataClient` class. – john Feb 08 '17 at 00:24
  • I need to allow customer to pass these two values `maximumCacheSize` and `expireAfterWrite` using the KeyHolder class since that is the requirement I have. If they don't pass it, I already have defaults values for those two so how can I use these two values from `KeyHolder` class and initialize cache in DataClient only once? – john Feb 08 '17 at 00:27
  • This is why singletons can be a pain. As it currently stands, you'd have to expose a `setCache(...)` and implement a strategy pattern so you can switch between a non-caching `executeSync` algorithm and a caching algorithm. Or if you don't need dynamic settings, you could load them via IO allowing the client to specify them in a settings file. Your last 2 options, from what I can see, would be hard-coding the settings (not good) or using DI to avoid the need for a singleton. – Vince Feb 08 '17 at 00:34
  • @david Why do they need to set it via `KeyHolder`? You'll be coupling the cache and `KeyHolder`, making it almost impossible to avoid speghetti code. Not to mention, the settings don't apply on a per-key basis. If it does, then changes are every key needs it's own cache, or one key's settings may interfere with the settings a different key specified. – Vince Feb 08 '17 at 00:36
  • That's the requirement I have sadly :( and KeyHolder is the only way by which any customer can set various parameters before calling our library so that is why I wanted to set it there.. Basically we can assume they will always pass these two values and they won't change it at all. Whatever they have passed first time for these two values, it will stay same always in the subsequent calls. Any thoughts how can I do this with this silly requirement? – john Feb 08 '17 at 00:37
  • Seems your supervisors are trying to oversimplify the interface, the only reason I could see something like that being a requirement. That's only going to cause harm and tangle up code. `KeyHolder` should only be responsible for key holding (SRP). But if it's out of your hands, you should stick with the static factory method approach if you can, avoid the need of creating another singleton. Make `Builder` private and force clients to call `newKeyHolder` if you're allowed. If they won't let you, then you're in for quite some stress at your job, as you're fighting against OOP philosophy. – Vince Feb 08 '17 at 00:46
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/135132/discussion-between-vince-emigh-and-david). – Vince Feb 08 '17 at 00:46
0

first, make it static

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

then, initialize it only if it hasn't been initialized

private KeyHolder(Builder builder) {
    ...

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

if you don't really need the cache to be customization at run-time (by using passed in parameters, I'd suggest going with something like

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

and remove it from the constructor.

Tyler Sebastian
  • 9,067
  • 6
  • 39
  • 62
  • @HaifengZhang static where? – Tyler Sebastian Feb 07 '17 at 23:09
  • What's wrong with making it `final` and initializating eagerly? – Lew Bloch Feb 07 '17 at 23:18
  • @VinceEmigh userCache *isn't* final. The null check is true the first time the class is instanced, and false every other time. I did have a typo, though, that I've since corrected. – Tyler Sebastian Feb 07 '17 at 23:21
  • @LewBloch nothing, but I assume that s/he wants some control over the initial `userCache` (the builder uses a couple passed in parameters) – Tyler Sebastian Feb 07 '17 at 23:22
  • Disregard my previous comment, I didn't realize you removed `final`. There's no reason to, especially since the OP explicitly stated "*should not change after initialization*", removing `final` is contradictive. I'm surprised that everyone, even people with 5k rep, would suggest removing `final`. The problem clearly lies within the relationship of the objects, more-so "does the builder code belong right there?" – Vince Feb 07 '17 at 23:34
  • @VinceEmigh This library is being used from a long time like that.. And now we need to add an extra feature which is the cache one. And that's why i want to initialize it only once and use it in my library. So once the cache is initialized only once, I will get with first call and if it is not there then do the normal flow and populate the cache. Let me know if anything is unclear. – john Feb 07 '17 at 23:43
  • @VinceEmigh it really isn't a big deal and is a common pattern - and, given his code where he's using passed in variables (`builder.maximumCacheSize`) to initialize the cache, required. That said I've updated my answer with your suggestion. – Tyler Sebastian Feb 07 '17 at 23:57
  • that is the problem, they will pass these values `maximumCacheSize` and `expireAfterWrite` and then I need to use these two values and initialize my cache but only once. After that they will still keep passing if they want but it will get initialized only once. – john Feb 07 '17 at 23:59
  • @david then you can use my first suggestion. – Tyler Sebastian Feb 08 '17 at 00:01
  • @TylerSebastian its not working.. I tried that.. I have updated my question to add more details how this `KeyHolder` class will be used. First I look up in cache, and if it is there then I return from the cache. Otherwise, I execute the normal flow and populate the cache. But somehow, after populating my cache for the first time, with the second call my same cache is empty after using your suggestion? – john Feb 08 '17 at 00:03
  • @david I fleshed out my answer a bit - make sure you're following it. If you are, put a break point or a `Log.d` inside the if and verify that it's only being entered once. Otherwise, perhaps the cacheSize or the expireTime is too small. – Tyler Sebastian Feb 08 '17 at 00:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/135130/discussion-between-david-and-tyler-sebastian). – john Feb 08 '17 at 00:10
0

You can make the variable static and only initialize it on the first call, if it is null. Something like this:

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
nhouser9
  • 6,730
  • 3
  • 21
  • 42
  • agreed, this would be solution to using a singleton. it's just missing the last bracket – RAZ_Muh_Taz Feb 07 '17 at 23:17
  • @VinceEmigh No, it won't, because the variable is static. When the first instance of the object is created, the static var will be null. Every time thereafter, it will have a value. Read more about how the `static` keyword works here: http://stackoverflow.com/questions/413898/what-does-the-static-keyword-do-in-a-class but basically it means that the variable will be shared across all instances of the class. – nhouser9 Feb 07 '17 at 23:21
  • I know what `static` means, I just didn't realize your use of it. You are initializing a `static` reference variable in a constructor, which is questionable (it belongs to the class, but objects are in charge of maintaining it's lifecycle, not the class?). You removed `final` to allow this. Huge code smell, a singleton would have worked just as well without the contradictive philosophy. But even then, a singleton is a band-aid to the actual problem, since a singleton would be excessive exposure. Again, I didn't downvote, I was just trying to shine some light on a possibility of why they did – Vince Feb 07 '17 at 23:28
  • @nhouser9 I have updated my question to add more details how this `KeyHolder` class will be used. First I look up in cache, and if it is there then I return from the cache. Otherwise, I execute the normal flow and populate the cache. But somehow, after populating my cache for the first time, with the second call my same cache is empty after using your suggestion? – john Feb 07 '17 at 23:37