Допустим, у меня есть конструктор тестов, чтобы учителя могли создать кучу вопросов для теста.
Однако не все вопросы одинаковы: у вас есть несколько вариантов выбора, текстовое поле, соответствие и т. Д. Каждый из этих типов вопросов должен хранить разные типы данных и иметь разные графические интерфейсы как для создателя, так и для тестируемого.
Я хотел бы избежать двух вещей:
- Проверка типа или приведение типа
- Все, что связано с GUI в моем коде данных.
В моей первой попытке я получаю следующие классы:
class Test{
List<Question> questions;
}
interface Question { }
class MultipleChoice implements Question {}
class TextBox implements Question {}
Однако, когда я иду показывать тест, я неизбежно получаю такой код:
for (Question question: questions){
if (question instanceof MultipleChoice){
display.add(new MultipleChoiceViewer());
}
//etc
}
Это похоже на действительно распространенную проблему. Есть ли какой-то шаблон дизайна, который позволяет мне задавать полиморфные вопросы, избегая пунктов, перечисленных выше? Или полиморфизм - неправильная идея?
design-patterns
polymorphism
Натан Меррилл
источник
источник
Ответы:
Вы можете использовать шаблон посетителя:
Другой вариант - дискриминационный союз. Это будет очень сильно зависеть от вашего языка. Это намного лучше, если ваш язык поддерживает его, но многие популярные языки этого не делают.
источник
visit
(посетитель посещает). Также обычно вызывается метод в посещаемых объектахaccept(Visitor)
(объект принимает посетителя). См. Oodesign.com/visitor-pattern.htmlВ C # / WPF (и, я полагаю, в других языках проектирования, ориентированных на пользовательский интерфейс) у нас есть DataTemplates . Определяя шаблоны данных, вы создаете связь между одним типом «объекта данных» и специализированным «шаблоном пользовательского интерфейса», созданным специально для отображения этого объекта.
Как только вы предоставите инструкции для пользовательского интерфейса для загрузки объекта определенного типа, он увидит, есть ли какие-либо шаблоны данных, определенные для объекта.
источник
Если каждый ответ может быть закодирован как строка, вы можете сделать это:
Где пустая строка означает вопрос без ответа. Это позволяет разделять вопросы, ответы и графический интерфейс, но при этом допускает полиморфизм.
Текстовое поле, соответствие и т. Д. Могут иметь похожие конструкции, все они реализуют интерфейс вопроса. Построение строки ответа происходит в представлении. Строка ответа представляет состояние теста. Они должны храниться по мере продвижения ученика. Применение их к вопросам позволяет отображать тест и его состояние как по шкале, так и по шкале.
Разделив вывод на
display()
иdisplayGraded()
представление не нужно менять местами и не нужно выполнять разветвление для параметров. Тем не менее, каждый вид может свободно использовать как можно больше логики при отображении. Какая бы схема не была разработана, она не должна просачиваться в этот код.Однако, если вы хотите более динамично контролировать отображение вопроса, вы можете сделать это:
и это
Это имеет тот недостаток, что требует представления, которые не предназначены для отображения
score()
илиanswerKey
зависят от них, когда они не нужны. Но это означает, что вам не нужно перестраивать тестовые вопросы для каждого типа представления, которое вы хотите использовать.источник
На мой взгляд, если вам нужна такая общая функция, я бы уменьшил связь между вещами в коде. Я бы попытался определить тип Вопроса как можно более общим, и после этого я бы создал разные классы для объектов рендерера. Пожалуйста, смотрите примеры ниже:
Затем для части рендеринга я удалил проверку типа, реализовав простую проверку данных в объекте вопроса. Приведенный ниже код пытается выполнить две вещи: (i) избежать проверки типов и избежать нарушения принципа «L» (подстановка Лискова в SOLID), удалив подтип класса Question; и (ii) сделать код расширяемым, никогда не меняя основной код рендеринга ниже, просто добавляя больше реализаций QuestionView и его экземпляров в массив (это на самом деле принцип «O» в SOLID - открытый для расширения и закрытый для модификации).
источник
Фабрика должна быть в состоянии сделать это. Карта заменяет оператор switch, который необходим исключительно для сопряжения Вопроса (который ничего не знает о представлении) с QuestionView.
При этом представление использует конкретный тип Вопроса, который он может отображать, и модель остается отключенной от представления.
Завод может быть заполнен с помощью отражения или вручную при запуске приложения.
источник
Question
в,MultipleChoiceQuestion
когда вы создадитеMultipleChoiceView
Я не уверен, что это считается «избеганием проверок типов», в зависимости от того, как вы относитесь к рефлексии .
источник
if
проверкиdictionary
типа к проверке типа. Например, как Python использует словари вместо операторов switch. Тем не менее, мне нравится этот способ больше, чем список операторов if.template <typename Q> struct question_traits;
с соответствующими специализациями