C.W.K.
Stream
Lesson 02 of 05 · published

Mixins and Composition over Inheritance

~18 min · mixin, composition, behavior-injection

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

Mixin — the inheritance pattern that doesn't pretend to be is-a

A mixin is a class designed to be combined with others, never instantiated alone. It typically provides a focused chunk of behavior — SerializableMixin adds to_json(), LoggingMixin adds log(). The receiving class inherits from the mixin to get that behavior. The naming convention helps: NameMixin reads like a label, not a noun.

Why mixins exist when composition is preferred

If composition is the recommended default, why mixins? They earn their keep when (a) the behavior really should be on every method call, (b) the behavior depends on the host class's attributes (a mixin can use self.x from whatever class includes it), and (c) you need polymorphic dispatch — code that works with any "serializable" object regardless of concrete class.

The risks — diamonds and surprise overrides

Two mixins that define the same method create a diamond. Adding a mixin to a hierarchy can override methods you didn't expect to override. The MRO determines which wins. Use mixins for orthogonal concerns where collisions are unlikely, not for overlapping ones.

When composition wins

Most of the time. self.serializer = JsonSerializer() is clearer than class MyClass(JsonSerializerMixin):. You can swap serializers without changing the class. The hierarchy stays flat. Tests can inject fakes. The cost is one indirection (obj.serializer.serialize() vs obj.serialize()) — usually worth it.

Pythonic Way: Reach for mixins when (1) the behavior is genuinely cross-cutting (every method on the class should see it), (2) the host class won't conflict with the mixin's methods, (3) you want isinstance(obj, SerializableMixin) to mean something. Otherwise reach for composition.

Code

Mixin — adds focused behavior·python
import json

class JsonMixin:
    def to_json(self):
        return json.dumps(self.__dict__)

    @classmethod
    def from_json(cls, s):
        return cls(**json.loads(s))

class User(JsonMixin):
    def __init__(self, name, email):
        self.name = name
        self.email = email

u = User("alice", "a@x.com")
print(u.to_json())                  # {"name": "alice", "email": "a@x.com"}
u2 = User.from_json(u.to_json())
print(u2.name, u2.email)

# JsonMixin can be reused for any class with simple attributes
Multiple mixins — orthogonal concerns·python
class TimestampMixin:
    def __init__(self, **kwargs):
        import datetime
        self.created_at = datetime.datetime.now()
        super().__init__(**kwargs)

class IdMixin:
    _next_id = 1
    def __init__(self, **kwargs):
        self.id = type(self)._next_id
        type(self)._next_id += 1
        super().__init__(**kwargs)

class Order(TimestampMixin, IdMixin):
    def __init__(self, item, **kwargs):
        self.item = item
        super().__init__(**kwargs)

o1 = Order("book")
o2 = Order("pen")
print(o1.id, o1.item, o1.created_at)
print(o2.id, o2.item, o2.created_at)
Composition — usually clearer·python
import json

class JsonSerializer:
    def serialize(self, obj):
        return json.dumps(obj.__dict__)

    def deserialize(self, cls, s):
        return cls(**json.loads(s))

class User:
    serializer = JsonSerializer()      # composition

    def __init__(self, name, email):
        self.name = name
        self.email = email

u = User("alice", "a@x.com")
print(User.serializer.serialize(u))
# Easy to swap: User.serializer = XmlSerializer()
Mixin gone wrong — name collision·python
class LoggerMixin:
    def log(self, msg):
        print(f"[LOG] {msg}")

class MetricsMixin:
    def log(self, msg):              # SAME name — collision
        print(f"[METRIC] {msg}")

class Service(LoggerMixin, MetricsMixin):
    pass

s = Service()
s.log("hello")     # [LOG] hello   — LoggerMixin wins (first in MRO)

# Refactoring tip: rename one to avoid the collision
# class LoggerMixin: def log_event(self, ...)
# class MetricsMixin: def emit_metric(self, ...)

External links

Exercise

Define EqualityMixin that provides __eq__ (compares all instance attributes) and __hash__ (frozenset of items). Create three unrelated classes (Coordinate(x, y), Color(r, g, b), Tag(name)) all using EqualityMixin. Demonstrate that equality works correctly across all three. Then write a composition-based version: a Comparator helper class with the same logic, and have each class hold a comparator attribute. Compare which one feels cleaner.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.