Я думаю, что ваш ProblemFactory потенциально пытается сделать слишком много, фабрика должна нести ответственность только за создание экземпляров и , зная, какие типы экземпляров создавать, без дополнительных затрат на знаниео конфигурациях.
Имея это в виду, вот как я бы подошел к проблеме:
/// <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; }
}
Для этого нам понадобится класс конфигурации проблемы
/// <summary>
/// A problem configuration class
/// </summary>
public class ProblemConfiguration
{
public int MinValue { get; set; }
public int MaxValue { get; set; }
public Operator Operator { get; set; }
}
Я бы тожевыделенный класс для обработки конфигурации уровней и удаления ее из класса фабрики.
/// <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];
}
}
Это позволит двоичной конфигурации выглядеть примерно так:
/// <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
};
}
}
Теперь фабрикадолжен только отвечать за создавать новые экземпляры проблем и знать, какие типы проблем он может обслуживать
/// <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);
}
}
Тогда вот как вы можете использовать все это
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);
}
}
Конечно, если вам просто нужно абстрагироваться от своих конфигураций, вам может не понадобиться заводская установка, все зависит от того, как вы собираетесь ее использовать.
IProblem problem3 = new BinaryProblem(binaryLevelConfig.GetProblemConfiguration(Level.Easy));
Возможные улучшения
Кроме того, если один проблемный класс всегда имеет проблемную конфигурацию, его можно еще улучшить до:
/// <summary>
/// Each class that can generate a problem should accept a level configuration
/// </summary>
public class BinaryProblem : IProblem
{
private static BinaryLevelConfiguration _levelConfiguration = new BinaryLevelConfiguration();
public BinaryProblem (Level level)
{
ProblemConfiguration configuration = _levelConfiguration.GetProblemConfiguration(level);
// 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; }
}
Тогда все, что вам нужно сделать, это:
IProblem problem4 = new BinaryProblem(Level.Easy);
Так что все сводится к тому, как вам нужно все это потреблять.Мораль этого поста заключается в том, что нет необходимости пытаться настроить конфигурацию в абстрактной фабрике, если все, что вам нужно, это конфигурация, все, что нужно сделать фабрике, это создать экземпляры и знать, какие типы создавать, вот и все, но выне реаааально это нужно:)
Удачи!