A building with orange and blue parts

Object composition with C#

Object composition is a design principle in which one class is composed of one or more objects of other classes.

It allows modularity in a class design and make it reusable, promoting the “has-a” relationship rather than an “is-a” relationship that inheritance implies.

Here’s a simple example of object composition in C#:

 1
 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using System;

namespace ObjectCompositionExample
{
    // Engine class
    public class Engine
    {
        public void Start()
        {
            Console.WriteLine("Engine started.");
        }

        public void Stop()
        {
            Console.WriteLine("Engine stopped.");
        }
    }

    // Radio class
    public class Radio
    {
        public void TurnOn()
        {
            Console.WriteLine("Radio is on.");
        }

        public void TurnOff()
        {
            Console.WriteLine("Radio is off.");
        }
    }

    // Car class that is composed of Engine and Radio
    public class Car
    {
        private Engine _engine;
        private Radio _radio;

        public Car()
        {
            // Engine and Radio start to exist only
            //  when the Car is created
            _engine = new Engine();
            _radio = new Radio();
        }

        public void Start()
        {
            _engine.Start();
            _radio.TurnOn();
            Console.WriteLine("Car started.");
        }

        public void Stop()
        {
            _radio.TurnOff();
            _engine.Stop();
            Console.WriteLine("Car stopped.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Car myCar = new Car();
            myCar.Start();
            myCar.Stop();
        }
    }
}

Engine Class represents the engine of a car. It has methods to start and stop the engine. Radio Class represents the radio in a car. It has methods to turn on and turn off the radio. Car Class represents a car and uses composition to include a Engine and a Radio. It has methods to start and stop the car, which internally starts/stops the engine and turns on/off the radio.

In this example, the Car class is composed of Engine and Radio objects, demonstrating how object composition allows a class to use the functionality of other classes to perform its duties. This approach provides flexibility and promotes code reuse.

Alternative with Loose Coupling of Car from Engine and Radio

The previous is OK, but best practices recommend this tight coupling between the classes Car, Engine and Radio

To achieve loose coupling, we can use interfaces to define the behavior expected from the Engine and Radio classes.

This way, the Car class doesn’t depend on the concrete implementations of Engine and Radio classes but rather on their interfaces. This promotes loose coupling and makes the system more flexible and easier to extend, modify or test.

Here is an example using interfaces for loose coupling:

 1
 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
using System;

namespace LooseCouplingExample
{
    // Engine interface
    public interface IEngine
    {
        void Start();
        void Stop();
    }

    // Radio interface
    public interface IRadio
    {
        void TurnOn();
        void TurnOff();
    }

    // Concrete implementation of IEngine
    public class Engine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("Engine started.");
        }

        public void Stop()
        {
            Console.WriteLine("Engine stopped.");
        }
    }

    // Concrete implementation of IRadio
    public class Radio : IRadio
    {
        public void TurnOn()
        {
            Console.WriteLine("Radio is on.");
        }

        public void TurnOff()
        {
            Console.WriteLine("Radio is off.");
        }
    }

    // Car class that uses IEngine and IRadio
    public class Car
    {
        private readonly IEngine _engine;
        private readonly IRadio _radio;

        // This is also called dependency injection
        public Car(IEngine engine, IRadio radio)
        {
            _engine = engine;
            _radio = radio;
        }

        public void Start()
        {
            _engine.Start();
            _radio.TurnOn();
            Console.WriteLine("Car started.");
        }

        public void Stop()
        {
            _radio.TurnOff();
            _engine.Stop();
            Console.WriteLine("Car stopped.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            IEngine engine = new Engine();
            IRadio radio = new Radio();

            Car myCar = new Car(engine, radio);
            myCar.Start();
            myCar.Stop();
        }
    }
}

Interfaces IEngine and IRadio define the behavior that the Engine and Radio classes must implement. This ensures that the Car class interacts with these interfaces, not the concrete implementations.

Concrete Implementations of Engine and Radio implement the IEngine and IRadio interfaces. They provide the actual behavior for starting/stopping the engine and turning on/off the radio.

Car Class uses IEngine and IRadio interfaces instead of the concrete Engine and Radio classes, consequently decoupling the Car class from specific implementations and allows for any implementation of IEngine and IRadio to be used.

In the Program Class, we now create instances of Engine and Radio, pass them to the Car constructor, and start/stop the car.

The output remains the same, but the code has become much more scalable. For example, you could create mock implementations for testing purposes or swap in different engines or radio types as needed.

Follow me

Thanks for reading this article. Make sure to follow me on X, subscribe to my Substack publication and bookmark my blog to read more in the future.

Photo by Nothing Ahead.

License GPLv3 | Terms
Built with Hugo
Theme Stack designed by Jimmy