7
\$\begingroup\$

I'm having a hard time creating a design (due to my lack of experience). I need a little of help to create the Model.

It's a mathematics system. I've created an IProblem interface with a function IsCorrect, this function helps me to know if the result is correct or not (by parameter). It also has a function CreateAnswer() because some problems have 1, 2 or many answers.

public interface IProblem
{
    bool IsCorrect(IAnswer answer);
    IAnswer CreateAnswer();
}

IAnswer is an interface which contains the answer, it can be a string, integer, decimal, several, multiple choice, etc..

public interface IAnswer
{
}

public class IntegerAnswer : IAnswer
{
    public int Number { get; set; }
}

public class StringAnswer : IAnswer
{
    public string Number { get; set; }
}

public class DecimalAnswer : IAnswer
{ ... }

public class MultipleChoiceAnswer : IAnswer
{ ... }

Here is a concrete Problem:

public class BinaryProblem : IProblem,  IEquatable<BinaryProblem>
{
    private int _number1;
    private int _number2;

    public int Number1
    {
        get { return _number1; }
        set { _number1 = value; }
    }

    public int Number2
    {
        get { return _number2; }
        set { _number2 = value; }
    }        

    public BinaryProblem(int number1, int number2)
    {
        this.Number1 = number1;
        this.Number2 = number2;
    }

    public bool IsCorrect(IAnswer answer)
    {
        IntegerAnswer integerAnswer = (IntegerAnswer)answer;
        return integerAnswer.Number == _number1 + _number2;
    }

    public IAnswer CreateAnswer()
    {
        return new IntegerAnswer();
    }

    public override string ToString()
    {
        return _number1 + " + " + _number2;
    }

    public override bool Equals(object obj)
    {
        if (obj is BinaryProblem)
            return this.Equals(obj);
        else
            return false;
    }

    public bool Equals(BinaryProblem other)
    {
        return this.Number1 == other.Number1 &&
               this.Number2 == other.Number2;
    }
}

And to run this:

        // Binary problem test
        BinaryProblem binary = new BinaryProblem(4, 5);
        IntegerAnswer answer = (IntegerAnswer)binary.CreateAnswer();
        answer.Number = 9;
        binary.IsCorrect(answer);

I realized it is necessary to store the correct answer. For example, if it's a problem where the stated question is: convert decimal to fraction.

enter image description here

My limitation here is that I need to know the result at the moment the problem is created. I'm convinced that the Answer doesn't belong to the problem, but also, the class needs to know the correct answer when it is created. Because 0.111 it's confusing, it can be 1/9 or 111/1000, so I prefer to store the correct answer in some place.

So, where should I modify the model?

I created a factory where one can create problems, which contains values of necessary properties and the correct answer. I'd like to know if the performance or OOP rules are correct, or if there are things to do to improve the factory.

I've modified the answers by a generic class.

This is the interface for my factories:

public interface IFactory<T> where T : IProblem
{
    T CreateProblem();
}

My base class factory:

public abstract class Factory<T> : IFactory<T> where T : IProblem
{
    protected const int DEFAULT_SIZE = 20;
    protected const int MAX_SIZE = 100;

    protected static Random rnd;
    protected static LinkedListNode<T> actualNode;
    protected static LinkedList<T> problems;

    public virtual int TotalProblemsPossible { get; set; }

    protected Factory(Levels level)
    {
        Initialize();
        ConfigureLevels(level);
        FirstTime();
    }

