Creating a complex data structure

Viewed 195

I am trying to self learn c# (asp.net core) by creating a little quiz app, but I am confused on how to create a complex data model. Imagine a quiz model with a collection of questions like this:

    public class Quiz
    {
       public int Id { get; set; }
       public Icollection<Question> Questions { get; set; }
    }

But what if I want different kinds of quesitons (multiple choice, true/false, text answer...) Can I use inheritance to shorten somethings or do i just have to create a different model for each type of question and put them in like this:

Public class Quiz
{
   public Icollection<MultipleChoicQuestion> Questions { get; set; }
   public Icollection<TrueOrFalseQuestion> Questions { get; set; }
   public Icollection<TextQuestion> Questions { get; set; }
}
2 Answers

One way to do this is to create an IQuestion interface that contains all the public-facing methods and properties you need to run a quiz:

public interface IQuestion
{
    void AskQuestion();
    string CorrectAnswer { get; }
    bool IsCorrect { get; }
}

Then you can have a collection of this interface in your Quiz class:

public class Quiz
{
    public ICollection<IQuestion> Questions { get; set; }
}

Now we can create separate classes that each implement the IQuestion properties and methods in their own way.

For example:

public class TextQuestion : IQuestion
{
    public bool IsCorrect => string.Equals(_userAnswer?.Trim(), CorrectAnswer?.Trim(),
        StringComparison.OrdinalIgnoreCase);

    public string CorrectAnswer { get; }

    private readonly string _question;
    private string _userAnswer;

    public TextQuestion(string question, string answer)
    {
        _question = question;
        CorrectAnswer = answer;
    }

    public void AskQuestion()
    {
        Console.WriteLine(_question);
        _userAnswer = Console.ReadLine();
    }
}

public class MultipleChoiceQuestion : IQuestion
{
    public bool IsCorrect => _userIndex == _correctIndex + 1;

    public string CorrectAnswer => (_correctIndex + 1).ToString();

    private readonly string _question;
    private readonly List<string> _choices;
    private readonly int _correctIndex;
    private int _userIndex;

    public MultipleChoiceQuestion(string question, List<string> choices, int correctIndex)
    {
        _question = question;
        _choices = choices;
        _correctIndex = correctIndex;
    }

    public void AskQuestion()
    {
        Console.WriteLine(_question);

        for (var i = 0; i < _choices.Count; i++)
        {
            Console.WriteLine($"{i + 1}: {_choices[i]}");
        }

        _userIndex = GetIntFromUser($"Answer (1 - {_choices.Count}): ",
            i => i > 0 && i <= _choices.Count);
    }

    private static int GetIntFromUser(string prompt, Func<int, bool> validator = null)
    {
        int result;

        do
        {
            Console.Write(prompt);
        } while (!int.TryParse(Console.ReadLine(), out result) &&
                 (validator == null || validator.Invoke(result)));

        return result;
    }
}

public class TrueOrFalseQuestion : IQuestion
{
    public bool IsCorrect => _userAnswer == _correctAnswer;

    public string CorrectAnswer => _correctAnswer.ToString();

    private readonly string _question;
    private readonly bool _correctAnswer;
    private bool _userAnswer;

    public TrueOrFalseQuestion(string question, bool correctAnswer)
    {
        _question = question;
        _correctAnswer = correctAnswer;
    }

    public void AskQuestion()
    {
        _userAnswer = GetBoolFromUser(_question + " (true/false)");
    }

    private static bool GetBoolFromUser(string prompt)
    {
        bool result;

        do
        {
            Console.Write(prompt + ": ");
        } while (!bool.TryParse(Console.ReadLine(), out result));

        return result;
    }
}

Example Usage

static void Main()
{
    var quiz = new Quiz
    {
        Questions = new List<IQuestion>
        {
            new MultipleChoiceQuestion("Which color is also a fruit?",
                new List<string> {"Orange", "Yellow", "Green"}, 0),
            new TextQuestion("What is the last name of our first president?", 
                "Washington"),
            new TrueOrFalseQuestion("The day after yesterday is tomorrow", false),
        }
    };

    foreach (var question in quiz.Questions)
    {
        question.AskQuestion();
        Console.WriteLine(question.IsCorrect
            ? "Correct!\n"
            : $"The correct answer is: {question.CorrectAnswer}\n");
    }

    Console.WriteLine("Your final score is: " +
        $"{quiz.Questions.Count(q => q.IsCorrect)}/{quiz.Questions.Count}");

    GetKeyFromUser("\nPress any key to exit...");
}

This question is not totally related to .NET Core or EF, as you tagged, its about data modeling.

For those kinda different type Models, I suggest you make it as following.

Here is the Quiz Model.

public class Quiz
{
    public int Id { get; set; }
    public List<Question> Questions { get; set; }
}

Question with enum

public class Question
{
    public int Id { get; set; }

    public QuestionType Type { get; set; }

    public List<Answer> Answers { get; set; }
}

public enum QuestionType
{
    MultipleChoice,
    TrueFalse,
    Text
}

And the last Answers

public class Answer
{
    public int Id { get; set; }

    public int QuestionId { get; set; } 

    public Question Question { get; set; }

    public bool IsCorrect { get; set; }

    public string Value { get; set; }
}

For this way, the application layer will handle all process, but you'll store the data very easy.

For MultipleChoice questions, you add multiple answers and set IsCorrect = true which are correct.

For TrueFalse, add only 2 answer and set IsCorrect = true for the correct.

For Text, add only 1 answer and set IsCorrect = true.

Related