master-wiki/void/Readwise/The Composition Over Inheritance Principle¶.md

6.3 KiB
Raw Permalink Blame History

The Composition Over Inheritance Principle¶

rw-book-cover

Metadata

[!tldr] The Composition Over Inheritance principle emphasizes that using composition (combining classes) is often more effective than inheritance (subclassing) for managing complexity in code. By employing design patterns like Adapter, Bridge, and Decorator, developers can create flexible logging systems that separate filtering from logging behavior. This approach allows for easier maintenance and the ability to mix and match different filters and handlers at runtime without creating a large number of subclasses.

Highlights

Favor object composition over class inheritance. View Highlight)

A crucial weakness of inheritance as a design strategy is that a class often needs to be specialized along several different design axes at once, leading to what the Gang of Four call “a proliferation of classes” in their Bridge chapter and “an explosion of subclasses to support every combination” in their Decorator chapter. View Highlight)

The total number of classes will increase geometrically if m and n both continue to grow. This is the “proliferation of classes” and “explosion of subclasses” that the Gang of Four want to avoid. View Highlight)

The solution is to recognize that a class responsible for both filtering messages and logging messages is too complicated. In modern Object Oriented practice, it would be accused of violating the “Single Responsibility Principle.” View Highlight)

One solution is the Adapter Pattern: to decide that the original logger class doesnt need to be improved, because any mechanism for outputting messages can be wrapped up to look like the file object that the logger is expecting. View Highlight)

Python encourages duck typing View Highlight)

Note

Duck typing: A programming concept primarily associated with the Python language, duck typing emphasizes an object's behavior (methods and properties) over its explicit type. The term is derived from the saying, "If it looks like a duck and quacks like a duck, it must be a duck," indicating that the suitability of an object is determined by the presence of certain methods and attributes rather than its inheritance or class. This approach allows for greater flexibility and adaptability in coding, as it encourages developers to focus on what an object can do rather than what it is, facilitating the creation of lightweight adapters and promoting code reuse.

And so the subclass explosion is avoided! Logger objects and adapter objects can be freely mixed and matched at runtime without the need to create any further classes: View Highlight)

The Bridge Pattern splits a classs behavior between an outer “abstraction” object that the caller sees and an “implementation” object thats wrapped inside. We can apply the Bridge Pattern to our logging example if we make the (perhaps slightly arbitrary) decision that filtering belongs out in the “abstraction” class while output belongs in the “implementation” class. View Highlight)


New highlights added at 2024-10-13 4:11 PM Instead of file output being native to the Logger but non-file output requiring an additional class, a functioning logger is now always built by composing an abstraction with an implementation. View Highlight)

The reason we cannot stack two filters is that theres an asymmetry between the interface they offer and the interface they wrap: they offer a log() method but call their handlers emit() method. Wrapping one filter in another would result in an AttributeError when the outer filter tried to call the inner filters emit(). If we instead pivot our filters and handlers to offering the same interface, so that they all alike offer a log() method, then we have arrived at the Decorator Pattern View Highlight)

Python logging module implements its own Composition Over Inheritance pattern.

  1. The Logger class that callers interact with doesnt itself implement either filtering or output. Instead, it maintains a list of filters and a list of handlers.
  2. For each log message, the logger calls each of its filters. The message is discarded if any filter rejects it.
  3. For each log message thats accepted by all the filters, the logger loops over its output handlers and asks every one of them to emit() the message. View Highlight)

a loggers messages might deserve both multiple filters and multiple outputs — to decouple filter classes and handler classes entirely View Highlight)

design principles like Composition Over Inheritance are, in the end, more important than individual patterns like the Adapter or Decorator. Always follow the principle. But dont always feel constrained to choose a pattern from an official list. View Highlight)

Sometimes, yes, you will find an existing Design Pattern thats a perfect fit for your problem — but if not, your design might be stronger if you move beyond them. View Highlight)

I suggest that the apparent simplicity of the if statement forest is, from the point of view of software design, largely an illusion. View Highlight)