简体   繁体   中英

how do i call a subclass method on a superclass list containing subclasses

I had a hard time wording my title so let me try to elaborate now. First here is my relevant code :

class Question
    {
        static bool checkFile(XElement q)
        {
            foreach (XElement a in q.Descendants())
            {
                if (a.Name.LocalName == "file")
                {
                    return true;
                }
            }
            return false;
        }
        protected string questionText;
        protected List<File> files;
        protected Question question;
        public Question(XElement q)
        {
            questionText = q.Element("questiontext").Element("text").Value.ToString();
            string name = q.Attribute("type").Value.ToString();
            if (checkFile(q))
                files.Add(new File(q));
        }
    }
    class multichoice : Question
    {
        private List<string> answers;
        public multichoice(XElement q)
            : base(q)
        {
            foreach (XElement a in q.Elements())
            {
                if (a.Name.LocalName == "answer")
                    answers.Add(a.Element("text").Value.ToString());
            }
        }
        public void writeQuestion(HtmlTextWriter writer)
        {
            writer.RenderBeginTag("p");
            writer.Write("<strong>Multiple Choice: </strong>" + this.questionText);
            writer.RenderEndTag();
            writer.AddAttribute("type", "A");
            writer.RenderBeginTag("ol");
            foreach (string answer in answers)
            {
                writer.RenderBeginTag("li");
                writer.Write(answer);
                writer.RenderEndTag();
            }
            writer.RenderEndTag();
        }
    }
    class truefalse : Question
    {
        public truefalse(XElement q)
            : base(q)
        {

        }
        public void writeQuestion(HtmlTextWriter writer)
        {
            writer.RenderBeginTag("strong");
            writer.Write("True or False : ");
            writer.RenderEndTag();
            writer.Write(questionText);
        }
    }

So I am creating multiple types of questions all being subclasses of "Question". Question contains all data that applies to every type of question and these subclasses contain methods unique to them, the main one being "writeQuestion". Now what I am trying to do with it is something like this:

static List<Question> collectQuestions(XDocument doc)
    {
        XDocument xdoc = doc;
        string elementName = null;
        List<Question> questions = null;
        foreach (XElement q in xdoc.Descendants("question"))
        {
            elementName = q.Attribute("type").Value.ToString();
            if (elementName != "category")
                continue;
            if (elementName == "truefalse")
                questions.Add(new truefalse(q)); //writeTrueFalse(writer, q);
            else if (elementName == "calculatedmulti")
                questions.Add(new calculatedmulti(q)); // writeCalculatedMulti(writer, q);
            else if (elementName == "calculatedsimple")
                questions.Add(new calculatedsimple(q)); // writeCalculatedSimple(writer, q);
            else if (elementName == "ddwtos")
                questions.Add(new Draganddrop(q)); //writeDragAndDrop(writer, q);
            else if (elementName == "description")
                questions.Add(new Description(q)); // writeDescription(writer, q);
            else if (elementName == "essay")
                questions.Add(new Essay(q)); // writeEssay(writer, q);
            else if (elementName == "gapselect")
                questions.Add(new Gapselect(q)); // writeGapSelect(writer, q);
            else if (elementName == "matching")
                questions.Add(new Matching(q)); // writeMatching(writer, q);
            else if (elementName == "multichoice")
                questions.Add(new multichoice(q)); // writeMultipleChoice(writer, q);
            else if (elementName == "multichoiceset")
                questions.Add(new Allornothing(q)); // writeAllOrNothing(writer, q);
            else if (elementName == "numerical")
                questions.Add(new Numerical(q)); // writeNumerical(writer, q);
            else if (elementName == "shortanswer")
                questions.Add(new shortanswer(q)); // writeShortAnswer(writer, q);
            else
                continue;
        }
        return questions;
    }
questions = collectQuestions(someDocument);
foreach (Question question in questions)
            {
                question.writeQuestion(writer);
            } 

