Композитные и узоры посетитель для дерева-функциональность обследование в C#


Я написал некоторые функции съемки для проекта. В принципе, общее обследование, которое может состоять из разделов и вопросов.

У меня есть опрос, класс, вопросы и разделы. В опросе в основном на дерево, где каждый узел может быть вопрос или раздел. Узлов у детей -- так что по сути вопрос может иметь удаление подразделов и подвопросов, и раздел может содержать коллекцию подразделов и подвопросов.

Узлы в моем опросе есть производные от абстрактного класса SurveyPart.

namespace Surveys
{
    public abstract class SurveyPart
    {
        public abstract List<SurveyPart> Children { get; set; }
    }

    public class Survey
    {
        public List<SurveyPart> Children { get; set; }

        public Survey()
        {
            Children = new List<SurveyPart>();
        }
    }

    public class Question : SurveyPart
    {
        public override List<SurveyPart> Children { get; set; }
        public string QuestionText { get; set; }

        public Question()
        {
            Children = new List<SurveyPart>();
        }
    }

    public class Section : SurveyPart
    {
        public override List<SurveyPart> Children { get; set; }
        public string Header { get; set; }

        public Section()
        {
            Children = new List<SurveyPart>();
        }
    }
}

Насколько я понимаю, это составная картина? Не уверена, что меня он совершенно прав.

Так что я могу создать опрос (в настоящее время с приходом разделы и вопросы из БД.) Следующая вещь-это сделать ее. Для этого я пытаюсь использовать шаблон Visitor осуществляться с помощью методов расширения.

namespace ExtensionMethods
{
    using Surveys;

    public static class SurveyTextRenderer
    {
        public static int Depth;

        public static void Write(this Survey survey)
        {
            Depth = 0;

            Console.WriteLine("Survey");
            Console.WriteLine(new string('-', "Survey".Length));

            foreach (SurveyPart child in survey.Children)
            {
                Depth++;
                child.Write();
                Depth--;
            }
        }


        public static void Write(this SurveyPart part)
        {
            if (part is Section)
                (part as Section).Write();
            if (part is Question)
                (part as Question).Write();
        }


        public static void Write(this Section section)
        {
            Console.Write(new String('\t', Depth));
            Console.WriteLine("S:" + section.Header);

            foreach (SurveyPart child in section.Children)
            {
                Depth++;
                child.Write();
                Depth--;
            }
        }


        public static void Write(this Question question)
        {
            Console.Write(new String('\t', Depth));
            Console.WriteLine("Q: " + question.QuestionText);

            foreach (SurveyPart child in question.Children)
            {
                Depth++;
                child.Write();
                Depth--;
            }
        }
    }
}

Все это работает нормально, если я создал следующие обследования:

Survey survey = new Survey
            {
                Children = new List<SurveyPart>
                {
                    new Section 
                    { 
                        Header = "Section 1", 
                        Children = new List<SurveyPart>
                        {
                            new Question { QuestionText = "Foo?" },
                            new Question { QuestionText = "Bar?" },
                            new Question { QuestionText = "Barry?" }
                        }
                    },
                    new Section 
                    { 
                        Header = "Section 2", 
                        Children = new List<SurveyPart>
                        {
                            new Question 
                            { 
                                QuestionText = "Did you like it?",
                                Children = new List<SurveyPart>
                                {
                                    new Section 
                                    { 
                                        Header = "If you answered yes, please answer the following",
                                        Children = new List<SurveyPart>
                                        {
                                            new Question { QuestionText = "How come?" },
                                            new Question { QuestionText = "How much did you like it?" }
                                        }
                                    }
                                }
                            },
                            new Question { QuestionText = "Please leave a comment" },
                        }
                    }
                }
            };

и позвонить

survey.Write();

Я получаю:

Survey
------
S:Section 1
    Q: Foo?
    Q: Bar?
    Q: Barry?
S:Section 2
    Q: Did you like it?
        S:If you answered yes, please answer the following
            Q: How come?
            Q: How much did you like it?
    Q: Please leave a comment

Так что, в общем подводя итоги, я пытаюсь использовать составной шаблон для дерево разделов и вопросов. Затем сориентироваться и представить это дерево я пытаюсь использовать посетителя шаблон.

Несколько вопросов возникает ко мне:

  • Я использую статический элемент глубину , чтобы отслеживать, как глубоко посетитель ушел. Это может быть проблематично, имеющих государство на мои методы расширения статический класс?
  • Бы SurveyPart абстрактный класс имеет смысл в качестве интерфейса?
  • Я использую посетителя и композитных правильно, или я borking их?

(Обратите внимание, я оставил много деталей из... например, различные типы вопросов, баллы/ответы на вопросы и т. д... просто сосредоточилась на дереве/композитный/визитер части кода для Сейчас.)



2505
7
задан 8 апреля 2011 в 01:04 Источник Поделиться
Комментарии
3 ответа


Я использую статический глубина члены
отслеживать, как глубоко посетитель
ушли. Это может быть проблематично, имеющих
государство на мои методы расширения статического
класса?

Абсолютно. Причина этого заключается в том, что статические методы должны всегда быть потокобезопасным, они должны быть автономными единицами работы, так сказать - на данный момент ваша переменная не исключительно доступными для текущего вызывающего потока. Это означает, что ваш метод может иметь побочные эффекты на исполнение, на иждивении один раз-снято на вещах из таких. Стресс-тестирование может сделать проблемы становятся очевидными, но без этого это просто вопрос использования и времени, прежде чем все становится жутко.


