I'm creating a factory design for math problems. The idea which I have is:
- When the factory initializes, creates in a list some problems (20 initially)
- If the program wants more than 20, the list should grow until reach the requested quantity.
For example, if I require 30 problems of the X problem, will generate two times.
- Some problems must be generated more difficult. I pass in the constructor the level and the factory must generate them.
- To do this, I've got an abstract method called
ConfigureLevels
, where you has to set up. - I set an abstract method called Generate, this one must be implemented in a concrete class.
- When the problem is generated, sometimes is not a good problem. When this happen, it must generate another's until gets a good problem. A good problem is according to a CRITERIA. I mean, the problem mustn't exist in the list, if we refer that the factory generates FRACTIONS, must be below than 1, etc. And I haven't done yet this last feature.
This is the factory which I'm talking to:
public abstract class Factory<T> where T : IProblem
{
protected static Random rnd;
protected LinkedListNode<T> actualNode;
protected LinkedList<T> problems;
public virtual int TotalProblemsPossible { get; set; }
protected virtual int InitialSize { get; set; }
protected Factory(Levels level)
{
this.InitialSize = 20;
Initialize();
ConfigureLevels(level);
FirstTime();
}
public virtual void FirstTime()
{
if (rnd == null) rnd = new Random(100);
if (problems == null)
{
problems = new LinkedList<T>();
Generate(problems);
}
actualNode = problems.First;
}
public virtual T CreateProblem()
{
if (actualNode.Next == null)
{
Generate(problems);
}
actualNode = actualNode.Next;
return actualNode.Value;
}
private void Generate(LinkedList<T> problems)
{
for (int i = 0; i < InitialSize; i++)
{
T newProblem;
int bucles = 0;
do
{
if (bucles == 50) throw new InvalidOperationException();
newProblem = Generate();
bucles++;
} while (problems.Contains(newProblem));
problems.AddLast(newProblem);
}
}
protected virtual void Initialize()
{
}
protected abstract T Generate();
protected abstract void ConfigureLevels(Levels level);
}
And here is a concrete class. Check how I'm overriding some methods from the abstract Factory. The factory create times table problems, and it knows how to calculates.
public class TimesTableProblemFactory : Factory<BinaryProblem>
{
private Bound<int> Bound1 { get; set; }
private Bound<int> Bound2 { get; set; }
public TimesTableProblemFactory(Levels level)
: base(level)
{
}
protected override void Initialize()
{
base.Initialize();
}
protected override void ConfigureLevels(Levels level)
{
switch (level)
{
case Levels.Easy:
this.Bound1 = new Bound<int>(2, 6);
this.Bound2 = new Bound<int>(3, 11);
break;
case Levels.Normal:
this.Bound1 = new Bound<int>(3, 13);
this.Bound2 = new Bound<int>(3, 10);
break;
case Levels.Hard:
this.Bound1 = new Bound<int>(6, 13);
this.Bound2 = new Bound<int>(3, 10);
break;
}
}
protected override BinaryProblem Generate()
{
BinaryProblem problem;
int number1 = rnd.Next(Bound1.Min, Bound1.Max);
int number2 = rnd.Next(Bound1.Min, Bound1.Max);
Answer<decimal> correctValue = new Answer<decimal>(number1 * number2);
problem = new BinaryProblem(number1, number2, Operators.Multiplication, correctValue);
return problem;
}
}
Here is another factory for the same problem, which creates additions and multiplications:
public class BinaryProblemFactory : Factory<BinaryProblem>
{
private Bound<int> _bound;
private LinkedList<Operators> _operatorsList;
private LinkedListNode<Operators> _node;
public BinaryProblemFactory(Levels level)
: base(level)
{
}
protected override void Initialize()
{
_bound = new Bound<int>();
_operatorsList = new LinkedList<Operators>(new List<Operators>() { Operators.Addition, Operators.Multiplication });
base.Initialize();
}
protected override void ConfigureLevels(Levels level)
{
switch (level)
{
case Levels.Easy:
this._bound = new Bound<int>(10, 100);
break;
case Levels.Normal:
this._bound = new Bound<int>(50, 200);
break;
case Levels.Hard:
this._bound = new Bound<int>(100, 10000);
break;
}
}
private Operators NextOperator()
{
if (_node == null || _node.Next == null)
{
_node = _operatorsList.First;
}
else
{
_node = _node.Next;
}
return _node.Value;
}
protected override BinaryProblem Generate()
{
BinaryProblem problem;
int number1 = rnd.Next(_bound.Min, _bound.Max);
int number2 = rnd.Next(_bound.Min, _bound.Max);
Answer<decimal> correctValue = new Answer<decimal>(number1 + number2);
problem = new BinaryProblem(number1, number2, NextOperator(), correctValue);
return problem;
}
}
I feel is not a well design, so I need a little of help to organize these factories. I'm gonna create around 200 hundreds of it. So, I prefer fix these instead spent pains.
Now I need to know a list of available levels. I guess create three three virtual functions CanConfigureXLevel
is not a good way.
Maybe it'll be great create a some dictionary which contains the available levels (Level enum as key) and value should have something like a container of objects useful to generate the problem (for example binary and times tables both needs two bound objects).
GrowSize
, why don't you just generate problems problems as you need them? It's not like it's going to be faster to generate many problems in a row, right? We're not talking about resizing an array here. \$\endgroup\$