Share This Article
Let’s explore the Composite Pattern in Ruby:
A composite is a structural design pattern that allows you to compose objects into a tree-like structure and then work with this structure as if it was a single object.
The composite pattern is often used in UI applications to represent objects in a tree-like structure (for example, files and directories in the file explorer).
You can think of the composite pattern as an alternative to inheritance. Instead of using inheritance to compose objects, you use aggregation.
For example, let’s say you want to represent a file system in your application. files and directories can be stored in other directories (sub-directories), so making the directory
class a composite would make sense.
class Directory
def initialize(name)
@name = name
@files = []
@directories = []
end
def add_file(file)
@files << file
end
def add_directory(directory)
@directories << directory
end
def size
@files.reduce(0) { |total_size, file| total_size + file.size } +
@directories.reduce(0) { |total_size, directory| total_size + directory.size }
end
end
class File
def initialize(name, size)
@name = name
@size = size
end
def size
@size
end
end
# usage
root = Directory.new("root")
root.add_file(File.new("file1", 10))
root.add_file(File.new("file2", 20))
sub_directory = Directory.new("sub-directory")
sub_directory.add_file(File.new("sub-file1", 30))
sub_directory.add_file(File.new("sub-file2", 40))
root.add_directory(sub_directory)
root.size
# 100
In this example, you have a Directory
and a File
class. You can put files in other directories (sub-directories).
When you want to calculate the size of a directory (which conceptually is the sum of all file sizes in that directory and its sub-directories), you can use the size
method.
The size
method uses recursion to first add all file sizes in the current directory and then all file sizes of the sub-directories.
Using Modules As Mixins
The composite pattern is often used with the decorator pattern to add extra functionality to an object.
For example, you may want to add extra functionality to your File
and Directory
class.
Example:
module Gzip
def compress
puts "Compressing #{self.name}"
end
end
class File
include Gzip
# other methods ...
end
class Directory
include Gzip
# other methods ...
end
# usage
sub_directory = Directory.new("sub-directory")
sub_directory.compress
# Compressing sub-directory
Here you have a Gzip
module that contains a compress
method. You include that module in your File
Directory
classes.
Now you can use the compress
method in both classes.
Summary
In this article, you’ve learned about the composite pattern and how to implement it in Ruby.
You have also learned about using modules as mixins to add extra functionality to the composite classes.