Featured image of post SP4 - Mixer - Learn Design Pattern From Simple Things

SP4 - Mixer - Learn Design Pattern From Simple Things

You are a dog farm owner who manages the properties of all dog breeds. The number of crossbred dogs is increasing every day because of the emergence of new breeds!

Mixer (Bridge) is a structural design pattern: šŸ–¤šŸ–¤šŸ–¤šŸ¤šŸ¤

What is Mixer?

Mixer is a structural design pattern that divides business logic or huge class into separate class hierarchies that can be developed independently. One of these hierarchies (often called the Abstraction) will get a reference to an object of the second hierarchy (Implementation).

imagine that you have a class hierarchy of different types of dogs, such as Labrador and Poodle, and another class hierarchy of different types of coats, such as Curly and Straight. You want to be able to create any combination of dog and coat without creating a cartesian product of subclasses, such as LabradorCurly, LabradorStraight, PoodleCurly, and PoodleStraight.

Mixer diagram

Why use Mixer?

Mixer avoids the problem of creating too many subclasses by separating the two aspects into separate class hierarchies and using composition to combine them. This way, you can reuse the same methods for different types of dogs and coats without creating a cartesian product of subclasses. This makes the code more modular, flexible, and maintainable.

Advantages:

  • Reduces code duplication and complexity
  • Follows the principle of composition over inheritance
  • Allows changing or extending abstractions and implementations independently
  • Hides implementation details from the client

Disadvantages:

  • Increases the number of classes and objects
  • Requires more effort to create the initial abstraction
  • May introduce indirection and complexity in some cases

non Mixer

Question: I hate the Mixer, what are the alternatives?

Answer: You can use inheritance instead of composition, but you will have to create more subclasses and repeat the same methods for each combination of dog and coat. This will violate the principle of composition over inheritance and make your code less flexible and maintainable.

When to use Mixer?

Question: When do I need to use Mixer?

Answer: You should use Mixer when you want to avoid creating a large number of subclasses for two orthogonal aspects that can vary independently.

Input:

You have two class hierarchies:

  • one for different types of dogs: Labrador, Poodle, …
  • another for different types of coats: Curly, Straight, …
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Labrador(Dog):
    def bark(self):
        print("The labrador barks loudly.")


class Poodle(Dog):
    def bark(self):
        print("The poodle barks softly.")


class Curly(Coat):
    def shed(self):
        print("The curly coat sheds little hair.")


class Straight(Coat):
    def shed(self):
        print("The straight coat sheds moderate hair.")

Expected Output:

  • Can use composition to pass an Coat concrete object to Dog concrete object,
  • Delegate some of the methods to the Coat object, such as shed().
1
2
3
4
5
The labrador barks loudly.
The straight coat sheds moderate hair.

The poodle barks softly.
The curly coat sheds little hair.

This way, you can reuse the same methods for different types of dogs and coats without creating a cartesian product of subclasses.

How to implement Mixer?

Non-Mixer implementation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class LabradorCurly:
    # A subclass that combines two aspects: labrador and curly
    def bark(self):
        print("The labrador barks loudly.")

    def shed(self):
        print("The curly coat sheds little hair.")


class LabradorStraight:
    # A subclass that combines two aspects: labrador and straight
    def bark(self):
        print("The labrador barks loudly.")

    def shed(self):
        print("The straight coat sheds moderate hair.")


class PoodleCurly:
    # A subclass that combines two aspects: poodle and curly
    def bark(self):
        print("The poodle barks softly.")

    def shed(self):
        print("The curly coat sheds little hair.")


class PoodleStraight:
    # A subclass that combines two aspects: poodle and straight
    def bark(self):
        print("The poodle barks softly.")

    def shed(self):
        print("The straight coat sheds moderate hair.")


if __name__ == "__main__":
    # Create a labrador with a straight coat
    lab_straight = LabradorStraight()
    lab_straight.bark()
    lab_straight.shed()

    # Create a poodle with a curly coat
    poo_curly = PoodleCurly()
    poo_curly.bark()
    poo_curly.shed()

This code has four subclasses that each implement their own methods for barking and shedding. This leads to code duplication and complexity.

If you want to add more types of dogs or coats, you will have to create more subclasses and repeat the same methods. This violates the principle of composition over inheritance.

Mixer Implementation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Dog:
    # Abstraction
    def __init__(self, coat):
        # Reference to an object of type Coat
        self.coat = coat

    def bark(self):
        pass

    def shed(self):
        # Delegate to the Coat object
        self.coat.shed()


class Labrador(Dog):
    # Refined Abstraction
    def bark(self):
        print("The labrador barks loudly.")


class Poodle(Dog):
    # Refined Abstraction
    def bark(self):
        print("The poodle barks softly.")


class Coat:
    # Implementer
    def shed(self):
        # Abstract method
        pass


class Curly(Coat):
    # Concrete Implementation
    def shed(self):
        print("The curly coat sheds little hair.")


class Straight(Coat):
    # Concrete Implementation
    def shed(self):
        print("The straight coat sheds moderate hair.")


if __name__ == "__main__":
    # Create a labrador with a straight coat
    lab_straight = Labrador(Straight())
    lab_straight.bark()
    lab_straight.shed()

    # Create a poodle with a curly coat
    poo_curly = Poodle(Curly())
    poo_curly.bark()
    poo_curly.shed()

This follows the principle of composition over inheritance:

  • This code has only two subclasses for each aspect: Labrador and Poodle for dogs, and Curly and Straight for coats.

  • One of these hierarchies (often called the Abstraction) will get a reference to an object of the second hierarchy (Implementation).

This way, you can avoid creating a large number of subclasses for two orthogonal aspects that can vary independently. This makes the code more modular, flexible, and maintainable.

Source Code

Made with the laziness šŸ¦„
by a busy guy