Assuming configuration manager is flat, i.e. it looks like this:
interface IConfigurationManager
{
void Get(ConfigurationKey key);
void Set(ConfigurationKey key, string value);
}
public class ConfigurationKey
{
public ConfigurationKey(string name)
{
Name = name;
}
public string Name { get; private set; }
}
I came up with this approach:
public static class ConfigurationKeys
{
public static class Root
{
public static class Database
{
public static ConfigurationKey Host;
public static ConfigurationKey Port;
public static ConfigurationKey User;
}
public static class X
{
public static ConfigurationKey Enabled;
}
public static class Y
{
public static ConfigurationKey Enabled;
}
}
public static void Build()
{
Build(typeof (ConfigurationKeys));
}
private static void Build(Type type)
{
var configurationKeyFields = type
.GetFields()
.Where(field => field.FieldType == typeof (ConfigurationKey));
foreach (FieldInfo field in configurationKeyFields)
field.SetValue(null, CreateConfigurationKey(type, field));
var nestedClasses = type.GetNestedTypes().Where(nestedType => nestedType.IsClass);
foreach (Type nestedClass in nestedClasses)
Build(nestedClass.UnderlyingSystemType);
}
private static ConfigurationKey CreateConfigurationKey(Type type, FieldInfo field)
{
var parentPath = type.FullName.Substring(typeof (Root).FullName.Length + 1).Replace("+", "/");
return new ConfigurationKey(parentPath + "/" + field.Name);
}
}
Now it's easy to access it by something like:
ConfigurationKeys.Build();
var cm = new ConfigurationManager();
cm.Set(ConfigurationKeys.Root.Database.Host, "localhost");
ConfigurationKeys.Root.Database.Host // == "Database/Host"
Pros:
- readable
- easy to refactor
- almost no boilerplate
- nested, thus successfully depicting reality and Intellisense-friendly
Cons:
- almost impossible to mock it in tests due to static classes and nested types
- difficult to port to other languages due to its reliance on reflection usage
- bogus call to a method like
Build()
; unfortunately, it cannot be superseded with static constructor because accessing nested types wouldn't fire parent static constructors, and supplying each nested type with static constructor adds boilerplate