The Dance of Components: The Composite Design Pattern
[tr] Türkçe Oku 2023-06-22
Once upon a time, there was a tree in a forest. This tree was a complex structure with leaves and branches. Each branch could have smaller branches and leaves on it. The tree worked as a whole, holding its branches and leaves together.
This tree is an example of the Composite design pattern. The tree (Composite) contains two types of components: branches (also Composite) and leaves (Leaf). Both branches and leaves implement the same interface (Component) recognized by the tree. This allows the tree to handle its branches and leaves in the same way.
Now let’s tell this fairy tale with code examples:
C#
interface IComponent
{
void Operation();
}
class Leaf : IComponent
{
public void Operation()
{
// leaf operation
}
}
class Composite : IComponent
{
private List<IComponent> _children = new List<IComponent>();
public void Operation()
{
foreach (var child in _children)
{
child.Operation();
}
}
public void Add(IComponent component)
{
_children.Add(component);
}
public void Remove(IComponent component)
{
_children.Remove(component);
}
}
Java
interface Component {
void operation();
}
class Leaf implements Component {
public void operation() {
// leaf operation
}
}
class Composite implements Component {
private List<Component> children = new ArrayList<>();
public void operation() {
for (Component child : children) {
child.operation();
}
}
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
}
Go
type Component interface {
Operation()
}
type Leaf struct{}
func (Leaf) Operation() {
// leaf operation
}
type Composite struct {
children []Component
}
func (c *Composite) Operation() {
for _, child := range c.children {
child.Operation()
}
}
func (c *Composite) Add(component Component) {
c.children = append(c.children, component)
}
func (c *Composite) Remove(component Component) {
// remove children
}
**Go** (continued)
```go
for i, child := range c.children {
if child == component {
c.children = append(c.children[:i], c.children[i+1:]...)
break
}
}
}
Rust
trait Component {
fn operation(&self);
}
struct Leaf;
impl Component for Leaf {
fn operation(&self) {
// leaf operation
}
}
struct Composite {
children: Vec<Box<dyn Component>>,
}
impl Component for Composite {
fn operation(&self) {
for child in &self.children {
child.operation();
}
}
}
impl Composite {
fn add(&mut self, component: Box<dyn Component>) {
self.children.push(component);
}
fn remove(&mut self, component: Box<dyn Component>) {
// remove children
}
}
These code examples show how to implement the Composite design pattern. In each language, an interface or trait named Component
(or IComponent
) is defined. This interface is implemented by the Leaf
and Composite
classes. The Composite
class stores and manages child components. This allows Composite
and Leaf
objects to be handled through the same interface.
There are several important reasons to use the Composite design pattern:
- Simplifies Hierarchical Structures: The Composite design pattern simplifies tree-like structures. For example, files and folders in an
operating system or departments and sub-departments in a company.
-
Provides Transparency Between Objects: The Composite design pattern allows client code to treat individual objects and object collections in the same way. This helps to make the code simpler and cleaner.
-
Increases Code Extensibility: It is easy to add new types of components because you don’t need to change existing code. To add a new type of component, you just need to create a new class that implements the
Component
interface. -
Increases Reusability: The Composite design pattern increases code reusability. You can share code between different components that implement the same operations.
-
Supports the Open/Closed Principle: The Composite design pattern supports the open/closed principle. That is, your code is open to new types of components but closed to modifications of existing code.
For these reasons, the Composite design pattern is an excellent choice for managing complex object hierarchies. However, it may not be suitable for all situations. For example, if the hierarchy is very deep or contains many objects, the Composite design pattern could cause performance issues. Therefore, it is important to carefully evaluate your requirements before using this pattern.