Studying GoF's Design Patterns(1994) book
Using tree structure to represent part-whole hiearchies. Let's clients treat both individual objects and compositions of objects uniformly.
The key is an abstract class that can represent both primitives and containers that contain a composition of these containers.
So Graphic
is the abstract class. And Line
, Rectangle
, Text
are derived primitive subclasses. Then Picture
Class is a container(or aggregate), composed of these primitive classes. Therefore it has child managing feature like Add()
, Remove()
, GetChild()
. Since Picture
Class also conforms to Graphic
Abstract class, its draw()
can tell its child to call draw()
recursively. Note that Picture
class can also be a child to another Picture
class.
Component - the abstract class
Leaf - primitives
Composite - aggregate of primitives + child related functionality
Client - uses objects in composition, via the component interface.
Defines hiearchy where primitive objects can be composed into more complex objects recursively.
Client code is simpler. Client code can treat Composite
and Leaf
as same. No need for tag-and-case-statement-style functions
Make it easier to add new components, using the Component
abstract class.
Can make your design overly general. As downside of making adding new components easier, the abstract class can be too general to enforce constraints. May need to do runtime checks instead of using type system at compile time.
Explicit parent reference. Usually in the Component abstract class. Make sure to have the invariant, where parent has reference to child and child has reference to parent. reference should go both ways, like doubly-linked list. Easiest way is to change a component's parent only when it is added or removed from composite.
Sharing components. You can make it so there can only be one parent. But to share component, a child can have more than one parent. But this can lead to ambiguities. Flyweight Pattern shows how to rework a design to avoid having reference to parents altogether, by externalizing some or all of their state.
Maximizing the Component interface. good, but sometimes conflicts with principle of class hiearchy design that says a class should only define operations that are meaningful to its subclasses.
Declaring the child management operations. You can define them in components, or define them in composites. tradeoff vs safety tradeoff.
or you could try to do GetComposite()
check in component abstract class, that defaults to a null pointer. If it returns a composite object, you can do child management operations. Else, no. Usually it’s better to make Add and Remove fail by default (perhaps by raising an exception) if the component isn’t allowed to have children or if the argument of Remove isn’t a child of the component, respectively.
Should component implement a list of components? No. Only if there are very few children in the structure.
Child ordering. If needed, but design carefully. check Iterator pattern.
Caching to improve performance. Maybe. But when component changes, it needto invalidate cache of parents. I think modern interpreters/processors or sth handles this well already?
Who should delete components? Most languages have garbage collection nowdays. just remove reference to it. no need to free()
memory or anything.
What's the best DS for storing components? Depends. check Interpreter pattern for example.