Załóżmy, że mam narzędzie do budowania testów, aby nauczyciele mogli zadać mnóstwo pytań do testu.
Jednak nie wszystkie pytania są takie same: masz wiele możliwości wyboru, pole tekstowe, dopasowanie itd. Każdy z tych typów pytań musi przechowywać różne typy danych i musi mieć inny GUI zarówno dla twórcy, jak i dla osoby wykonującej test.
Chciałbym uniknąć dwóch rzeczy:
- Sprawdzanie typu lub rzutowanie typu
- Wszystko związane z GUI w moim kodzie danych.
W mojej pierwszej próbie kończę z następującymi klasami:
class Test{
List<Question> questions;
}
interface Question { }
class MultipleChoice implements Question {}
class TextBox implements Question {}
Jednak kiedy idę, aby wyświetlić test, nieuchronnie skończy się na kodzie:
for (Question question: questions){
if (question instanceof MultipleChoice){
display.add(new MultipleChoiceViewer());
}
//etc
}
To wydaje się być bardzo częstym problemem. Czy jest jakiś wzór, który pozwala mi zadawać pytania polimorficzne, unikając elementów wymienionych powyżej? A może polimorfizm to zły pomysł?
design-patterns
polymorphism
Nathan Merrill
źródło
źródło
Odpowiedzi:
Możesz użyć wzorca odwiedzającego:
Inną opcją jest dyskryminowany związek zawodowy. Zależy to bardzo od twojego języka. Jest to o wiele lepsze, jeśli Twój język to obsługuje, ale wiele popularnych języków tego nie robi.
źródło
visit
(odwiedzający odwiedzający). Zwykle wywoływana jest również metoda w odwiedzanych obiektachaccept(Visitor)
(obiekt akceptuje gościa). Zobacz oodesign.com/visitor-pattern.htmlW C # / WPF (i, jak sądzę, w innych językach projektowania skoncentrowanych na interfejsie użytkownika) mamy DataTemplates . Definiując szablony danych, tworzysz powiązanie między jednym typem „obiektu danych” a specjalistycznym „szablonem interfejsu użytkownika” utworzonym specjalnie w celu wyświetlenia tego obiektu.
Gdy podasz instrukcje dla interfejsu użytkownika dotyczące ładowania określonego rodzaju obiektu, zobaczysz, czy dla tego obiektu zdefiniowano jakieś szablony danych.
źródło
Jeśli każdą odpowiedź można zakodować jako ciąg, możesz to zrobić:
Gdzie pusty ciąg oznacza pytanie, na które nie ma jeszcze odpowiedzi. Umożliwia to rozdzielenie pytań, odpowiedzi i GUI, a jednocześnie pozwala na polimorfizm.
Pole tekstowe, dopasowanie itp. Mogą mieć podobne projekty, wszystkie implementują interfejs pytań. Konstrukcja ciągu odpowiedzi ma miejsce w widoku. Łańcuchy odpowiedzi reprezentują stan testu. Powinny być przechowywane w miarę postępów ucznia. Zastosowanie ich do pytań pozwala wyświetlić test i jego stan zarówno w sposób stopniowany, jak i niesklasyfikowany.
Poprzez oddzielenie wyjście do
display()
idisplayGraded()
widok nie muszą być zamienione na zewnątrz, bez rozgałęzień do zrobienia na parametrach. Jednak każdy widok może ponownie wykorzystać tyle logiki wyświetlania, ile może podczas wyświetlania. Niezależnie od tego, jaki plan zostanie stworzony, nie trzeba wyciekać do tego kodu.Jeśli jednak chcesz mieć bardziej dynamiczną kontrolę nad sposobem wyświetlania pytania, możesz to zrobić:
i to
Ma to tę wadę, że wymaga widoków, które nie zamierzają wyświetlać
score()
lubanswerKey
polegać na nich, gdy ich nie potrzebują. Oznacza to jednak, że nie musisz odbudowywać pytań testowych dla każdego typu widoku, którego chcesz użyć.źródło
Moim zdaniem, jeśli potrzebujesz takiej ogólnej funkcji, zmniejszyłbym sprzężenie między elementami w kodzie. Spróbuję zdefiniować typ pytania bardziej ogólny, jak to możliwe, a następnie utworzę różne klasy dla obiektów renderujących. Zobacz poniższe przykłady:
Następnie w części dotyczącej renderowania usunąłem sprawdzanie typu, wykonując proste sprawdzenie danych w obiekcie pytania. Poniższy kod próbuje osiągnąć dwie rzeczy: (i) uniknąć sprawdzania typu i uniknąć naruszenia zasady „L” (podstawienie Liskova w SOLID) poprzez usunięcie podtypu klasy pytania; oraz (ii) uczynić kod rozszerzalnym, nigdy nie zmieniając podstawowego kodu renderującego poniżej, po prostu dodając do tablicy więcej implementacji QuestionView i jego instancji (jest to w rzeczywistości zasada „O” w SOLID - otwarta na rozszerzenie i zamknięta na modyfikację).
źródło
Fabryka powinna być w stanie to zrobić. Mapa zastępuje instrukcję switch, która jest potrzebna wyłącznie do sparowania pytania (które nie wie nic o widoku) z pytaniem.
Dzięki temu widok używa określonego rodzaju pytania, które jest w stanie wyświetlić, a model pozostaje odłączony od widoku.
Fabrykę można wypełnić poprzez odbicie lub ręcznie przy uruchomieniu aplikacji.
źródło
Question
się w,MultipleChoiceQuestion
kiedy tworzyszMultipleChoiceView
Nie jestem pewien, czy liczy się to jako „unikanie sprawdzania typu”, w zależności od tego, co myślisz o odbiciu .
źródło
if
kontroli typu dodictionary
kontroli typu. Podobnie jak Python używa słowników zamiast instrukcji switch. To powiedziawszy, podoba mi się w ten sposób bardziej niż lista instrukcji if.template <typename Q> struct question_traits;
z odpowiednimi specjalizacjami