Composite
A mechanism for treating individual objects and certain compositions of objects in a uniform manner(same api).
Motivation
- Make compound objects.
- Objects use other object's fields/properties/members through inheritance and composition.
- Treat both single and composite objects uniformly.
Geometric Shapes
To make single object and grouped objects having same behaviors, we set a collection of the same type(List<Graphic> children
), and then apply recursive actions on them(void Display()
). The side effect is that it may result in a reference loop, when displaying the group finally got a overflow. We shall have a reference checking before we add any child, but we are not going to talk about that.
class Graphic
{
public Color Color { get; set; }
public string Name => this.GetType().Name;
protected readonly Lazy<List<Graphic>> children = new();
public void AddChildren(params Graphic[] child) => child.ToList().ForEach(children.Value.Add);
public void Display()
{
Console.WriteLine($"Displaying {Name} object with color {Color.Name.ToLower()}");
children.Value.ForEach(x => x?.Display());
}
}
class Rectangle : Graphic { }
class Circle : Graphic { }
public class GroupingTest
{
[Fact]
public void Test()
{
Rectangle rectangle = new() { Color = Color.Aqua };
Circle circle = new() { Color = Color.Orange };
Graphic graphic = new() { Color = Color.White };
Graphic graphicAnother = new() { Color = Color.Black };
graphicAnother.AddChildren(circle, rectangle);
graphic.AddChildren(rectangle, circle, graphicAnother);
graphic.Display();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Neutral Networks
The key of Composite
pattern is treating single object as a group. Neuron is the minimal unit of Neural Network, Neural Layer are made of several Neurons, Neural Network are made of Neural Layers. So when implementing Composite
pattern, we treat them all as a collection(group), so we make Neuron
as IEnumerable<Neuron>
, NeuronLayer
as Collection<Neuron>
. So all of them are IEnumerable<Neuron>
. So now we can define some common functionalities using extension methods.
class Neuron : IEnumerable<Neuron>
{
public required Half Value { get; set; }
internal List<Neuron> In { get; } = new();
internal List<Neuron> Out { get; } = new();
public IEnumerator<Neuron> GetEnumerator()
{
yield return this;
}
public void ConnectTo(Neuron other)
{
other.In.Add(this);
this.Out.Add(other);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
class NeuralLayer : Collection<Neuron> { }
class NeuralNetwork : Collection<NeuralLayer> { }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
We hope Neuron
, NeuralLayer
, NeuralNetwork
have a same functionality like void ConnectTo()
to connect whatever a Neuron
to NeuralLayer
or NeuralNetwork
, and so forth.
So we make an extension method.
static class NeuronExtension
{
public static void ConnectTo(this IEnumerable<Neuron> from, IEnumerable<Neuron> to)
{
foreach (var f in from)
foreach (var t in to)
{
f.Out.Add(t);
t.In.Add(f);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
Now any neuron, any layer, any network can be connected to each other like there is no difference among them. This is the composite pattern.
Neuron neuron1 = new() { Value = (Half)1 };
Neuron neuron2 = new() { Value = (Half)2 };
NeuralLayer layer1 = new();
NeuralNetwork net = new();
neuron1.ConnectTo(neuron2);
neuron2.ConnectTo(layer1);
layer1.ConnectTo(net);
2
3
4
5
6
7