5
\$\begingroup\$

I have a "generic" cache object:

public class Cache {

    private static final Map<Class, ICacheable> cacheMap = new HashMap<>();

    public static ICacheable getCacheableElement(Class cacheableElementClass, Object... params) {
        ICacheable cacheableElement = cacheMap.get(cacheableElementClass);
        if (cacheableElement == null) {
            cacheableElement = addIfNotExists(cacheableElementClass, params);
        }
        return cacheableElement;
    }

    private static ICacheable addIfNotExists(Class cacheableElementClass, Object... params) {
        ICacheable cacheableElementAdded;

        if (cacheableElementClass.equals(SourceCode.class)) {
            cacheMap.put(cacheableElementClass, new SourceCode((String) params[0]));
        } else if (cacheableElementClass.equals(SymbolTable.class)) {
            cacheMap.put(cacheableElementClass, new SymbolTable());
        } else {
            // Error!
            throw new NullPointerException();
        }
        cacheableElementAdded = cacheMap.get(cacheableElementClass);
        return cacheableElementAdded;
    }
}

The Cache's responsibility is cache any kind of object. For that reason, I have the method's param params (I need to find a better name). params is for those objects which need a param for its creation. For example:

public class SourceCode implements ICacheable {

    private String sourceCode;
    public static final Character EOF = '#';

    public SourceCode(String sourceCode) {
        this.sourceCode = sourceCode.toUpperCase() + EOF;
    }
}

As you can see, SourceCode needs a String param (sourceCode) for its creation.

In some part of the code, I fill the cacheMap... In the case of the SourceCode:

cacheMap.put(cacheableElementClass, new SourceCode((String) params[0]));

Here is my problem. As you can see, I create the SourceCode object with the first element of params array (remember the varargs in the above method's signature). IMHO, this is not a clean solution, because some objects will use the params array (SourceCode), and others won't use it.

Is there a design pattern or a better way to refactoring this?


EDIT: This is the solution I created: enter image description here

As you can see, each object will have its own cache object (implementing ICache interface). Also,I have a CacheFactory which responsibility is create the cache object and returning it if it was already created.

Here is the code:

ICache:

public interface ICache {

    public Object getCacheableElement(Object... params);
}

CacheFactory

public class CacheFactory {

    private static final Map<Class<?>, ICache> caches = new HashMap<>();

    public static ICache createCache(Class cacheClass) {
        Boolean isICacheType = isICacheType(cacheClass);

        if (isICacheType) {
            if (caches.containsKey(cacheClass)) {
                return caches.get(cacheClass);
            } else {
                try {
                    ICache cache = (ICache) cacheClass.getConstructor().newInstance();
                    caches.put(cacheClass, cache);
                    return cache;
                } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
                    // TODO: Is this safe? If not, FIX IT!
                    System.err.println(ex.getMessage());
                    throw new NullPointerException();
                }
            }
        } else {
            throw new IllegalArgumentException(String.format("Class \"%s\" not supported yet.",
                    cacheClass.getClass().getName()));
        }
    }

    private static Boolean isICacheType(Class cacheClass) {
        Boolean isICacheType = false;

        Class[] interfaces = cacheClass.getInterfaces();
        for (Class crtInterface : interfaces) {
            if (crtInterface.equals(ICache.class)) {
                isICacheType = true;
                break;
            }
        }
        return isICacheType;
    }
}

SourceCodeCache:

public class SourceCodeCache implements ICache {

    private SourceCode sourceCode = null;

    @Override
    public Object getCacheableElement(Object... params) {
        sourceCode = (sourceCode == null) ? new SourceCode((String) params[0]) : sourceCode;
        return sourceCode;
    }
}

SymbolTableCache:

public class SymbolTableCache implements ICache {

    private SymbolTable symbolTable;

    @Override
    public Object getCacheableElement(Object... params) {
        symbolTable = (symbolTable == null) ? new SymbolTable() : symbolTable;
        return symbolTable;
    }
}

Thanks to all of you for your recommendations and advices. It was a real pleasure.

\$\endgroup\$
0

5 Answers 5

11
\$\begingroup\$

I have a "generic" cache object:

That's actually pretty non-generic. Especially, Class is a raw type.

private static final Map<Class, ICacheable> cacheMap = new HashMap<>();

Moreover, static mutable state is nearly always wrong. Use an instance variable and if you really have to a static instance of Cache.

It's also strange to restrict the keys and values like this. Compare to Guava Cache.

Especially restricting the values to some ICacheable sounds wrong.

The Cache's responsibility is cache any kind of object.

