0

I'd like to ask the design pattern when method in a mixin depends on a method of the class that mixin-ed to. The example below is in python, but the question will be also the case with other languages I believe.

For example, say I have the following two mixins, and I'd like to inject into some class. As in the code below I'd like to inject f but f requires that the class mixin-ed to implements g because g will be used in f

from abc import ABC, abstractmethod

class MixinBase(ABC):

    @abstractmethod
    def f(self, a: int) -> int: ...
        # the main function that we want to mix-in

    @abstractmethod
    def g(self, a: int) -> int: ...
        # a method that we know that is used in f()

class Mixin1(MixinBase):
    def f(self, a: int) -> int: return self.g(a) ** 2

class Mixin2(MixinBase):
    def f(self, a: int) -> int: return self.g(a) + 2

Now, my question is, what is the better practice to inject such mixins?

example

I could come up with the following two ways to mixin. The case one is the implicit one:

class ImplicitExample:
    def g(self, a: int): return a
    ## and other methods ...

class ImplicitExampleWithMixin1(ImplicitExample, Mixin1): ...
class ImplicitExampleWithMixin2(ImplicitExample, Mixin2): ...

This mixing is implicit in the sense that the implementer of ImplicitExample implicitly know the dependency of the mixins on ImplicitExample.

Another way of mixing is explicitly inherit the MixinBase so that g is guaranteed to be implemented.

class ExplicitExample(MixinBase):
    def g(self, a: int): return a
    # and other methods ...
class ExplicitExampleWithMixin1(ExplicitExample, Mixin1): ...
class ExplicitExampleWithMixin2(ExplicitExample, Mixin2): ...

I think the above two examples has pros-and-cons. The first explicit one is simpler dependency graph but the implementer must be aware the implicit dependency. On the other hand, for the second explicit example, mental stress of implementer is less intensive, but this causes diamond dependency graph. If MixIn is only few its ok, but if many mental stress could be intensive.

orematasaburo
  • 1,207
  • 10
  • 20
  • The first one is odd because there seems to be no reason for `g` to exist *except* in anticipation of a subclass using the mix-in. – chepner Jan 26 '23 at 14:12
  • That is, `ImplicitExample` is itself another mix-in, but one that is (too) tightly coupled to a subclass of `Mixin`. – chepner Jan 26 '23 at 14:14
  • 1
    The second one suffers the same problem as well. If you want a common `g`, define that in a direct subclass of `MixinBase`, and then have *it* as the common parent of `Mixin1` and `Mixin2`. – chepner Jan 26 '23 at 14:20
  • Consider inverting the dependency. If `f` needs to depend on someone else to supply `g`, than have it take the necessary method as an *argument*, and let a caller worry about how to get an appropriate function to pass. – chepner Jan 26 '23 at 14:22
  • You want just a "name"for this pattern, os is there some result you want to achieve that you could not do? Otherwise, not sure why this would have a distict name - you are just using abstract bases. – jsbueno Jan 26 '23 at 14:23
  • This looks like dependency injection, just using inheritance to inject `g` into `f` instead of function arguments. – chepner Jan 26 '23 at 14:26
  • @jsbueno Though I wrote two examples, It would be helpful if one could provide alternative patterns that are free from the problem I pointed for my example: implicitness and diamond graph – orematasaburo Jan 26 '23 at 14:56
  • sorry - I don't see either - the implicitness or diamong graph - as a problem there. This pattern will be naturally demanding on the developer and only good testing can ensure your project will actually work (tests will fail when trying to instantiated the most-derived concrete classes if any abstract method is missing). – jsbueno Jan 26 '23 at 15:13

1 Answers1

0

Personally, I'd probably just document the dependency in the Mixin's docstring and define Mixin.g() as a docstring plus a single line: raise NotImplementedError(). I might also skip defining g() at all outside documentation for the Mixin class. The details are likely to depend on both personal preference and how / by whom you expect the Mixin to be used; a small-group project which is defining the Mixin for internal use is going to have different requirements around portability and documentation than something which is part of a publicly-accessible open-source library. Those requirements should shape your thinking in class design.

If you've got more complex dependencies - multiple things calling g() in different ways, several nested functions / mixins which have to work together, or other complications, then I'd recommend worrying about the dependency graph a bit more. But as you've presented it here? Either's fine.

Reasons I wouldn't stress the details:

  • By definition, Mixins don't have to be independent, instantiable classes
  • Diamond inheritance in Python is potentially confusing, but isn't inherently problematic as long as you give g() a well-documented name that's unlikely to be duplicated accidentally... and you should probably be doing that anyway.
  • For your unit tests, instantiate a separate class which defines MixinUser.g() in some minimal way and exercises Mixin.f(). This demonstrates proper usage (so it's documentation) and makes sure that if the folks subclassing Mixin follow your intent, Mixin.f() will work as intended.

In short, you've got a couple options, but most of the concerns you raised as part of the question are unlikely to be problems in practice as long as you document the dependency and write some tests for it.

Sarah Messer
  • 3,592
  • 1
  • 26
  • 43