0

I am working on a simulator which simulates patients, so specific processes can be validated on "virtual" patients, before testing on real patients. The simulator is essentially a mathematical model which consists of multiple ordinary differential equations(ODE), which can be calculated using numerical method. These mathematical models are calculated step by step, meaning you need the previously calculated values to calculate new values. These steps can be small or bigger, the smaller the step the better the result but the longer it takes to calculate.

At first development went fine, but gradually the simulator became a mess, we introduced a second mathematical model and a seconds ODE solver. Which had to work with the same "virtual" patients. But the second mathematical model required the patient to have some extra fields and the the calculated values vector array became a bit bigger(from 7 entries per step to 10 entries). This worked, but now we are thinking of adding another mathematical model, which requires even more properties in a patient and the caclculated values would not be able to be stored in the same vector array, because the first 3 entries are for different values from the other models. We are now contemplating on leaving the first 3 entries 0/null and adding 3 entries to the end of the vector array for the newest model.

Here is our dumbed down class diagram to show the problem: class diagram

The class diagram shows only 2 concrete model and 2 concrete solvers, but we need to add at least 1 more model. As you can see the patient has 7 variables. The first model requires the first 5, the seconds required all of them and the newest model requires only the last 4(and 2 extra not added yet). Beneath that is a schedule with "life events" of the patient which have an influence on the calculated values of the patient. Below that is the vector array which has 10 entries. the first 7 are only used by math model X, all 10 are used by model Y and the newest model would require an extra 3 to be added to the end, and the first 3 wouldn't be used by that model.

What is a design pattern that would work here?

Inheritance
I tried using inheritance. The second model uses the first model as a base class, this works. But when a third model is added which does not need some values/functions is that still a correct way to go? Also the problem of the patient still existed, because it would also need to be either inherited or extra attributes would just be added.

decorator pattern
Also tried this, but had the same problems as with inheritance.

Does anyone see a solution for this design problem?

Then there is a second issue
This issue is regarding the creation of the above mentioned simulator. As you can see the creation of patients is done through a CreatePatient() function in the simulator, which requires a list of patient values(the varibale 1 through 7 in patient) that is growing since adding more model. And a schedule with "life events" that influence the patient calculated values.

The creation of the simulator, schedule, model and solver is now done in a CLI and a GUI project which reference the Simlator project. There is 2 issues I see, the first is regarding the growing list of attributes/properties and calculated values vector array when adding a model. This makes is harder to create a patient. To solve this, the first addressed problem above should first be resolved is my believe. Secondly the creation is a bit weird now. The CLI request a file path from the user which points to a .ini file with configuration for the simulator. This .ini file consist of configuration like the duration of the simulator, how big the stepsize should be(remember the above mentioned models and how they are solved), if patients should be exported, seeds. Also it gives for each possible patient value(attribute/property of patient) the range this value can be, and you can also add a list of file path to .ini files which contain hardcoded patient values. Thus you could say I want 50 patients, then you give point to 25 .ini files in the .ini configuration file for the simulator and the rest of the patients will be generated using the the specified range per value. The same is done for the schedule that needs to be generated for each patient.

The problem I see with the above is that a patient is created through the simulator object after creation, but the schedule of the patient is not. The reason the schedule is not is because it is now generated in the CLI/GUI based on some settings in the .ini file. Should these creation be done some other way? should the creation of a patient even be done through a simulator object? should there be a factory method for each object(schedule, patient)?

Summary | High lever explanation
I am currently trying to refactor a project to be more extendable. The base of the project consist of a "patient" model with a set of attributes and a vector array. The attributes will be populated on creation, but are too tightly coupled to the classes that require these attributes. For example: class1 needs only attribute 1-5 and class 2 only needs 4-10.

I am looking for a solution that makes it easier to add class3, class4 and class5 but that does not bloat the patient class with even more attributes.

A solution I have tried is to have different patient classes per class1, class2, etc. This works to a certain extend, because the patient class needs to be used in other places which the would require if/switch statement to know which attributes they can get from the patient eg. 1 through 5 or 4 through 10.

Possible solution 1
To work with the ever changing amount of patient attributes I am now going to build a parameter structure. When creating the simulator object I will check which mathematical model is used and based on that create a struct which hold patient values that that specific model requires. The struct will inherit from IPatientParameter which can then be used to store it in the patient object.

13
  • 1
    I would like to help you, but your question suffers both from too much details to properly describe and too little details to provide viable solution. I really feel you should start thinking about abstractions so that adding new model or solver won't need changes to those abstractions.
    – Euphoric
    Commented Dec 28, 2019 at 14:09
  • Alright let me add another section with an abstract summary Commented Dec 28, 2019 at 14:12
  • @Euphoric for the first problem I added a summary with a higher level explanation Commented Dec 28, 2019 at 14:18
  • This is pretty confusing, but I can say something is fundamentally wrong if you have variables called variable1, variable2 etc. Does the meaning of these variable change depending on the "model"? Because then they should be properties of the "model" not of the "patient".
    – JacquesB
    Commented Dec 28, 2019 at 14:56
  • @JacquesB the variables are named differently, this was just to show some variables are needed and some are not depending on the used model. The variables however are different per patient, so it wouldn't work if the model was holding these variables Commented Dec 28, 2019 at 15:08

3 Answers 3

4

I have run into this problem many times. When the fields used are expected to change like this the best plan is to stop pretending you have a class. What you have is a key value collection.

Single inheritance only works when change grows in one dimension. That is, our base class had these 3 fields. The child needs those 3 plus these 2. That all falls apart when a new child needs a grab bag of those and some new ones as well. Ugg.

Multiple inheritance works to resolve name collisions. The 3rd field from one parent has the same name as the 5th field from the second parent.

But this is just an unpredictable random assortment of fields that some things care about and other things don't that will grow in unpredictable ways and make a mess of anything you call your patient.

In these cases I just reach for a key value collection. This data structure will help ensure that the names of fields are unique across uses. It can be used as a DTO to populate patient classes that each are focused on the use this patient data is used for. I go so far as to make multiple patient classes that hold the same fields just because they are put to different uses.

Each patient is a boiling mass of changing data fields but each use that data is put to is stable. Build your classes around that stability. You can see this in the real world as well. Everyone has their own patient forms. Yes they contain duplicates. Yes they're all different.

This idea used to be called aspects but aspect oriented programming came along and swallowed the term.

1
2

You are having problems with your Patient class because your software doesn't have a singular concept that can be called "Patient". Thus, you can't create a single cohesive class for that concept. Each mathematical model has a different view of how the concept of a Patient should be modeled and thus they require different properties.

In a situation like that, I would create a Patient interface or abstract base class with the common properties that are shared by every model (including the ones planned for implementation at some future date), and derive model-specific derived classes that sore the properties used by the model they are tied to.

If the idea behind a single Patient class was to be able to swap models halfway through a run, then you have to be really careful to ensure that the combination of property values represents at least a valid state for the new model and preferably the correct state. That might be a non-trivial task anyway.

1

Patient variants have different data according to the simulation, right? So why not use composition with an IPatientModel that will be associated with the IModel used by the simulator?

enter image description here

If your IPatientModel instances are like documents, you could use the composite pattern (but maybe it's speculative overdesign).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.