As long as it's ICacheable???

Here is my problem. As you can see, I create the SourceCode object with the first element of params array (remember the varargs in the above method's signature). IMO, this is not a clean solution, because some objects will use the params array (SourceCode), and others won't use it.

I'm rather lost what it's all about. If there's a params array, it sounds OK to me to use just a part of it. Not using it at all is a special case as it's like using a subarray of zero length.

Maybe your single cache should be multiple caches, one per class? See also ClassToInstanceMap.

\$\endgroup\$
2
  • \$\begingroup\$ Hi maaartinus. You're right, especially regarding to restrict the values to ICacheable elements. You said that Class is a raw type, and that's true. What would you use instead of this (thinking in create a generic solution). I didn't understand the explanation about static =/ \$\endgroup\$ Commented Jun 22, 2015 at 14:13
  • 1
    \$\begingroup\$ @leonardo.manrique You could use Class<? extends ICacheable> or simply just Class<?>. The latter is about the same as raw Class, but produces no warnings. +++ Static is evil. As a rule of thumb, the less mutable static state, the better. Sometimes, it's impractical, but then consider "postponing the static keyword", e.g., use a singleton instead of static fields. \$\endgroup\$
    – maaartinus
    Commented Jun 22, 2015 at 14:21
8
\$\begingroup\$

Instead of doing a whole lot of if-else over the supported constructor classes, you could use Reflection to accomplish the construction of objects.

cacheableElementClass.getDeclaredConstructor or getDeclaredConstructors to retrieve the constructor you want to invoke.

Constructor.newInstance(params) to create the constructor.

Sample code that might work:

try {
    // assuming the class only has one constructor that we can invoke
    Constructor<?> constructor = cacheableElementClass.getDeclaredConstructors[0];
    Object instance = constructor.newInstance(params);
    cacheMap.put(cacheableElementClass, instance);
} catch (... /*a whole lot of exceptions*/) {
    ...
}

If you want to be sure only a limited number of classes are supported, put the supported classes in a HashSet and check if the hashset contains the class you want to retrieve.

The throwing of the NullPointerException would be better of as an IllegalArgumentException, and don't forget to provide a helpful error message.


I find it a bit funny weird that if you call this:

SourceCode a = (SourceCode) Cache.getCacheableElement(SourceCode.class, "A");
SourceCode b = (SourceCode) Cache.getCacheableElement(SourceCode.class, "B");

The second call will return the a object, with "A" as the string value. This makes me wonder a bit about the general design and purpose of this code, it feels a bit strange.


This method could make use of generics to avoid having to typecast the result:

public static ICacheable getCacheableElement(Class cacheableElementClass, Object... params) {

Make that:

public static <T extends ICachable> T getCacheableElement(Class<T> cacheableElementClass, Object... params) {

Although I think the ICachable marker interface is useless, I'd recommend making it just:

public static <T> T getCacheableElement(Class<T> cacheableElementClass, Object... params) {
\$\endgroup\$
1
  • 1
    \$\begingroup\$ IMHO, this is too hacky for what you want to achieve. I'd maybe you it if there were tens of cacheableElementClasses, but even then it's not nice. And probably slower than the more direct solution I'm just about to figure out.... Now I see that it may make sense. However, I'd probably avoid the whole problem by not using an universal all-classes-eating Cache. \$\endgroup\$
    – maaartinus
    Commented Jun 22, 2015 at 14:27
5
\$\begingroup\$

Since your cache will be used application-wide, I think it should be thread-safe because otherwise the state of your cache might change halfway through an operation. To do this, add the synchronized keyword to your method signature. (I've never done thread-safety in java, only c#, so I might be a little off on the best practices about that in java.)

Also, the method named getCacheableElement might add an object into the cache, but it is impossible to know this without first checking the source code. I don't think a single method to do add and get is a good idea. Why not split in 2 different methods?

This cache implementation is not generic. Imagine I want to use your cache in my application, I wouldn't be able to do it since my classes wouldn't be part of your if,else if,else.

Throwing NullPointerException isn't a good idea. You might want to create a custom exception like KeyNotFoundException.

\$\endgroup\$
1
  • \$\begingroup\$ Hi TopinFrassi, I split the operations in two different methods thinking on taking further the single responsibility principle; it's perfectly reasonably your advice, because the principle talk about classes and not about methods. Thanks! \$\endgroup\$ Commented Jun 22, 2015 at 19:13
4
\$\begingroup\$

I've answered the question before it's reached it final state, so I'm adding a review on this part:

    if (cacheableElementClass.equals(SourceCode.class)) {
        cacheMap.put(cacheableElementClass, new SourceCode((String) params[0]));
    } else if (cacheableElementClass.equals(SymbolTable.class)) {
        cacheMap.put(cacheableElementClass, new SymbolTable());
    } else {
        // Error!
        throw new NullPointerException();
    }

It feels hacky and the problem seems to be the very existence of the universal all-classes-managing Cache and the existence of ICacheable. With not concentrating the code around this, you could have something like this

public SourceCode getSourceCode(String param0) {
    SourceCode result = sourceCodeCache.get(param0);
    if (result == null) {
        result = new SourceCode(param0);
        sourceCodeCache.put(param0, result);
    }
    return result;
}

Above, I'm assuming that you need multiple elements per cacheableElementClass. Assuming I'm right, you get more type safety by

  • returning SourceCode rather than ICacheable
  • passing String rather than Object...

Assuming you really need just one element per class, then you should consider pre-creating all of them and use a ClassToInstanceMap to retrieve them in a type-safe manner.


If you can't pre-create one stupid instance per class, then reconsider the whole design. It may be right, but probably is not.

An afterthought

Maybe you need something completely different, namely a couple of singletons. Probably, there should be exactly one SourceCode and exactly one SymbolTable in your whole code. This is where the singleton (anti-)pattern would help. A better alternative is to use DI, i.e., create all needed stuff somewhere in main and pass it to everyone needing it. It may get verbose, but it's pretty simple and perfectly testable. Once it gets complicated, use Guice to manage your dependencies.

\$\endgroup\$
3
\$\begingroup\$

Original answer is left below unaltered but my opinion has changed. The Java Class Library prefers NullPointerException to IllegalArgumentException when the element is required to be non-null. I think it's better to match their style here rather than be opinionated. Also, NullPointerException is more specific than IllegalArgumentException. Besides, you can use Objects.requireNonNull without importing any third party dependencies.


A small thing, in addIfNotExists you check if it's a few different types and if it's not you do throw new NullPointerException();.

if (cacheableElementClass.equals(SourceCode.class)) {
    cacheMap.put(cacheableElementClass, new SourceCode((String) params[0]));
} else if (cacheableElementClass.equals(SymbolTable.class)) {
    cacheMap.put(cacheableElementClass, new SymbolTable());
} else {
    // Error!
    throw new NullPointerException();
}

I don't like the use of NullPointerException here. If cacheableElementClass is null then it's fine (but I still don't like it). It seems better to throw a InvalidArgumentException. In addition, if it actually was null it would throw a "real" NullPointerException at the first if statement.


I recommend this, but it's ugly. I'd like it better if the InvalidArgumentException throwing was at the top, but it's a trivial change you can figure on your own (It makes sense for all preconditions to be at the top).

if (cacheableElementClass == null) {
    // Error!
    throw new NullPointerException();
}

if (cacheableElementClass.equals(SourceCode.class)) {
    cacheMap.put(cacheableElementClass, new SourceCode((String) params[0]));
} else if (cacheableElementClass.equals(SymbolTable.class)) {
    cacheMap.put(cacheableElementClass, new SymbolTable());
} else {
    // Error!
    throw new InvalidArgumentException();
}

The only reason I even include the manual throwing of NullPointerException is because I assume you do more stuff in the // Error! part, if you don't, remove it because it will do that on its own without you writing it in.


If you're using Preconditions library you can do this:

Preconditions.checkNotNull(cacheableElementClass);
// Preconditions.checkArgument(cacheableElementClass != null);
Preconditions.checkArgument(cacheableElementClass.equals(SourceCode.class) || cacheableElementClass.equals(SymbolTable.class));

if (cacheableElementClass.equals(SourceCode.class)) {
    cacheMap.put(cacheableElementClass, new SourceCode((String) params[0]));
} else if (cacheableElementClass.equals(SymbolTable.class)) {
    cacheMap.put(cacheableElementClass, new SymbolTable());
}

The commented out line is basically the same as the checkNotNull but it throws InvalidArgumentException which to me is just more informative in this case than NullPointerException, but it's up to you which you prefer.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks a lot! I didn't know about Preconditions... I'll check it right now! \$\endgroup\$ Commented Jun 22, 2015 at 19:07
  • 2
    \$\begingroup\$ @Leonardo I added a link to the GitHub for the project (Guava), specifically the Preconditions class page. Guava is also useful for adding functional programming stuff that Java 8 added if you are stuck using a previous version for whatever reason. (Unfortunately you won't get lambda syntax...) \$\endgroup\$ Commented Jun 22, 2015 at 19:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.