Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to not violate Open/Closed Principle when checking for type is needed

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?

like image 439
Kilian Reichart Avatar asked Nov 17 '25 17:11

Kilian Reichart


1 Answers

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.

like image 111
nik0x1 Avatar answered Nov 20 '25 06:11

nik0x1



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!