Search code examples
pythonoopdesign-patternsbuilder

Passing parameters to a builder with component sub-builders


What is an appropriate design for passing keyword arguments to a builder that contains component sub-builders which each use a distinct portion of the parameter set? Is it better to pass in a single keyword parameter set which is then parsed by each sub-builder? Or is it better to explicity specify which parameter sets belong to which sub-builders when calling the parent builder?

I know "better" can be subjective in these kinds of situations but I am wondering if there is some collective wisdom I can draw from, here. Also, note that in the details below I'm using a feature specific to the Python language but any thoughts on how other languages manage this situation is appreciated.

Details - I am using a builder design pattern where BuilderA contains sub-builders BuilderB() and BuilderC. My current thought on the design was to pass the same set of keyword arguments to each sub-builder:

class BuilderA():
    def __init__(self):
        self.builder_b = BuilderB()
        self.builder_c = BuilderC()

    def build(self, **build_parameters):
        artifact_b = self.builder_b.build(**build_parameters)
        artifact_c = self.builder_c.build(**build_parameters)
        # ... do something to create BuilderA's artifact ...

Each sub-builder has distinct keyword parameters:

class BuilderB():
    def build(self, param_1=None, param_2=None, **ignored_kwds):
        # ...

class BuilderC():
    def build(self, param_3=None, param_4=None, **ignored_kwds):
        # ...

Alternatively, BuilderA's API could look like this:

class BuilderA():

    def build(self, builder_b_parameters={}, builder_c_parameters={}):
        artifact_b = self.builder_b.build(**builder_b_parameters)
        artifact_c = self.builder_c.build(**builder_c_parameters)
        # ... do something to create BuilderA's artifact ...

Is one of these designs, generally, more acceptable than the other? Or is there another option I haven't considered?

Thoughts - The first design is cleaner but prevents BuilderB and BuilderC from sharing keyword names. Plus, it requires that each of their builders includes **ignored_kwds in its build method declaration. The danger of this approach is that if the user misspells a keyword argument then there can be unexpected behavior without error. On the other hand, the second design makes the interface to BuilderA more cumbersome but resolves the aforementioned issues.


Solution

  • Surely, narrow things. Don't let your artifact-specific builder to "know" about the whole world; it would definitely stab your in return.

    Maps of maps is much better solution, as well as much more flexible. Eventually, you may find yourself in a weird situations, where differnet subbuilder require different implementations of the same abstract class or whatever.

    Keep things much simplier by explicit labeling.