I am writing a back-end for a program that manages surveys in TypeScript but don't know how to design my Survey and SurveyRepository class without violating the Open/Closed Principle (OCP)
My survey class looks like this
class Survey {
private readonly metadata: SurveyMetadata
private readonly options: SurveyOptions
private title: string
private description: string
private readonly questions: Question[]
//getter setter
}
class Question {
private readonly metadata: QuestionMetadata
private required: boolean
private question: string
}
Now I have a class for every question type
class TextQuestion extends Question {
//just a super call
}
class ChoiceQuestion extends Question {
private answerOptions: AnswerOptions
private isMultiple: boolean
}
Now to store my data in the database I wrote a repository class
class SurveyRepository extends IRepositroy {
public insert(survey: Survey): number
}
Now if I want to insert a survey into a database I also need to store the questions for that I need to know the type of the question and the only way I know to do this is like this:
for(const question of questions){
switch(instanceof question){
case TextQuestion:
//code to store text question
break;
case ChoiceQuestion:
//code to store choice question
break;
}
}
But this violates the OCP.
Solutions I thought of:
Make a repository for every question type would be an option but that way the survey repository wouldn't make any sense
I read about the visitor pattern would that work in this case?
EDIT:
After more research i found this question on stackoverflow but not answerd Stack Overflow
The first solution I came up with is adding a type field to the base question and injecting a map that maps question type to the right question repository, but this approach has 1 flaw: how do I select, and if I use this solution, I will have the same problem later on the client or controller.
The second solution would be to make only one question class that holds all The problem with this is that the questions have different properties, so I would have a lot of nulls, and if I wanted to add a new type, I would need to change the question class every time, so same problem.
The third solution I thought of would be to not inherit from question but use an instance of question in every subtype, but how do I store the question objects in the survey class?
I think a solution to your problem is to use a more object-oriented way: keep the logic of saving surveys and questions inside the objects and do not use the repository.
interface Question {
save(): void;
}
class TextQuestion implements Question {
// constructors and fields
save() {
// logic to save TextQuestion to DB
}
}
class ChoiceQuestion implements Question {
// constructors and fields
save() {
// logic to save ChoiceQuestion to DB
}
}
class Survey {
// constructors and fields
private readonly questions: Question[];
save(): void {
// logic to save data of the Survey to DB
for (let question of this.questions) {
question.save();
}
}
}
// Usage
let cq1 = new ChoiceQuestion(/* some parameters */);
let cq2 = new ChoiceQuestion(/* some parameters */);
let tq1 = new TextQuestion(/* some parameters */);
let survey = new Survey(/* some parameters and questions: cq1, cq2, tq1 */);
survey.save();
P.S. Now you can add new classes of questions without touching ChoiceQuestion, TextQuestion and Survey, thus without violating the Open–closed principle.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With