The Composite Design Pattern is the third in the Gamma's Structural Category that I want to share in this post. Let's see what is this design pattern and how to implement it.
Note: Code can be downloaded at my Github.
1. What is the Composite Design Pattern?
The Composite Design Pattern has the peculiar goal of allowing us to treat individual components and aggregate objects in the same manner. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies.
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.
2. When to implement the Composite Design Pattern?
When the core model of your application has a tree structure, it's time to consider the Composite Design Pattern.
There are many examples of a tree structure in applications. For instance, if you have a e-commerce website which utilizes an ordering system that uses Product and Box classes. One order can contain one ore more boxes. Also, a box can contain one or more smaller boxes or products. In the smaller boxes, they can also contain other boxes or products as well.
Another example is a supervisory chain of command, which represents the manager-employee hierarchy in a work place. You are reporting directly to your manager and your manager may have his or her manager who is also reporting to their higher level managers. Also, you may be a manager of someone else.
So, what is the problem here? The problem is we have different classes (Box and Product in the first example and different types of employee in the second example) which are difficult to process in one looping round. We need to come up with a way to treat the different classes uniformly. The Composite Design Pattern can help to achieve that goal.
3. How to implement the Composite Design Pattern?
Let's take the supervisory hierarchy example above to implement the Composite Design Pattern. In your company, there are 3 types of Employee: Fulltime, Contractor, and Intern which have different characteristics.
Step 1: Create a Component interface
public interface IEmployeeComponent { void Add(Employee e); void Remove(Employee e); void Display(int depth); }
Step 2: Create a Composite class
public abstract class Employee : IEmployeeComponent { public string Name { get; set; } public string Title { get; set; } public List<Employee> _children { get; set; } = new List<Employee>(); public Employee(string name, string title) { Name = name; Title = title; } public abstract double GetMonthlySalary(); public void Add(Employee e) { _children.Add(e); } public void Remove(Employee e) { _children.Remove(e); } public void Display(int depth) { Console.WriteLine(new String('-', depth) + $"{Name} " + $"is a {Title} who earns " + $"{GetMonthlySalary().ToString("F2")} monthly."); // Recursively display child nodes foreach (Employee emp in _children) { emp.Display(depth + 2); } } }
Step 3: Create subclasses for different types of Employee
public class Fulltime : Employee { public double AnnualRate { get; set; } public Fulltime(string name, string title, double annualRate) : base(name, title) { AnnualRate = annualRate; } public override double GetMonthlySalary() { return AnnualRate / 12; } } public class Contractor : Employee { public double HourlyRate { get; set; } public Contractor(string name, string title, double hourlyRate) : base(name, title) { HourlyRate = hourlyRate; } public override double GetMonthlySalary() { return HourlyRate * 8 * 5 * 4; } } public class Intern : Employee { public double HourlyRate { get; set; } public double WorkingDaysPerWeek { get; set; } public double HoursPerDay { get; set; } public Intern(string name, string title, double hourlyRate, double workingDaysPerWeek, double hoursPerDay) : base(name, title) { HourlyRate = hourlyRate; WorkingDaysPerWeek = workingDaysPerWeek; HoursPerDay = hoursPerDay; } public override double GetMonthlySalary() { return HourlyRate * HoursPerDay * WorkingDaysPerWeek * 4; } }
So, now you can create a tree of supervisory hierarchy and print it.
class Program { static void Main(string[] args) { Employee ftEmp0 = new Fulltime("Hoang", "Fulltime Employee", 145000.00); Console.WriteLine(); Console.WriteLine("---Add Employee Then Display---"); Employee ftEmp1 = new Fulltime("Rachel", "Fulltime Employee", 90000.00); Employee ctEmp1 = new Contractor("Lu", "Contractor Employee", 72.00); Employee iEmp1 = new Intern("Shawn", "Intern Employee", 15.00, 3, 7); ftEmp0.Add(ftEmp1); ftEmp0.Add(ctEmp1); ftEmp0.Add(iEmp1); Employee ftEmp2 = new Fulltime("Yong", "Fulltime Employee", 82000.00); Employee ctEmp2 = new Contractor("Lucas", "Contractor Employee", 45.00); ftEmp1.Add(ctEmp2); ctEmp1.Add(ftEmp2); ftEmp0.Display(1); Console.WriteLine(); Console.WriteLine("---Remove Employee Then Display---"); ftEmp0.Remove(ftEmp1); ftEmp0.Display(1); } }
4. Conclusion
With the help of the Composite Design Pattern, it's easier to build a structure for a group of complex classes and treat them uniformly. I hope this tutorial is helpful! Please let me know your thoughts and questions in the comment section below. See you next time!