Is there a way to call the writeQuestion on each of the items? right now it of course gives the error that Questions does not contain a definition for writeQuestion even though each of its subclasses does. Please comment if i should add anything more for clarification, my code has gotten a little mangled as i have been repeatedly reworking it. I am decently new to working with classes like this so i may be missing some key concepts, please point out anything you see, thankyou.

让基类abstract ,抽象添加WriteQuestion成员的基类,然后override它在每个具体的实现。

IMHO I would split out your code into more classes to get a good separation of concerns.

Your Question class and specializations (ie derived classes) shouldn't know about how they're stored and also they shouldn't know about how they're turned into some format like an HTML representation.

I would define a class called XmlQuestionConverter :

public class XmlQuestionConverter
{   
    public XmlQuestionConverter() 
    {
        TypeToConvertMap = new Dictionary<Type, Action<Question, XElement>>
        {
            { typeof(TrueFalseQuestion), new Action<Question, XElement>(ConvertTrueFalseFromXml) }
            // other mappings...
        };
    }
    private Dictionary<Type, Action<Question, HtmlTextWriter>> TypeToConvertMap
    {
        get;
    }

     // This dictionary maps element names to their type
     private Dictionary<string, Type> QuestionTypeMap { get; } = new Dictionary<string, Type>()
     {
          { "truefalse", typeof(TrueFalseQuestion) },
          { "multichoice", typeof(MultiChoiceQuestion) }
          // And so on
     };

     public IList<Question> ConvertFromXml(XDocument questionsDocument)
     {
        // This will get all question elements and it'll project them
        // into concrete Question instances upcasted to Question base
        // class
        List<Question> questions = questionsDocument
                     .Descendants("question")
                     .Select
                     (
                        element =>
                        {
                           Type questionType = QuestionTypeMap[q.Attribute("type").Value];
                           Question question = (Question)Activator.CreateInstance(questionType);

                           // Calls the appropiate delegate to perform specific
                           // actions against the instantiated question
                           TypeToConvertMap[questionType](question, element);

                           return question;
                        }
                     ).ToList();

        return questions;
     }

     private void ConvertTrueFalseFromXml(TrueFalseQuestion question, XElement element)
     {
          // Here you can populate specific attributes from the XElement
          // to the whole typed question instance!
     }
}

Now you can convert an XDocument to a question list, and we're ready to turn them into HTML with an HtmlTextWriter :

public class HtmlQuestionConverter
{
    public HtmlQuestionConverter() 
    {
        TypeToConvertMap = new Dictionary<Type, Action<Question, HtmlTextWriter>>
        {
            { typeof(TrueFalseQuestion), new Action<Question, HtmlTextWriter>(ConvertTrueFalseToHtml) }
            // other mappings...
        };
    }

    private Dictionary<Type, Action<Question, HtmlTextWriter>> TypeToConvertMap
    {
        get;
    }

    public void ConvertToHtml(IEnumerable<Question> questions, HtmlTextWriter htmlWriter)
    {
        foreach (Question question in questions)
        {
            // Calls the appropiate method to turn the question
            // into HTML using found delegate!
            TypeToConvertMap[question.GetType()](question, htmlWriter);
        }
    }

    private void ConvertTrueFalseToHtml(Question question, HtmlTextWriter htmlWriter)
    {
        // Code to write the question to the HtmlTextWriter...
    }
}

Going this way I don't see that you need polymorphism at all:

XmlQuestionConverter xmlQuestionConverter = new XmlQuestionConverter();
IList<Question> questions = xmlQuestionConverter.ConvertFromXml(xdoc);

HtmlQuestionConverter htmlQuestionConverter = new HtmlQuestionConverter();
htmlQuestionConverter.ConvertToHtml(questions, htmlWriter);

Note: I couldn't try to execute this code and I'm not 100% sure if it'll work, but it's a good start to understand how to implement your code with a clear separation of concerns! You might need to do some tweaks to adapt my code to your actual use case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM