The point of this is to show you a better way to manage - a better way to encapsulate and access - your properties so that the implementation is hidden behind an expressive interface, easy for anyone to understand and use.
The point is to show the
So, this example has a strategy factory class that needs to make its decisions based on a property setting, this property having a true/false (boolean) value. It starts off looking like this:
public class GeneratorFactory {
public String propFileName =
"ELearningSettings.properties";
public GenerationStrategy projectGenerationStrategy() {
if (shouldIgnoreCode())
return new GenerateWithoutCode();
else
return new GenerateWithCode();
}
private boolean shouldIgnoreCode() {
Object valueObj = getProperty("CODE_IS_USELESS");
if (valueObj != null)
return Boolean.parseBoolean((String)valueObj);
return false;
}
private Object getProperty(String key) {
Properties properties = new Properties();
try {
properties.load(new FileInputStream(propFileName));
} catch (IOException e) {
throw new RuntimeException(e);
}
return properties.get(key);
}
}
You'll hopefully notice something about this factory. First, it's primary responsibility is to serve up the correct strategy to its caller. Of course though, you'll also notice that not only does this class do its primary purpose, but it also has a lot of low-level knowledge about how to obtain the information it needs to make its decision. It knows all sorts of things about icky java property mania.
Now, if this is the lone property in your system, than maybe this is okay and doing any kind of extraction here might be YAGNI (you might though, even if that is the case, be bothered by that fact that this class clearly violates the SRP, nonetheless...).
Of course though, this is not the case, and so let's assume that although this is the only class you see in this example there are in fact more properties in your system and more users of them.
So, the logical thing to do is to extract that property reading management into its own class, happy to have its sole responsibility being providing property values. You do so, and end up with this new "Settings" class:
Many systems I've seen have figured this much out, and have this in place. A "Settings" class that hides the details of how to look up properties, all you have to give it is the key to the property you want.
But, this has limitations.
First, and as many will figure out and solve on their own, a sprawl of the property key strings hard-coded in the various users of the properties. You want to change this key? Have fun chasing all the uses down. Of course you could create a constants file for these keys, and have the callers only know the constants (which, in Java at least, provides the big benefit of being able to leverage you IDE when you want to change its name), but is that really enough?
Second, yes your factory doesn't know about "ELearningSettings" or the "java...Properties" guts, but it does still have the job of knowing the format of these property values ('Object' in this case) and how to turn that into what it really wants, a boolean.
Third, particularly if haven't created a constants file (in response to the first problem), how easy is it for future developers to go to one place and see all the possible properties/settings the system has available for them?
There may be more limitations, but that's enough for me.
So, our answer to each of these questions is where the magic happens, the MBark of this article. We evolve our generic "Settings" class into a domain-friendly "PropertyFor" class:PropertyFor utility.
So, you'll see our factory now has to worry just about choosing a strategy, and no longer has any overhead related to obtaining the values it needs to make its decision. More specifically, it doesn't know where the settings come from, doesn't know the details of how to access them or the keys that identify them, and it doesn't know what raw format they come in. The factory knows it needs the boolean value of whether or not to "ignore code", and it gets that in one clear, simple line of code.
Further, you've got the beauty of expressive methods on singular class to be very domain-centric in assisting other programmers to access the available setttings/properties for your system - these methods, not only being clearly and expressively named, but also being typed appropriately for ultimate ease to the consumer. And of course, when you need to change the name of the setting and/or the name the callers know it by, no problemo.
So, that's really the main meat.
For good measure though, we take a moment to improve the design of our PropertyFor class. First, this isn't the only boolean setting we have (use your imagination) so we should have a nice clear reusable method to handle our boolean typed settings:
And then, it really is pretty inefficient of us to read the properties file over and over, so let's take care of doing that just once and being done with it:
PropertyFor .
And, of course, this isn't a post about TDD, but here is the test that was in place and passing before and after any of these refactorings went down:
So, the logical thing to do is to extract that property reading management into its own class, happy to have its sole responsibility being providing property values. You do so, and end up with this new "Settings" class:
public class ELearningSettings {You slap five with your pair and check in, this is much better! Now your factory can do it's job without having any internal worry about where the settings (properties) are really coming from or how they're implemented. Any other users of properties also get the same luxury. A big improvement, no doubt.
public String propFileName =
"ELearningSettings.properties";
public Object getProperty(String key) {
Properties properties = new Properties();
try {
properties.load(new FileInputStream(propFileName));
} catch (IOException e) {
throw new RuntimeException(e);
}
return properties.get(key);
}
}
public class GeneratorFactory {
public GenerationStrategy projectGenerationStrategy() {
if (shouldIgnoreCode())
return new GenerateWithoutCode();
else
return new GenerateWithCode();
}
private boolean shouldIgnoreCode() {
Object valueObj =
ELearningSettings.getProperty("CODE_IS_USELESS");
if (valueObj != null)
return Boolean.parseBoolean((String)valueObj);
return false;
}
}
Many systems I've seen have figured this much out, and have this in place. A "Settings" class that hides the details of how to look up properties, all you have to give it is the key to the property you want.
But, this has limitations.
First, and as many will figure out and solve on their own, a sprawl of the property key strings hard-coded in the various users of the properties. You want to change this key? Have fun chasing all the uses down. Of course you could create a constants file for these keys, and have the callers only know the constants (which, in Java at least, provides the big benefit of being able to leverage you IDE when you want to change its name), but is that really enough?
Second, yes your factory doesn't know about "ELearningSettings" or the "java...Properties" guts, but it does still have the job of knowing the format of these property values ('Object' in this case) and how to turn that into what it really wants, a boolean.
Third, particularly if haven't created a constants file (in response to the first problem), how easy is it for future developers to go to one place and see all the possible properties/settings the system has available for them?
There may be more limitations, but that's enough for me.
So, our answer to each of these questions is where the magic happens, the MBark of this article. We evolve our generic "Settings" class into a domain-friendly "PropertyFor" class:
public class PropertyFor {What did we do here? Well, we've taken all the knowledge highlighted above as our limitations and removed it from our factory (really, from all of the users of settings), and put it into our "Settings" class - which, as a result, now becomes a more useful, expressive
public static String propFileName =
"ELearningSettings.properties";
public static boolean shouldIgnoreCode() {
Object valueObj = getProperty("CODE_IS_USELESS");
if (valueObj != null)
return Boolean.parseBoolean((String)valueObj);
return false;
}
private static Object getProperty(String key) {
Properties properties = new Properties();
try {
properties.load(new FileInputStream(propFileName));
} catch (IOException e) {
throw new RuntimeException(e);
}
return properties.get(key);
}
}
public class GeneratorFactory {
public GenerationStrategy projectGenerationStrategy() {
if (PropertyFor.shouldIgnoreCode())
return new GenerateWithoutCode();
else
return new GenerateWithCode();
}
}
So, you'll see our factory now has to worry just about choosing a strategy, and no longer has any overhead related to obtaining the values it needs to make its decision. More specifically, it doesn't know where the settings come from, doesn't know the details of how to access them or the keys that identify them, and it doesn't know what raw format they come in. The factory knows it needs the boolean value of whether or not to "ignore code", and it gets that in one clear, simple line of code.
Further, you've got the beauty of expressive methods on singular class to be very domain-centric in assisting other programmers to access the available setttings/properties for your system - these methods, not only being clearly and expressively named, but also being typed appropriately for ultimate ease to the consumer. And of course, when you need to change the name of the setting and/or the name the callers know it by, no problemo.
So, that's really the main meat.
For good measure though, we take a moment to improve the design of our PropertyFor class. First, this isn't the only boolean setting we have (use your imagination) so we should have a nice clear reusable method to handle our boolean typed settings:
public class PropertyFor {
public static String propFileName =
"ELearningSettings.properties";
public static boolean shouldIgnoreCode() {
return boolanValueFor("CODE_IS_USELESS");
}
private static boolean boolanValueFor(String key){
Object valueObj = valueFor(key);
if (valueObj != null)
return Boolean.parseBoolean((String)valueObj);
return false;
}
private static Object valueFor(String key) {
Properties properties = new Properties();
try {
properties.load(new FileInputStream(propFileName));
} catch (IOException e) {
throw new RuntimeException(e);
}
return properties.get(key);
}
}
And then, it really is pretty inefficient of us to read the properties file over and over, so let's take care of doing that just once and being done with it:
public class PropertyFor {So there you have it:
public static String propFileName =
"ELearningSettings.properties";
private static Properties properties;
static {
loadProperties();
}
private static void loadProperties() {
properties = new Properties();
try {
properties.load(new FileInputStream(propFileName));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static boolean shouldIgnoreCode() {
return boolanValueFor("CODE_IS_USELESS");
}
private static boolean boolanValueFor(String key){
Object valueObj = properties.get(key);
if (valueObj != null)
return Boolean.parseBoolean((String)valueObj);
return false;
}
}
And, of course, this isn't a post about TDD, but here is the test that was in place and passing before and after any of these refactorings went down:
public class GenerationStrategyFactoryTest {As an exercise, go ahead and write the test you think I have for the PropertyFor class, because of course you know I do!
@Test
public void needingACodelessStrategy() {
propertiesFileIs("CodelessGenerationStrategy");
assertTrue(strategy() instanceof GenerateWithoutCode);
}
@Test
public void needingACodeStrategy() {
propertiesFileIs("IncludeCodeGenerationStrategy");
assertTrue(strategy() instanceof GenerateWithCode);
}
private void propertiesFileIs(String fileNameBase) {
String filePath = "testdata/"
+ fileNameBase + ".properties";
PropertyFor.propFileName = filePath;
}
private GenerationStrategy strategy() {
return new GeneratorFactory().
projectGenerationStrategy();
}
}
ps// I'm really not liking this code highlighter library so far. Any other Bloggers have good suggestions for a good library?!?