There are two kinds of relationships between classes:
- Inheritance (is-a)
- Composition (has-a)
This post is based on an instructional video by Mosh Hamedani at Udemy.com. Suppose we are building an application that has many parts to it. One of the parts, a logger, is responsible for logging information to a text file. We have two other parts of the application that need to use the logger. We have a DbMigrator and we have an Installer. Both of these have a relationship to the logger class. They both use it. This is nothing new here in C#. We don’t need any special syntax like we need with inheritance. One class contains another. We can say that a car has an engine. The engine is inside the car.
Generally, developers favour composition over inheritance. For an example of inheritance we have the post called C# Interfaces Extensibility.
With composition we get code re-use, as we get with inheritance. Composition is more flexible and a means to designing loosely coupled applications. Here is a UML diagram showing our example project.
First, here is our logger code. It just has a method that writes to the console so we can see that it was executed.
class Logger { public void Log(string message) { Console.WriteLine("Log it: " + message); } }
Here is the Installer. Notice that we have a constructior that takes a Logger.
class Installer { private readonly Logger _logger; public Installer(Logger logger) { _logger = logger; } public void Install() { _logger.Log("installing..."); } }
Here is the DbMigrator.
class DbMigrator { private readonly Logger _logger; public DbMigrator(Logger logger) { _logger = logger; } public void Migrate() { _logger.Log("we are migrating..."); } }
Below is our program. Notice that the first three lines of code create a dbMigrator and an Installer. In the first of the three lines, we create a logger object and pass it all in one line of code. In the second line of code, we create a new Logger object called logger. In the third line, we pass the logger object.
namespace Composition { class Program { // Mosh Hamedani at Udemy.com static void Main(string[] args) { var dbMigrator = new DbMigrator(new Logger()); var logger = new Logger(); var installer = new Installer(logger); dbMigrator.Migrate(); installer.Install(); } } }
A Private Field and a Constructor
In our two classes that use the logger, they each have a private field that contains the logger. That field is of the type of our class: Logger. To say it again, they have a private field that contains the custom class logger. Also, they each have a constructor that we wrote. The constructor requires a logger object that is used to initialize the private field. The Logger is a private field in the Installer and the DbMigrator.
The related class is a private filed in the composite class.
using System; namespace Composition { class Program { // Mosh Hamedani at Udemy.com - code has been modified. static void Main(string[] args) { var logger = new Logger(); logger.dateTime = DateTime.Now; // current date and time var dbMigrator = new DbMigrator(logger); dbMigrator.Migrate(); logger.dateTime = DateTime.Now; // current date and time var installer = new Installer(logger); installer.Install("programA"); logger.dateTime = DateTime.Now; // current date and time installer.Location = "D:\\Program Files"; // to D: not C: installer.Install("programB"); } } }