Reputation: 502
Given these classes forming a flexible tree:
class ElementBase(object):
pass
class Form(ElementBase):
pass
class TextInput(ElementBase):
pass
class DateTimeInput(ElementBase):
pass
class NumberInput(ElementBase):
pass
If an element is inside a Form it should be able to find said form. Since this is a very common case for elements (in this case about a third of the Elements inheriting from ElementBase need to do it) this feature belongs into the ElementBase. Which directly leads to a circular dependency since the only way I came up with is this:
def get_parent_form(self, compo):
if isinstance(compo, Form):
return compo
if not hasattr(compo, 'container_compo'):
return None
return self.get_parent_form(compo.container_compo)
How do I do this properly?
Upvotes: 2
Views: 109
Reputation: 896
@jonrsharpe talked about this some in the comments but I'll elaborate some more:
Your structure as-is doesn't make sense as an object hierarchy. Your Form is partly made up of a composition of your *Input objects, all of which can then be grouped together by the common attribute of being Elements and having a Form parent. This is also distinguishable behavior from the Form itself, as (at least from what I can tell) your Forms cannot be nested inside one another, and distinguishable from other ElementBase inheriting objects because by your account only a third of them exhibit this behavior.
Because you have a large group of objects all exhibiting common behavior in a clearly separable way, the most reasonable thing to do is implement a parent class that implements that behavior, and have the necessary Inputs inherit from that class.
You then have ElementBase -> Form and ElementBase -> Input -> TextInput, etc.
This isn't "hacky" because you can clearly delineate this behavior and make logical groupings of the classes that exhibit it. I would consider it much more hacky to try to shoehorn this logic into ElementBase when 2/3 of the classes that inherit from it don't display that behavior.
Also note that if you did a FormInput like you talked about in the comments, then that FormInput wouldn't have to reside in the Form's module as it describes its own group of objects that just happen to have a relation to Form.
There are ways that you could re-write this whole system to allow for things like arbitrary Form-within-Form nesting, etc. but that would probably end up with a rather radically different API. One thing you might want to consider right now, though, is why you're having your child elements access their parent Forms in the first place. As a general OOP rule you really only want your parents to be accessing their children, because otherwise your objects usually aren't properly encapsulating their specific behavior and properties. In your example, I'd only expect a TextInput to "own" the characters inside of it, so why would it need to manipulate something in the Form? Why can't the Form just grab the text from the input when it needs to?
You might want to look at some other code bases that have done something similar to your project for ideas on how to model the data. I would recommend Django-REST-Framework as a particularly good example of a form-input (in their case, Serializer-Field) implementation. Their Serializers are very much like Forms but because they're careful in the way they access their data, they can have Serializers inherit from Fields and thus make them arbitrarily nestable. And it's all very pretty code, too.
Upvotes: 1