views:

364

answers:

6

I have a class that manages user preferences for a large software project. Any class in the project that may need to set or retrieve a user preference from a persistent store is to call the static methods on this class. This centralized management allows the preferences to be completely wiped programmatically - which would be impossible if each pref was handled close to its use code, sprinkled throughout the project.

I ran into another implication of the centralization design in the course of this. The software has a public API. That API can be provided by itself in a jar. Classes in that API might refer to the pref management class. So, the pref manager has to go in the API jar.

Each preference might have a default value. Upon software startup, that default might be computed. The algorithm depends on the preference, and thus tends to reside near the use code. So if the pref manager needs to come up with a default, it calls the class in question.

But now that pref manager has become an "octopus class", sucking in all sorts of classes into the API jar that shouldn't be there. If it doesn't, then programs using the API jar quickly run into ClassDef exceptions. If it does, then the API jar is now bloated, as each of those other classes may refer to still others.

In general, do other Java programmers manage their preferences with a centralized class?

Does it make sense to distribute that static pref management class as part of a public API?

Should that pref manager be the keeper of the code for determining defaults?

+10  A: 

IMHO, I think that the answer to your first question is "yes" and "no".

Preferences are commonly handled as a centralized class, in the sense that the class is a "sink" for many classes in the project. Trying to do it closer to the calling code means that if the same preference is later useful elsewhere, you are in trouble. In my experience, trying to put the preferences "too close" also results in a very inconsistent handling.

That being said, it is often preferable to use multiple preference classes or "preference set", each supporting a module or submodule. If you look at the main components of your architecture, you will often find that the set of preferences can be logically partitioned. This reduces the mess in each preference class. More importantly, it will allow you to split your program into multiple jars in the future. Now, the "default value" calculators can be placed in the module but still in a global enough area.

I would also suggest not setting preferences directly as static methods, but rather using some getInstance() like operation to obtain a shared instance of the preferences manage, and then operating on it. Depending on your semantics, you may want to lock that object for a while (e.g., while the user is editing preferences in a UI) and this is easier if you have an actual object.

For your other questions, I would say that your public API should have a way of letting users change preferences, but only if you can document well enough what the results of these changes could be.

If you use a single API function to get the "reference manager", you can give users the possibility of providing their own "default values calculator". The preference manager will ask this calculator first before resorting to the one you have provided by default.

Uri
Wow; great answer. I have maybe minor quibbles with 10% of it, but the partitioning of related prefs was a good call, and one I should have thought of.
Paul Brinkley
I often face the same problem (though luckily Eclipse doesn't give me many choices), I'd be interested in hearing your quibbles :)
Uri
As for getting a shared manager instance - I wasn't really worried about any locking except at the disk writing level (which Java handles), but of course there's a higher level of abstraction I realize I should be concerned about, too.
Paul Brinkley
The one thing I probably won't do in this particular case is parametrize "default values calculators". No doubt useful in other cases, but I'm intent on hardcoded algorithms here, to simplify the design.
Paul Brinkley
+4  A: 

Can't you just handle preferences in a really generic way? You'd then just use the preference manager to handle the persistence. So from a class you'd just say to the preference manager PreferenceManager.setPreference(key, value) and it doesn't care what it's saving in terms of the semantics of the data.

Or am I simplifying this too much?

Problems come when you want to do things like clear all of the preferences - for instance, when testing the behavior of a fresh install. Then all those preferences various devs were using throughout the code base become hard to track. And that's just one problem.
Paul Brinkley
A: 

You might want to look at Cocoa's NSUserDefaults class for inspiration. It handles the problem you describe by having several layers of preferences, called domains. When you look up the value for a key, such as "PrintUsingAllCaps", it first checks for a value in the user's local domain. If it isn't found there, it can check the system-wide domain, or a network-level domain, and so on.

The absolute last place it checks is called the "Registration Domain", which is basically where hard coded defaults are supposed to go. So, at any point in my code, I can write a preference into the registration domain, and NSUserDefaults will only serve that value if the user hasn't overridden it.

So, in your case, you could provide a method for classes to set a default value for a key before it accesses the (possibly) user defined value. The preferences class doesn't have to know anything about the classes it is serving.

As somebody else suggested, if you need something more sophisticated, you could set a DefaultValueProvider callback object instead of a straight value.

benzado
+2  A: 

I'm no Java Dev, but as far as the whole "octopus class" thing goes, can you not just supply an interface to the jar and connect the calls between the interface and the prefs manager at runtime, using the application configuration file to determine the prefs manager to instantiate?

Something like the .NET provider pattern?

That way you can decouple your jar from the prefs manager.

rism
A: 

I deleted my first answer since I misunderstood what the author was asking.

To actually address the actual question--it feels like your desire to place preferences (and the calculation of the default values) with the code that uses them makes sense.

Could you meet both requirements by using a preferences container class for each area that follows a pattern for that area, but having it register with a "Global" preference object collection?

Your global collection could do things like iterate over each set of preferences and reset it to defaults but your preferences themselves would still be locally defined and maintained so that it doesn't spider out into other parts of the code.

The only problem I can see is that if you allow the preference object to register itself when instantiated, you run the risk of trying to "reset to defaults" with some of the preferences not instantiated yet.

I suppose this could be fixed by having the main "preference" class instantiate all the others, then any piece of code could retrieve it's local preference object from the central one though a static getter.

This seems to be a lot like how some loggers work. There is a central mechanism for maintaining log levels, output streams and such, but each class has it's own instance of a "log" method and logs to it.

I hope this was more on target. Oh, I also agree with the accepted answer, don't ever make all your methods public static, always use a getter--you'll be glad you did some day.

Bill K
A: 

The JSR-10 (java.util.prefs.*) API uses a factory method with a Class<?> parameter to create Preferences instances. That way the API can store preferences from different classes belonging to the same package in a single file.

martijno