2

I have a C++ code that performs simulation of a physical system which deals with motion of objects. It has the following classes:

  • Class Main, containing all the main calculation methods and the data pertaining to the state of the physical system and the simulation (e.g., positions and velocities of objects). It is instantiated only once.

  • Class A, which contains specific functionality that may be needed in particular situations (for instance, A can represent the effect of a particular phenomenon that might affect motion, e.g., wind). It is an optional functionality that may be enabled or disabled by the user. A is also instantiated only once.

Class A requires access only to a small subset of the data members of Main (e.g., it might not care about the positions but it does need to know the velocities). It also needs to be able to modify them.

My current solution is to define a unique pointer to A as a data member of Main and create A in the constructor of Main:

m_A = std::make_unique<A>(arg1, arg2, ...);

where arg1, arg2, etc., are data members of Main.

However, this approach has the following drawbacks:

  • Sometimes, when I want to extend the functionality of A, I may need to gain access to additional data members of Main. This requires the redefinition of the constructor signature of A. It also makes the list of arguments passed to A pretty long (depending on how complex the functionality of A is). The latter problem can perhaps be solved by gathering the different arguments in a single struct, but I am not sure if this is a good solution.

  • In the future, I might want to create another class B that would implement some other functionality, much like A. However, unlike A, it might need access not only to the data members of Main but also to some of Main methods. In this case, I see no other option than passing a reference to Main to the constructor of B (e.g., using *this). However, I would almost certainly never use all the methods of Main in B (but only a small subset of them).

Question: Given these requirements, what is the most optimal way to organize the classes and the communication between them? Is there some design pattern that suits this problem better? I prefer a solution that does not impact performance too much but also allows flexibility (for future extensions).

2 Answers 2

4

Let me restate your main issue first: you have a loosely structured Main class which has become too large over time, and now you are refactoring parts of it to classes A and B, to give the program more structure. Still the code in A and B requires access to certain members in Main which will stay there, and what you observe is that the access from A or B now becomes more effort, at least more effort than it was before when all your code was in a single "god" class.

Actually, that is quite normal. When you refactor some functionality into a new class (or module, or component), you have to make some things explicit which were formerly implicit. For example, it will become explicit which input data the functionality requires, which output data it's function produces, and which access to other functionality in Main the new class requires. And more explicitness comes for the price of requiring more boilerplate code, that's common.

Technically, you are on the right track. You wrote

Sometimes, when I want to extend the functionality of A, I may need to gain access to additional data members of Main. This requires the redefinition of the constructor signature of A.

This is exactly what happens when the input to some functionality is made explicit. However, it is not necessarily the constructor which has to be extended, sometimes, it is sufficient (or even more correct) to extend the signature of certain functions in A which are called when using its functionality.

It also makes the list of arguments passed to A pretty long (depending on how complex the functionality of A is).

Well, that can happen. It could be a sign that

  1. the functionality in A is indeed quite complex and requires all the data.

  2. one put too many things into A, or too few, one did not make the ideal cutout from Main when creating A.

  3. certain data members belong into A exclusively, and should not even exist in Main.

If you are just bothering about the length of A's constructor argument list, you may also consider to rethink if all the data is mandatory and has to be known at construction time of A, or whether there is data which can or should be passed later by some other functions.

It should be clear, however, that noone can tell what the right answer here is by just looking at meaningless names like A and Main, or unknown members and functionalities.

The latter problem can perhaps be solved by gathering the different arguments in a single struct, but I am not sure if this is a good solution.

It is a sensible technique when the grouped arguments form a coherent type, a type for which you can come up with a clear, self-descripting name. Otherwise, it is not. Note there is normally no need to group all arguments together, sometimes you can identify subsets of all arguments which can used to create a new, sensible type.

... I might want to create another class B [...]. Unlike A, it might need access not only to the data members of Main but also to some of Main methods. In this case, I see no other option than passing a reference to Main to the constructor of B (e.g., using *this). However, I would almost certainly never use all the methods of Main in B (but only a small subset of them).

There are several other options here (but again, there is no way to tell which ones are the "best" for your specific case):

  1. The classic OOP solution: create an abstract class X (an "abstract interface") with the subset of methods required by B as pure virtuals, make Main implement X and then make B's constructor consume an object reference of type X instead of typ Main. Main will still have to pass this to B's constructor, but now you made the restricted access explicit, which makes it much easier to reason about the behaviour of B, or to write tests for B.

  2. Functional solution: in case B requires only one or two methods from Main, you can spare the interface X, and simply pass member functions of Main as parameters to B, just as data. You may have to learn a few bits about topics like std::function, lambda expressions and closures in C++.

  3. The solution you definitely should check first, before everything else: is B really a good cut-out from Main? Can you avoid the whole issue by moving a function from Main to B? Can you avoid the whole issue by redefinining the functionality of B?

The hard part, however, is to make the decisions which technique should be applied when, what to put into classes A and B (and what not), and how to design their interfaces. Unfortunately, there is normally no most optimal way and definitely no simple, braindead design pattern which can tell you what to do. The best recommendation I can give you is to discuss your real code with others, more experienced programmers, try out different things and practice. Codereview.Stackexchange might be worth a try, if you cannot find someone in your organisation for reviewing your code.

1
  • Thank you for your recommendations. I avoided getting into the code details because the simulation involves complicated physical principles. However, I believe your general points are sufficient to determine the optimal design.
    – grjj3
    Commented Jul 16 at 11:25
2

I would start by changing your perspective on the Main class. Treat this more as a composition root for your application. The Main class should be responsible for configuration and initializing objects which specialize in the various aspects of your simulation. A related concept is inversion of control.

Beyond that it is difficult to recommend anything because there is no standard architecture for physical simulations.

Some things to consider:

  • Utilize dependency injection to pass information, configuration, and helper objects around to whoever needs them. Inversion of control and having a composition root pairs very well with dependency injection.
  • Identify reusable behavior that can be pulled into its own class with minimal dependencies. This allows for code sharing between A and your hypothetical B. This is known as object composition.

Physics simulations have vastly different problems to solve than other types of programs. Many times it's not very useful to model real world objects as classes. So much of physics is steeped in math that memoizing calculations, organizing algorithms, and parallelization of calculations becomes the predominant driver for application architecture. I'm not saying you shouldn't the use object-oriented programming. Be careful not to model your simulation too literally. Remember that you are automating an enormous number of math calculations. It is this process you are automating, not modeling real physical things.

I'm afraid this ended up being a very general answer, but without a deep-dive into the requirements (and possibly physics) I cannot give anything more specific. It sounds like a fun project, though. Good luck!

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.