Class with too many parameters: better design strategy?

后端 未结 13 894
醉酒成梦
醉酒成梦 2020-12-04 08:29

I am working with models of neurons. One class I am designing is a cell class which is a topological description of a neuron (several compartments connected together). It ha

相关标签:
13条回答
  • 2020-12-04 08:34

    could you supply some example code of what you are working on? It would help to get an idea of what you are doing and get help to you sooner.

    If it's just the arguments you are passing to the class that make it long, you don't have to put it all in __init__. You can set the parameters after you create the class, or pass a dictionary/class full of the parameters as an argument.

    class MyClass(object):
    
        def __init__(self, **kwargs):
            arg1 = None
            arg2 = None
            arg3 = None
    
            for (key, value) in kwargs.iteritems():
                if hasattr(self, key):
                    setattr(self, key, value)
    
    if __name__ == "__main__":
    
        a_class = MyClass()
        a_class.arg1 = "A string"
        a_class.arg2 = 105
        a_class.arg3 = ["List", 100, 50.4]
    
        b_class = MyClass(arg1 = "Astring", arg2 = 105, arg3 = ["List", 100, 50.4])
    
    0 讨论(0)
  • 2020-12-04 08:36

    In my opinion, in your case the easy solution is to pass higher order objects as parameter.

    For example, in your __init__ you have a DendriticTree that uses several arguments from your main class LayerV:

    main_apical_dendrite = DendriticTree(
        bifibs=apical_bifibs,
        first_sec_L=apical_sec1_L,
        L_sigma=L_sigma,
        L_decrease_factor=ldecf,
        first_sec_d=9, 
        branch_prob=apical_branch_prob
    )
    

    Instead of passing these 6 arguments to your LayerV you would pass the DendriticTree object directly (thus saving 5 arguments).

    You probably want to have this values accessible everywhere, therefore you will have to save this DendriticTree:

    class LayerV(__Cell):
        def __init__(self, main_apical_dendrite, ...):
            self.main_apical_dendrite = main_apical_dendrite        
    

    If you want to have a default value too, you can have:

    class LayerV(__Cell):
        def __init__(self, main_apical_dendrite=None, ...):
            self.main_apical_dendrite = main_apical_dendrite or DendriticTree()
    

    This way you delegate what the default DendriticTree should be to the class dedicated to that matter instead of having this logic in the higher order class that LayerV.

    Finally, when you need to access the apical_bifibs you used to pass to LayerV you just access it via self.main_apical_dendrite.bifibs.

    In general, even if the class you are creating is not a clear composition of several classes, your goal is to find a logical way to split your parameters. Not only to make your code cleaner, but mostly to help people understand what these parameter will be used for. In the extreme cases where you can't split them, I think it's totally ok to have a class with that many parameters. If there is no clear way to split arguments, then you'll probably end up with something even less clear than a list of 15 arguments.

    If you feel like creating a class to group parameters together is overkill, then you can simply use collections.namedtuple which can have default values as shown here.

    0 讨论(0)
  • 2020-12-04 08:41

    Can you give a more detailed use case ? Maybe a prototype pattern will work:

    If there are some similarities in groups of objects, a prototype pattern might help. Do you have a lot of cases where one population of neurons is just like another except different in some way ? ( i.e. rather than having a small number of discrete classes, you have a large number of classes that slightly differ from each other. )

    Python is a classed based language, but just as you can simulate class based programming in a prototype based language like Javascript, you can simulate prototypes by giving your class a CLONE method, that creates a new object and populates its ivars from the parent. Write the clone method so that keyword parameters passed to it override the "inherited" parameters, so you can call it with something like:

    new_neuron = old_neuron.clone( branching_length=n1, branching_randomness=r2 )
    
    0 讨论(0)
  • 2020-12-04 08:46

    Looks like you could cut down the number of arguments by constructing objects such as Axon, Soma and DendriticTree outside of the LayerV constructor, and passing those objects instead.

    Some of the parameters are only used in constructing e.g. DendriticTree, others are used in other places as well, so the problem it's not as clear cut, but I would definitely try that approach.

    0 讨论(0)
  • 2020-12-04 08:51

    UPDATE: This approach may be suited in your specific case, but it definitely has its downsides, see is kwargs an antipattern?

    Try this approach:

    class Neuron(object):
    
        def __init__(self, **kwargs):
            prop_defaults = {
                "num_axon_segments": 0, 
                "apical_bifibrications": "fancy default",
                ...
            }
            
            for (prop, default) in prop_defaults.iteritems():
                setattr(self, prop, kwargs.get(prop, default))
    

    You can then create a Neuron like this:

    n = Neuron(apical_bifibrications="special value")
    
    0 讨论(0)
  • 2020-12-04 08:51

    After looking over your code and realizing I have no idea how any of those parameters relate to each other (soley because of my lack of knowledge on the subject of neuroscience) I would point you to a very good book on object oriented design. Building Skills in Object Oriented Design by Steven F. Lott is an excellent read and I think would help you, and anyone else in laying out object oriented programs.

    It is released under the Creative Commons License, so is free for you to use, here is a link of it in PDF format http://homepage.mac.com/s_lott/books/oodesign/build-python/latex/BuildingSkillsinOODesign.pdf

    I think your problem boils down to the overall design of your classes. Sometimes, though very rarely, you need a whole lot of arguments to initialize, and most of the responses here have detailed other ways of initialization, but in a lot of cases you can break the class up into more easier to handle and less cumbersome classes.

    0 讨论(0)
提交回复
热议问题