The Composite pattern in C#
Hello, fellow C# developers! Today, I will write a short article about the composite pattern. The composite pattern is a structural design pattern that allows you to compose objects into tree structures and then work with these structures as if they were individual objects. The composite pattern is useful when you need to represent part-whole hierarchies of objects, such as files and folders, GUI components, or organizational charts. 😎
The composite pattern consists of three main components:
- Component: This is an abstract class or an interface that defines the common operations for all the objects in the hierarchy. It can also define some default behavior for common methods.
- Leaf: This is a class that implements the component interface and represents the primitive objects that cannot be further divided. For example, a file is a leaf in a file system hierarchy. 📄
- Composite: This is a class that implements the component interface and represents the complex objects that can contain other components (either leafs or other composites) as children. For example, a folder is a composite in a file system hierarchy. 📁
The main benefit of the composite pattern is that it allows you to treat both simple and complex objects uniformly, by using the same component interface. This way, you can apply the same operations to any object in the hierarchy, without knowing its concrete type. For example, you can calculate the size of any file or folder by calling the same method on them. 🙌
Let’s see an example of how to implement the composite pattern in C#. Suppose we want to model a financial portfolio that consists of different types of assets, such as stocks, bonds, and funds. Each asset has a name and a value, and can be added or removed from the portfolio. A fund is a special type of asset that can contain other assets as its components. Here is how we can define the component interface:
// The component interface defines the common operations for all assets
public interface IAsset
{
// Returns the name of the asset
string GetName();
// Returns the value of the asset
decimal GetValue();
// Adds a new asset to the portfolio (only applicable for funds)
void Add(IAsset asset);
// Removes an existing asset from the portfolio (only applicable for funds)
void Remove(IAsset asset);
}
Here is how we can define the leaf classes for stocks and bonds:
// A stock is a simple asset that has a name and a value
public class Stock : IAsset
{
private readonly string _name;
private readonly decimal _value;
// Creates a new stock with the given name and value
public Stock(string name, decimal value)
{
_name = name;
_value = value;
}
// Returns the name of the stock
public string GetName()
{
return _name;
}
// Returns the value of the stock
public decimal GetValue()
{
return _value;
}
// Throws an exception, since stocks cannot contain other assets
public void Add(IAsset asset)
{
throw new InvalidOperationException("Cannot add asset to stock");
}
// Throws an exception, since stocks cannot contain other assets
public void Remove(IAsset asset)
{
throw new InvalidOperationException("Cannot remove asset from stock");
}
}
// A bond is a simple asset that has a name and a value
public class Bond : IAsset
{
private readonly string _name;
private readonly decimal _value;
// Creates a new bond with the given name and value
public Bond(string name, decimal value)
{
_name = name;
_value = value;
}
// Returns the name of the bond
public string GetName()
{
return _name;
}
// Returns the value of the bond
public decimal GetValue()
{
return _value;
}
// Throws an exception, since bonds cannot contain other assets
public void Add(IAsset asset)
{
throw new InvalidOperationException("Cannot add asset to bond");
}
// Throws an exception, since bonds cannot contain other assets
public void Remove(IAsset asset)
{
throw new InvalidOperationException("Cannot remove asset from bond");
}
}Here is how we can define the composite class for funds:// A fund is a complex asset that can contain other assets as its components
public class Fund : IAsset
{
private readonly string _name;
// A list of assets that belong to this fund
private readonly List<IAsset> _assets;
// Creates a new fund with the given name and an empty list of assets
public Fund(string name)
{
_name = name;
_assets = new List<IAsset>();
}
// Returns the name of the fund
public string GetName()
{
return _name;
}
// Returns the value of the fund, which is the sum of the values of its components
public decimal GetValue()
{
decimal value = 0;
foreach (IAsset asset in _assets)
{
value += asset.GetValue();
}
return value;
}
// Adds a new asset to the fund
public void Add(IAsset asset)
{
_assets.Add(asset);
}
// Removes an existing asset from the fund
public void Remove(IAsset asset)
{
_assets.Remove(asset);
}
// Returns the list of assets that belong to this fund
public List<IAsset> GetAssets()
{
return _assets;
}
}Now we can create some assets and add them to a portfolio, which is also a fund:// Creates some assets
IAsset apple = new Stock("Apple", 150);
IAsset google = new Stock("Google", 200);
IAsset tesla = new Stock("Tesla", 250);
IAsset bond1 = new Bond("Bond1", 100);
IAsset bond2 = new Bond("Bond2", 120);
// Creates a fund that contains some stocks and bonds
IAsset fund1 = new Fund("Fund1");
fund1.Add(apple);
fund1.Add(google);
fund1.Add(bond1);
// Creates another fund that contains some stocks and bonds
IAsset fund2 = new Fund("Fund2");
fund2.Add(tesla);
fund2.Add(bond2);
// Creates a portfolio that contains both funds
IAsset portfolio = new Fund("Portfolio");
portfolio.Add(fund1);
portfolio.Add(fund2);We can now use the same component interface to work with any asset in the portfolio, without knowing its concrete type:// Prints the name and value of each asset in the portfolio
Console.WriteLine($"Name: {portfolio.GetName()}, Value: {portfolio.GetValue()}");
foreach (IAsset asset in ((Fund)portfolio).GetAssets())
{
Console.WriteLine($"Name: {asset.GetName()}, Value: {asset.GetValue()}");
}Output:Name: Portfolio, Value: 820 Name: Fund1, Value: 450 Name: Fund2, Value: 370We can also modify the portfolio by adding or removing assets:// Removes Google stock from Fund1
fund1.Remove(google);
// Adds a new bond to Fund2
IAsset bond3 = new Bond("Bond3", 140);
fund2.Add(bond3);
// Prints the updated value of the portfolio
Console.WriteLine($"Name: {portfolio.GetName()}, Value: {portfolio.GetValue()}");Output:Name: Portfolio, Value: 760As you can see, the composite pattern allows us to manipulate complex structures of objects as if they were individual objects. This simplifies the code and makes it more flexible and maintainable. The composite pattern is widely used in many domains, such as graphics, user interfaces, file systems, and more. 😍
If you want to learn more about the composite pattern and other design patterns in C#, you can check out these resources:
- [Design Patterns in C#]: This is a comprehensive book that covers all the design patterns in C#, with detailed explanations and examples.
- [Composite Pattern Tutorial]: This is a video tutorial that explains the composite pattern in C#, with a different example than this article.
- [Composite Pattern Implementation]: This is a GitHub repository that contains the source code for this article, as well as some tests.
I hope you found this article helpful and interesting. If you have any questions or feedback, please leave a comment below.
You can find the code examples here: source code
Thank you for reading! 😊
Comments
Post a Comment