0

I'm doing scientific computations using Python.

So far I have a module core and a class Simulation inside. There are many instances of Simulation at the runtime but all of them must share the same "problem setup". So far this problem setup is represented by global variables and functions in the module core with sophisticated initialization logic.

For various reasons I think the program architecture would benefit from refactoring in the following way:

  • there is a class Problem and the only instance of it
  • all the initialization logic is encapsulated in Problem.__init__
  • all the instances of Simulation are connected to the same problem instance

What I have the following implementation using class variables in mind:

class Problem:
    def __init__(self, ...)
        # sophisticated logic moves here

    ...

class Simulation:
    ... various methods relying on self.problem ...

Simulation.problem = Problem(...)
s1 = Simulation(...)
s2 = Simulation(...)
...

What are the best practices to do something like this?

1

2 Answers 2

1

A few comments and recommendations:

'Sophisticated logic' in a constructor give me a little pause. Experience tells me to avoid lots of complexity in initializers. I would prefer a factory method on the module, for example, and make the creation of the Problem the last step.

On a related note, I don't see a good reason to set a Problem globally on the Simulation class. I would instead make the Problem an explicit, required parameter of the Simulation initializer. The approach here will make it challenging if you ever need to create simulations for more than one Problem at the same time.

3
  • Thank you for the answer! I will check what the factory method is. I'm a mathematician, not a software engineer, but I want to work on my skills to write better programs. Concerning the class variable problem. So far it doesn't seem that I will ever use more than one problem at the runtime. However, my code creates many Simulation objects on the go and they must all be related to one problem. I was thinking of a class variable to avoid possible mistakes. As a positive side effect, I don't have to pass it to every Simulation object, which makes the code more clear. Oct 8, 2020 at 15:40
  • It's possible that you will never need more than one Problem and perhaps this is going to be fine. Probably the first place you might struggle if you need to write some unit tests. I would argue that it makes the code less clear since it's a little unexpected. One question I have is how many places in your code are you creating simulation objects? If it's one place, the solutions are '6 of one half-dozen of another'. If you are creating them in a lot of places, you might want to consider another level of abstraction. Ultimately these are general recommendations and not cut-and-dried.
    – JimmyJames
    Oct 8, 2020 at 17:42
  • if you need to write some unit tests. This was one of the issues motivating me to refactor. Thank you! I will follow your advice and make problem the instance's parameter. Oct 8, 2020 at 20:13
0

Many-to-one relations are exactly the kind of relations that exist between instances-to-class, or derivedClasses-to-parentClass.

They modelize a hierarchy (children-to-parent)and a dependency with a referential constraint:

  • The parent should first be created: you may decide what will happen to the parent when no children are created or all of them have been deleted (or linked to another parent). It's up to your app to define the lifecycle of the parent (archiving, hiding, making it read-only so that another parent will be created with a different creating time or for a new context/scope id).
  • Each child can then be created by specify only its parent in the constructor. You not be do both things at the same time in a complex constructor for the children (even if you may define several constructors for each child).

So I agree that your constructors should be easy, but nothing prohibits the constructor to manage transient states for the parent, such as defining a rules that the parent will persist at least for as long as the current transaction where you're supposed to create and link children to this parent: for the commit time, you can check if there are children added in the transaction and if not decide to rollabck and not commit the parent creation. This is usually done for (1..N)-to-1 relations where N but be >0.

So not only you need to design the data schema, but also the referential constraints (enforced by triggers launched at commit time in a transactional database system, or by invariants implemented and checked in your client application)

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.