Lecture 7 - Structural Design Patterns (Composite, Decorator)
Lecture 7 - Structural Design Patterns (Composite, Decorator)
Design Patterns
Composite, Decorator
COMPOSITE
Composite is a structural design pattern that lets you compose objects into tree
structures and then work with these structures as if they were individual objects.
Problem
An order might comprise various products, packaged in boxes, which are packaged in
bigger boxes and so on. The whole structure looks like an upside down tree.
Solution
The Composite pattern lets you run a behavior recursively over all components of an
object tree.
Real-World Analogy
● Use the pattern when you want the client code to treat both simple and
complex elements uniformly.
How to Implement
1. Make sure that the core model of your app can be represented as a tree structure. Try to break
it down into simple elements and containers. Remember that containers must be able to
contain both simple elements and other containers.
2. Declare the component interface with a list of methods that make sense for both simple and
complex components.
3. Create a leaf class to represent simple elements. A program may have multiple different leaf
classes.
4. Create a container class to represent complex elements. In this class, provide an array field for
storing references to subelements. The array must be able to store both leaves and containers,
so make sure it’s declared with the component interface type. While implementing the methods
of the component interface, remember that a container is supposed to be delegating most of
the work to sub-elements.
5. Finally, define the methods for adding and removal of child elements in the container. Keep in
mind that these operations can be declared in the component interface. This would violate the
Interface Segregation Principle because the methods will be empty in the leaf class. However,
the client will be able to treat all the elements equally, even when composing the tree.
Pros and Cons
+ You can work with complex tree structures more conveniently: use
polymorphism and recursion to your advantage.
+ Open/Closed Principle. You can introduce new element types into the app
without breaking the existing code, which now works with the object tree.
- It might be difficult to provide a common interface for classes whose
functionality differs too much. In certain scenarios, you’d need to
overgeneralize the component interface, making it harder to comprehend.
Relations with Other Patterns
● You can use Builder when creating complex Composite trees because you can program its
construction steps to work recursively.
● Chain of Responsibility is often used in conjunction with Composite. In this case, when a leaf
component gets a request, it may pass it through the chain of all of the parent components down to
the root of the object tree.
● You can use Iterators to traverse Composite trees.
● You can use Visitor to execute an operation over an entire Composite tree.
● You can implement shared leaf nodes of the Composite tree as Flyweights to save some RAM.
● Composite and Decorator have similar structure diagrams since both rely on recursive composition
to organize an open-ended number of objects. A Decorator is like a Composite but only has one
child component. There’s another significant difference: Decorator adds additional responsibilities to
the wrapped object, while Composite just “sums up” its children’s results. However, the patterns can
also cooperate: you can use Decorator to extend the behavior of a specific object in the Composite
tree.
● Designs that make heavy use of Composite and Decorator can often benefit from using
Prototype. Applying the pattern lets you clone complex structures instead of re-constructing them
DECORATOR
Decorator is a structural design pattern that lets you attach new behaviors to
objects by placing these objects inside special wrapper objects that contain the
behaviors.
Problem
A program could use the notifier class to send notifications about important events to a
predefined set of emails.
Problem
● Use the pattern when it’s awkward or not possible to extend an object’s
behavior using inheritance.
How to Implement
1. Make sure your business domain can be represented as a primary component with
multiple optional layers over it.
2. Figure out what methods are common to both the primary component and the optional
layers. Create a component interface and declare those methods there.
3. Create a concrete component class and define the base behavior in it.
4. Create a base decorator class. It should have a field for storing a reference to a wrapped
object. The field should be declared with the component interface type to allow linking to
concrete components as well as decorators. The base decorator must delegate all work
to the wrapped object.
5. Make sure all classes implement the component interface.
6. Create concrete decorators by extending them from the base decorator. A concrete
decorator must execute its behavior before or after the call to the parent method (which
always delegates to the wrapped object).
7. The client code must be responsible for creating decorators and composing them in the
way the client needs.
Pros and Cons
+ You can extend an object’s behavior without making a new subclass.
+ You can add or remove responsibilities from an object at runtime.
+ You can combine several behaviors by wrapping an object into multiple
decorators.
+ Single Responsibility Principle. You can divide a monolithic class that
implements many possible variants of behavior into several smaller classes.
- It’s hard to remove a specific wrapper from the wrappers stack.
- It’s hard to implement a decorator in such a way that its behavior doesn’t
depend on the order in the decorators stack.
- The initial configuration code of layers might look pretty ugly.
Relations with Other Patterns
● Adapter changes the interface of an existing object, while Decorator enhances an object without changing its interface. In
addition, Decorator supports recursive composition, which isn’t possible when you use Adapter.
● Adapter provides a different interface to the wrapped object, Proxy provides it with the same interface, and Decorator
provides it with an enhanced interface.
● Chain of Responsibility and Decorator have very similar class structures. Both patterns rely on recursive composition to
pass the execution through a series of objects. However, there are several crucial differences. The CoR handlers can
execute arbitrary operations independently of each other. They can also stop passing the request further at any point. On the
other hand, various Decorators can extend the object’s behavior while keeping it consistent with the base interface. In
addition, decorators aren’t allowed to break the flow of the request.
● Composite and Decorator have similar structure diagrams since both rely on recursive composition to organize an open-
ended number of objects. A Decorator is like a Composite but only has one child component. There’s another significant
difference: Decorator adds additional responsibilities to the wrapped object, while Composite just “sums up” its children’s
results. However, the patterns can also cooperate: you can use Decorator to extend the behavior of a specific object in the
Composite tree.
● Designs that make heavy use of Composite and Decorator can often benefit from using Prototype. Applying the pattern
lets you clone complex structures instead of re-constructing them from scratch.
● Decorator lets you change the skin of an object, while Strategy lets you change the guts.
● Decorator and Proxy have similar structures, but very different intents. Both patterns are built on the composition principle,
where one object is supposed to delegate some of the work to another. The difference is that a Proxy usually manages the
life cycle of its service object on its own, whereas the composition of Decorators is always controlled by the client.
Thank you!