Digamos que eu tenha um construtor de testes, para que os professores possam criar um monte de perguntas para um teste.
No entanto, nem todas as perguntas são iguais: você tem várias opções, caixa de texto, correspondência e assim por diante. Cada um desses tipos de perguntas precisa armazenar diferentes tipos de dados e uma GUI diferente para o criador e o responsável pelo teste.
Eu gostaria de evitar duas coisas:
- Verificações de tipo ou conversão de tipo
- Qualquer coisa relacionada à GUI no meu código de dados.
Na minha tentativa inicial, termino com as seguintes classes:
class Test{
List<Question> questions;
}
interface Question { }
class MultipleChoice implements Question {}
class TextBox implements Question {}
No entanto, quando vou exibir o teste, inevitavelmente acabo com código como:
for (Question question: questions){
if (question instanceof MultipleChoice){
display.add(new MultipleChoiceViewer());
}
//etc
}
Isso parece um problema muito comum. Existe algum padrão de design que me permita ter perguntas polimórficas, evitando os itens listados acima? Ou o polimorfismo é a idéia errada em primeiro lugar?
fonte
Respostas:
Você pode usar um padrão de visitante:
Outra opção é uma união discriminada. Isso vai depender muito do seu idioma. Isso é muito melhor se o seu idioma suportar, mas muitos idiomas populares não.
fonte
visit
(o visitante visita). Também o método nos objetos que estão sendo visitados geralmente é chamadoaccept(Visitor)
(o objeto aceita um visitante). Veja oodesign.com/visitor-pattern.htmlEm C # / WPF (e, imagino, em outras linguagens de design focadas na interface do usuário), temos DataTemplates . Ao definir modelos de dados, você cria uma associação entre um tipo de "objeto de dados" e um "modelo de interface do usuário" especializado, criado especificamente para exibir esse objeto.
Depois de fornecer instruções para a interface do usuário carregar um tipo específico de objeto, ele verificará se existem modelos de dados definidos para o objeto.
fonte
Se todas as respostas puderem ser codificadas como uma sequência, você poderá fazer o seguinte:
Onde a cadeia vazia significa uma pergunta sem resposta ainda. Isso permite que as perguntas, as respostas e a GUI sejam separadas, mas permite polimorfismo.
Caixa de texto, correspondência e assim por diante podem ter designs semelhantes, todos implementando a interface da pergunta. A construção da sequência de respostas acontece na exibição. A sequência de respostas representa o estado do teste. Eles devem ser armazenados à medida que o aluno progride. Aplicá-los às perguntas permite exibir o teste e seu estado, tanto de forma graduada quanto não.
Ao separar a saída em
display()
edisplayGraded()
a visualização não precisa ser trocada e nenhuma ramificação precisa ser feita nos parâmetros. No entanto, cada exibição é livre para reutilizar o máximo de lógica de exibição possível durante a exibição. Qualquer que seja o esquema planejado para fazer isso, não precisa vazar para esse código.Se, no entanto, você deseja ter um controle mais dinâmico de como uma pergunta é exibida, faça o seguinte:
e isto
Isso tem a desvantagem de exigir visualizações que não pretendem exibir
score()
ouanswerKey
depender delas quando não precisam delas. Mas isso significa que você não precisa recriar as perguntas do teste para cada tipo de visão que deseja usar.fonte
Na minha opinião, se você precisar de um recurso genérico, diminuiria o acoplamento entre as coisas no código. Eu tentaria definir o tipo de pergunta o mais genérico possível e, depois disso, criaria classes diferentes para os objetos do renderizador. Por favor, veja os exemplos abaixo:
Em seguida, para a parte de renderização, removi a verificação de tipo implementando uma verificação simples dos dados no objeto de pergunta. O código abaixo tenta realizar duas coisas: (i) evitar a verificação de tipo e violação do princípio "L" (substituição de Liskov no SOLID) removendo a subtipo de classe Pergunta; e (ii) tornar o código extensível, nunca alterando o código de renderização principal abaixo, apenas adicionando mais implementações do QuestionView e suas instâncias à matriz (esse é realmente o princípio "O" no SOLID - aberto para extensão e fechado para modificação).
fonte
Uma fábrica deve ser capaz de fazer isso. O mapa substitui a instrução switch, necessária apenas para emparelhar a Pergunta (que nada sabe sobre a exibição) com o QuestionView.
Com isso, a visualização usa o tipo específico de pergunta que pode exibir, e o modelo permanece desconectado da visualização.
A fábrica pode ser preenchida por reflexão ou manualmente no início do aplicativo.
fonte
Question
em umMultipleChoiceQuestion
quando você cria aMultipleChoiceView
Não sei se isso conta como "evitar verificações de tipo", dependendo de como você se sente em relação à reflexão .
fonte
if
verificação de tipo para umadictionary
verificação de tipo. Assim como o Python usa dicionários em vez de instruções switch. Dito isto, gosto desta maneira mais do que uma lista de declarações if.template <typename Q> struct question_traits;
com especializações apropriadas