Design Patterns – Representing Complex Object Dependencies in Python

dependency-injectiondesigndesign-patternsobject-oriented-designpython

I have several classes with a reasonably complex (but acyclic) dependency graph. All the dependencies are of the form: class X instance contains an attribute of class Y. All such attributes are set during initialization and never changed again.

Each class' constructor has just a couple parameters, and each object knows the proper parameters to pass to the constructors of the objects it contains. class Outer is at the top of the dependency hierarchy, i.e., no class depends on it.

Currently, the UI layer only creates an Outer instance; the parameters for Outer constructor are derived from the user input. Of course, Outer in the process of initialization, creates the objects it needs, which in turn create the objects they need, and so on.

The new development is that the a user who knows the dependency graph may want to reach deep into it, and set the values of some of the arguments passed to constructors of the inner classes (essentially overriding the values used currently). How should I change the design to support this?

I could keep the current approach where all the inner classes are created by the classes that need them. In this case, the information about "user overrides" would need to be passed to Outer class' constructor in some complex user_overrides structure. Perhaps user_overrides could be the full logical representation of the dependency graph, with the overrides attached to the appropriate edges. Outer class would pass user_overrides to every object it creates, and they would do the same. Each object, before initializing lower level objects, will find its location in that graph and check if the user requested an override to any of the constructor arguments.

Alternatively, I could rewrite all the objects' constructors to take as parameters the full objects they require. Thus, the creation of all the inner objects would be moved outside the whole hierarchy, into a new controller layer that lies between Outer and UI layer. The controller layer would essentially traverse the dependency graph from the bottom, creating all the objects as it goes. The controller layer would have to ask the higher-level objects for parameter values for the lower-level objects whenever the relevant parameter isn't provided by the user.

Neither approach looks terribly simple. Is there any other approach? Has this problem come up enough in the past to have a pattern that I can read about? I'm using Python, but I don't think it matters much at the design level.

Best Answer

You should look at all of the common object creation patterns and make an assessment based upon the state of your logic, time you have to implement the change, and so forth. Also, rather than have the participating objects know how to construct each other, you should consider Inversion of Control.

Its hard to be specific without more info, but given that you are talking about a complex graph I would think that Abstract Factory or Builder would be more useful here than just a simple Factory depending on how different each of your scenarios is.

If you have some initial decisions to make that influence creation of the graph but the differences are nuances, look at Builder...particularly if there are some structural differences between the "products" (such as, some participating objects are optional or in some scenarios participating objects drag in another set of collaborators).

If the differences between the scenarios are more distinct / elaborate and take the form of "families" of objects that should be used together, look at Abstract Factory with a few different factory implementations.

If you actually just have a few specific flavors of this graph and want to pick and choose between them dynamically, you might be able to just go with Prototype; treat your current implementation as a prototype, code up your alternate implementation(s) as another prototype, and just clone the appropriate prototype as needed.

Related Topic