vendredi 30 août 2019

Class Architecture: Circular Dependency of Inner and Outer Class in Python

My Design

Verbal Description

I have a class Model, of course with some methods on it. Besides that I have a class ModelList whose childclass represents a List of instances of q child class of Model. Amongst other things, the use of ModelList child classes is to provide bulk operations which differ from just delegating the operation to each of the elements of the ModelList. So, the purpose of the ModelList child classes is to "vectorize" methods of the corresponding Model class.

I also use the ModelList in some places where I want to allow either a childclass of Model or ModelList allowed as parameter passed to a function.

ModelList is knowing (and checking) the type that will be accepted for any of its elements. To make the ModelList childclass know its corresponding Model child class, I define this as a class variable element_type on the ModelList child class.

Each ModelList child class is closely coupled to a Model childclass: One ModelList class belongs to one Model class. That's why I put the ModelList child class as an inner classes to their respective Model class. An here comes my problem: Because ModelList needs to know Model and Model needs to know ModelList and both during initialisation of each class, I have a circular dependency between my classes.

Minimum Example

I reduced my code to a Minimum Example to make my design easier understandable:

class Model(ABC):
    pass

class ModelList(list):
    @classmethod
    def __init__(self, elements=None):
        elements = list() if not elements else elements

        for value in elements:
            self._check_type(value)

        list.__init__(self, elements)

    def _check_type(self, val):
        if not isinstance(val, self.__class__.element_type):
            raise TypeError(
            f"{self.__class__} accepts only instances of {self.__class__.element_type} as elements. `{val}` is not!") 

The following leads to the Error free variable 'SomeModel' referenced before assignment in enclosing scope:

class SomeModel(Model):
    class List(ModelList):
        element_type = SomeModel  # this causes the Error

I do not want to decouple

I know I can get rid of the circular dependency by just decoupling the two classes. But I do want both the Model class know its corresponding ModelList class and also I want the ModelList class to know its Model class. Each Model class ought to have one and only one List attached to it.

Is monkey patching appropriate?

I know I can circumvent the dependency by "monkeypatching" my Model child class like this:

class SomeModel(Model):
    pass

class SomeModelList(ModelList):
        element_type = SomeModel

SomeModel.List = SomeModelList

For me it feels like this is a sign of a design flaw. I cannot say why but it feels "wrong".

Questions

  1. Is monkeypatching appropriate here? Or is it indicating a deeper conceptual problem of my design?
  2. Which other solutions are there?
  3. How can I redesign to get rid of this circular dependencny?
  4. Is it possible to evaluate the element_type at some point later when the respective Model childclass is defined?

Aucun commentaire:

Enregistrer un commentaire