The factory should only have the responsibility of creating instances and knowing what types of instances to create, without having the added overhead of knowing about configurations.
With that in mind, here is how I would approach the problem:
/// <summary>
/// Each class that can generate a problem should accept a problem configuration
/// </summary>
public class BinaryProblem : IProblem
{
public BinaryProblem (ProblemConfiguration configuration)
{
// sample code, this is where you generate your problem, based on the configuration of the problem
X = new Random().Next(configuration.MaxValue + configuration.MinValue) - configuration.MinValue;
Y = new Random().Next(configuration.MaxValue + configuration.MinValue) - configuration.MinValue;
Answer = X + Y;
}
public int X { get; private set; }
public int Y { get; private set; }
public int Answer { get; private set; }
}
For this we will need a problem configuration class
/// <summary>
/// A problem configuration class
/// </summary>
public class ProblemConfiguration
{
public int MinValue { get; set; }
public int MaxValue { get; set; }
public Operator Operator { get; set; }
}
I would also a dedicated class for handling the configuration of levels and remove it from the factory class.
/// <summary>
/// The abstract level configuration allows descendent classes to configure themselves
/// </summary>
public abstract class LevelConfiguration
{
protected Random Random = new Random();
private Dictionary<Level, ProblemConfiguration> _configurableLevels = new Dictionary<Level, ProblemConfiguration>();
/// <summary>
/// Adds a configurable level.
/// </summary>
/// <param name="level">The level to add.</param>
/// <param name="problemConfiguration">The problem configuration.</param>
protected void AddConfigurableLevel(Level level, ProblemConfiguration problemConfiguration)
{
_configurableLevels.Add(level, problemConfiguration);
}
/// <summary>
/// Removes a configurable level.
/// </summary>
/// <param name="level">The level to remove.</param>
protected void RemoveConfigurableLevel(Level level)
{
_configurableLevels.Remove(level);
}
/// <summary>
/// Returns all the configurable levels.
/// </summary>
public IEnumerable<Level> GetConfigurableLevels()
{
return _configurableLevels.Keys;
}
/// <summary>
/// Gets the problem configuration for the specified level
/// </summary>
/// <param name="level">The level.</param>
public ProblemConfiguration GetProblemConfiguration(Level level)
{
return _configurableLevels[level];
}
}
This would allow the Binary Configuration to look something like this:
/// <summary>
/// Contains level configuration for Binary problems
/// </summary>
public class BinaryLevelConfiguration : LevelConfiguration
{
public BinaryLevelConfiguration()
{
AddConfigurableLevel(Level.Easy, GetEasyLevelConfiguration());
AddConfigurableLevel(Level.Medium, GetMediumLevelConfiguration());
AddConfigurableLevel(Level.Hard, GetHardLevelConfiguration());
}
/// <summary>
/// Gets the hard level configuration.
/// </summary>
/// <returns></returns>
private ProblemConfiguration GetHardLevelConfiguration()
{
return new ProblemConfiguration
{
MinValue = 100,
MaxValue = 1000,
Operator = Operator.Addition
};
}
/// <summary>
/// Gets the medium level configuration.
/// </summary>
/// <returns></returns>
private ProblemConfiguration GetMediumLevelConfiguration()
{
return new ProblemConfiguration
{
MinValue = 10,
MaxValue = 100,
Operator = Operator.Addition
};
}
/// <summary>
/// Gets the easy level configuration.
/// </summary>
/// <returns></returns>
private ProblemConfiguration GetEasyLevelConfiguration()
{
return new ProblemConfiguration
{
MinValue = 1,
MaxValue = 10,
Operator = Operator.Addition
};
}
}
Now the factory should only be responsible for creating new instances of problems and knowing what types of problems it can serve
/// <summary>
/// The only responsibility of the factory is to create instances of Problems and know what kind of problems it can create,
/// it should not know about configuration
/// </summary>
public class ProblemFactory
{
private Dictionary<Type, Func<Level, IProblem>> _registeredProblemTypes; // this associates each type with a factory function
/// <summary>
/// Initializes a new instance of the <see cref="ProblemFactory"/> class.
/// </summary>
public ProblemFactory()
{
_registeredProblemTypes = new Dictionary<Type, Func<Level, IProblem>>();
}
/// <summary>
/// Registers a problem factory function to it's associated type
/// </summary>
/// <typeparam name="T">The Type of problem to register</typeparam>
/// <param name="factoryFunction">The factory function.</param>
public void RegisterProblem<T>(Func<Level, IProblem> factoryFunction)
{
_registeredProblemTypes.Add(typeof(T), factoryFunction);
}
/// <summary>
/// Generates the problem based on the type parameter and invokes the associated factory function by providing some problem configuration
/// </summary>
/// <typeparam name="T">The type of problem to generate</typeparam>
/// <param name="problemConfiguration">The problem configuration.</param>
/// <returns></returns>
public IProblem GenerateProblem<T>(Level level) where T: IProblem
{
// some extra safety checks can go here, but this should be the essense of a factory,
// the only responsibility is to create instances of Problems and know what kind of problems it can create
return _registeredProblemTypes[typeof(T)](level);
}
}
Then here is how you could use all this
class Program
{
static void Main(string[] args)
{
ProblemFactory problemFactory = new ProblemFactory();
BinaryLevelConfiguration binaryLevelConfig = new BinaryLevelConfiguration();
// register your factory functions
problemFactory.RegisterProblem<BinaryProblem>((level) => new BinaryProblem(binaryLevelConfig.GetProblemConfiguration(level)));
// consume them
IProblem problem1 = problemFactory.GenerateProblem<BinaryProblem>(Level.Easy);
IProblem problem2 = problemFactory.GenerateProblem<BinaryProblem>(Level.Hard);
}
}
The problem is, in BinaryProblemConfiguration methods, I'd like to add more configurations in the methods, I mean, returns something like an IEnumerable, but I'm not sure how would I modify ProblemFactory.
I guess I need to create another class ListProblemFactory, and maintain the last configuration to show the next one.
I mean generate problems with the configs sequently.
BinaryLevelConfiguration. – Quentin Pradet Sep 20 '12 at 9:57