    public virtual void FirstTime()
    {
        if (rnd == null) rnd = new Random(MAX_SIZE);
        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 < DEFAULT_SIZE; 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 a particular factory. At this factory case, I create a class called Bound which contains limits for a number, and gets some numbers randomly to create the problem.

public class BinaryProblemFactory : Factory<BinaryProblem>
{
    private Bound<int> Bound { get; set; }

    public BinaryProblemFactory(Levels level)
        : base(level)
    {
    }

    protected override void Initialize()
    {
        Bound = new Bound<int>();
        base.Initialize();
    }

    protected override void ConfigureLevels(Levels level)
    {
        switch (level)
        {
            case Levels.Easy:
                this.Bound.Min = 10;
                this.Bound.Max = 100;

                break;
            case Levels.Normal:
                this.Bound.Min = 50;
                this.Bound.Max = 200;

                break;
            case Levels.Hard:
                this.Bound.Min = 100;
                this.Bound.Max = 10000;

                break;
        }
    }

    protected override BinaryProblem Generate()
    {
        BinaryProblem problem;

        int number1 = rnd.Next(Bound.Min, Bound.Max);
        int number2 = rnd.Next(Bound.Min, Bound.Max);
        Answer<int> correctValue = new Answer<int>(number1 + number2);

        problem = new BinaryProblem(number1, number2, correctValue);
        return problem;
    }
}

The idea is to do something similar with each problem type I want to add to the program. What do you think?

For testing:

        BinaryProblemFactory factory = new BinaryProblemFactory(Levels.Normal);
        List<BinaryProblem> problemList = new List<BinaryProblem>();
        for (int i = 0; i < 50; i++)
        {
            BinaryProblem binary2 = (BinaryProblem)factory.CreateProblem();
            problemList.Add(binary2);
        }

Here is the current project.

\$\endgroup\$

migrated from programmers.stackexchange.com Jan 13 '12 at 19:25

This question came from our site for professionals, academics, and students working within the systems development life cycle.

  • 1
    \$\begingroup\$ Not a direct answer to your question, but you should override the bool Equals(object) method instead of (or in addition to) providing you own typed Equals method. Also, you should override the int GetHashCode() method. This will ensure that your object's instances are treated correctly when they are compared by things like the standard .NET collection classes. \$\endgroup\$ – Jonathan DeCarlo Jan 13 '12 at 20:11
4
\$\begingroup\$

Firstly, do you really need it to be that generic? You support arbitrary types and numbers of answers. But unnecessary genericness just make the design overly complicated. If you are only going to every have a single number answer, then just write code to do that.

Secondly, you store the answers on the Problem object. But the answers given are not a property of the problem. It makes more sense to have the problem take in the answers as a parameter to the IsCorrect() function.

Thirdly, your problem interfaces don't appear very useful. It seems that I get access and presumably modify the given answers, and then check if the answers are correct. But what about finding out what the problem is? There's not much point in an interface if you are just going to have to cast down to the actual class to do any work.

Fourthly, your provide getters/setters on the numbers and such for the problem. Do you really need them? Are you really changing the problems after they are created?

Fifthly, an obvious way to use OOP here would be to one class for each operator (multiple, divide, add, subtract, etc).

The way I'd approach this:

interface IProblem
{
    String GetQuestion();
    bool CheckAnswer(int answer);
}

class BinaryOperatorProblem : IProblem
{
     int number1, number2;
     BinaryOperatorProblem(int number1, int number2);
}

class AdditionProblem
{
     String GetQuestion()
     {
           return "%d + %d" % (number1, number2);
     }

     bool CheckAnswer(int answer)
     {
          return answer == number1 + number2;
     }
}

Notes: I don't do C# so above syntax is mostly guesses. I also don't know much about your problem, so you mileage will vary.

\$\endgroup\$
  • \$\begingroup\$ Well, I really need to store the values which the student wrote. Yes, I agree with that the generic interface is causing much trouble. So do you think is better to maintain another interface for the answers or results? \$\endgroup\$ – Darf Zon Jan 15 '12 at 5:26
  • \$\begingroup\$ And some problems will need several answers \$\endgroup\$ – Darf Zon Jan 15 '12 at 5:55
  • \$\begingroup\$ @DarfZon, if you need multiple answers I'd pass the answer as an array instead. Without a better idea what you are doing I'm not sure where I'd store the answers, but I wouldn't store them with the problem. \$\endgroup\$ – Winston Ewert Jan 15 '12 at 18:56
  • \$\begingroup\$ okay, by the momment, I've uploaded the project in mediafile (check the post, below). The idea which I have, is to store the problem and answers in a single class, where I plan later to serialize. I'm not ready to do that yet, but that's the idea. \$\endgroup\$ – Darf Zon Jan 15 '12 at 19:15
  • \$\begingroup\$ @DarfZon, but why do you want them in the same class? They are distinct concepts, don't necessarily have a one-to-one relationship, so it doesn't make any sense to me to put in them in one class. \$\endgroup\$ – Winston Ewert Jan 15 '12 at 20:40
3
\$\begingroup\$

Declaring the interfaces as below might proof useful:

public interface IProblem<T>
{
    bool IsCorrect(IAnswer<T> answer);
    IAnswer<T> CreateAnswer();
}

public interface IAnswer<T>
{
    T Value { get; set; }
}

public class Answer<T> : IAnswer<T>
{
    public T Value { get; set; }
}

This would allow for the following implementation:

public class BinaryProblem : IProblem<int>, IEquatable<BinaryProblem>
{
    private int _number1;
    private int _number2;

    // .....

    public bool IsCorrect(IAnswer<int> answer)
    {
        return answer.Value == _number1 + _number2;
    }

    public IAnswer<int> CreateAnswer()
    {
        return new Answer<int>();
    }

    //....
}

And can save you a ton of descendants, reduce the amount of knowledge you need within an IProblem implementation, while still offering the possibility of creating very specialized IAnswer implementations.

\$\endgroup\$
  • \$\begingroup\$ thanks for the advise. I started doing this, but generics was causing much trouble because, I guess later I need to put all those answers into an array. \$\endgroup\$ – Darf Zon Jan 17 '12 at 16:42
  • \$\begingroup\$ the project is updated! please read the edit and prefer download it \$\endgroup\$ – Darf Zon Jan 19 '12 at 21:45
  • \$\begingroup\$ @DarfZon: No offense, but this is not a "let's-build-a-project-together" forum. If you have a specific piece of code that you want reviewed, feel free to post it in a separate question. If you have a problem, feel free to post it on the appropriate forum. But this one question is not supposed to be a continuing saga of your project. \$\endgroup\$ – Willem van Rumpt Jan 20 '12 at 12:18
  • \$\begingroup\$ All right Willem, I'll bear in mind next time. \$\endgroup\$ – Darf Zon Jan 20 '12 at 17:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.