Бы SurveyPart абстрактный класс
больше смысла в качестве интерфейса?

Это зависит от того, собираетесь ли вы добавить какие-либо базовую функциональность для этого типа? Не подвергайте какие-нибудь полезные элементы многократного использования, которые могут быть и использованы таким же образом, все наследники? Если так, то да, в противном случае, если единственная причина для этого типа является сдерживание других типов в определенной модели (или придерживаются паттернов), тогда нет.


Я использую посетителя и композита в
правильно, или я borking них
вверх?

Честно говоря, я на работе и не имеют времени для анализа вашей модели прямо сейчас. Хотя, шаблон Composite может диктовать использование интерфейсов де-Марк ваши известные типы, уровни абстракции, начиная от этого уровня.

2
ответ дан 8 апреля 2011 в 01:04 Источник Поделиться

Что дизайн будет хорошо работать для различных средств визуализации - когда вы заявили в комментарии, что вы бы это сделали гораздо больше смысла. Вы должны удалить статическое глубина переменная, хотя - это делает ваш код не ориентирован на многопотоковое исполнение. Кроме того, вы можете уменьшить количество перегрузок. Вот способ, чтобы выполнить рефакторинг свой рендерер:

public class SurveyTextRenderer
{
public Write(Survey survey)
{
Console.WriteLine(survey.Name);
Console.WriteLine(new string('-', survey.Name.Length);

for (SurveyPart part in survey.Children)
{
processNode(part, 0);
}
}

protected void ProcessNode(SurveyPart part, int depth)
{
if (part is Section)
WriteSection(part as Section, depth);
else if (part is Question)
WriteQuestion(part as Question, depth);
else
// Error handling or default case

for (SurveyPart part in survey.Children)
{
ProcessNode(part, depth + 1);
}
}
}

Обратите внимание на обработку ошибок - что произойдет, если вы добавите новую SurveyPart и не обновление рендерера? Кроме того, я изменил название, чтобы использовать название опроса, который вы должны увидеть, как легко реализовать.

Я не выполнил WriteSection и WriteQuestion; они будут довольно близко к тому, что вы уже за исключением того, что рекурсия удаляется. Я не думаю, что вам действительно нужен статическийв этом случае, но вы можете сделать их статичными, если вы хотите. Однако, вы могли бы сделать, скажем,

public abstract class Renderer
{
public abstract void Write(Survey survey);
}

и продлить это. Это может или не может быть полезным для вас. Это зависит от вызывающего кода том, стоит ли добавлять, что абстракция. Если у вас есть метод, который вызывается как PrintSurvey(новый TextRenderer(), источник данных) , где источником данных является одним из нескольких мест обследования могут быть сохранены (в XML, базы данных, файл и т. д.) это может быть полезно. Вы не хотите повторяться. На самом деле, вы могли бы опрос расширить SurveyPart (может, переименовать его SurveyElement?) и удалить какой-то-избыточные записи()->ProcessNode() звонки.

Надеюсь, по крайней мере, это дает вам несколько идей!

2
ответ дан 8 апреля 2011 в 05:04 Источник Поделиться

Я думаю, что пытаюсь реализовать шаблон посетителя статические члены-это неверный путь, вы должны использовать экземпляр посетителя и передать его в структуру данных.

Кроме того, мне вообще не нравится идея одного класса принимать решения, основанные на тип другой, просто чувствует себя неправильно для меня.

Во всех описаниях посетитель картины, которые я видел, это было обязанностью объекты в иерархии, чтобы передать посетителю объекта для своих детей, хотя я всегда считал его более удобным для посетителей, чтобы сделать свой собственный животных (разных посетителей разные обходы - префикс, постфикс, инфиксной и т. д.).

public interface IVisitor
{
void Visit(Survey survey);
void Visit(Section section);
void Visit(Question question);
}

public abstract class SurveyPart
{
// ...

public abstract void Apply(IVisitor visitor);
}

public class Survey
{
// ...

public abstract void Apply(IVisitor visitor);
}

public class Question : SurveyPart
{
// ...

public override void Apply(IVisitor visitor)
{
visitor.Visit(this);
}
}

public class Section : SurveyPart
{
// ...

public override void Apply(IVisitor visitor)
{
visitor.Visit(this);
}
}

Затем рендер посетитель может быть реализован следующим образом.

public class RenderVisitor : IVisitor
{
public RenderVisitor(TextWriter writer)
{
this.writer = writer;
}

void Visit(Survey survey)
{
writer.Write(...);
VisitChildren(servey.Children);
}

void Visit(Section section);
{
writer.Write(...);
VisitChildren(servey.Children);
}

void Visit(Question question);
{
writer.Write(...);
VisitChildren(servey.Children);
}

void VisitChildren(List<SurveyPart> children)
{
depth++;

foreach(SurveyPart child in children)
{
child.Apply(this);
}

depth--;
}

int depth;
readonly TextWriter writer;
}

И используется как:

servey.Apply(new RenderVisitor(Console.Out));

2
ответ дан 9 апреля 2011 в 02:04 Источник Поделиться