2021-06-01
|~5 min read
|901 words
I’m actively reading the Gang of Four (aka Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides’s Design Patterns: Elements of Reusable Object-Oriented Software). Below are some of my notes. I’ll continue to fill this in as I make progress / learn more.
More than anything else, the uncovering and mastery of powerful organizational techniques accelerates our ability to create large, significant programs.
Harold Abelson, Gerald Jay Sussman, Julie Sussman. Structure and Interpretation of Computer Programs, Second Edition.
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
Simplify the client code by allowing the client to not know (or care) about the differences between elements in the composition.
The two component pieces are Composites and Leafs. Composites are components that can have children.
Much of the discussion is around where to put declaration and implementation for certain methods that are not equally applicable to all subclasses, e.g., add
and remove
. There’s a trade-off between safety and transparency:
Implement methods in … | Safety | Transparency |
---|---|---|
Root | ❌ - Leaves may try to add children, which doesn’t make sense | ✅ - All classes share the same API |
Composite | ✅ - Compile time checks ensure that disallowed methods are caught | ❌ - Composite and Leaves have different interfaces |
This tradeoff reveals a tension with the Principle of Class Hierarchy Design which says a class should only define operations that are meaningful to its subclasses. One clever solution: have the root (Component
) define children
, but the default implementation is return no children, leaving it to the Composite
to implement its own method. In this way all classes have the same API, but it only performs the desired / expected behavior where it should. This, however, opens up a new plane for bugs in that a Leaf
could try to access its children and while it won’t work, it shouldn’t have attempted in the first place.
Determining where / how to share components is another potential design decision of the Composite pattern. To ensure that the parent/child relationship is stable, it’s useful to ensure that the only places where the relationship is modified is in the add
/ remove
methods. However, this becomes trickier if you also want to share components (i.e., not create multiple instances and therefore reduce storage requirements). By implication, this means that a child can now have multiple parents.
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use them.
Use the Strategy pattern if you have many classes that all implement a similar interface, but differ in how they perform a single algorithm. In this way, the Strategy pattern provides an alternative to subclassing. Instead of defining the different implementation in each subclass, you can abstract the algorithmic differences to Strategy subclasses and allow your main class to evolve independently.
The “Composition” (the class that’s being invoked) sends a request to the “Compositor” (the class that declares the common interface for the various strategies). The context of that request should be sufficient for the Compositor to know which algorithms to invoke (i.e., which strategy is called for).
In this way, there are three participants in the Strategy Pattern:
Some of the benefits / drawbacks of the Strategy pattern:
When implementing a Strategy pattern, there are three primary modes of defining the communication between the Context and ConcreteStrategy (by way of the Strategy):
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!