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#:
|
|
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:
|
|
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.