Let's start with a common term used with a class association called 'Coupling'.
Class Coupling
- It quantifies the level of interconnectedness among classes and subsystems.
- Tight coupling between classes makes them more difficult to modify, as changes in one class can cascade across many others.
- Loosely coupled software allows for easier modifications compared to tightly coupled software
- Classes exhibit two main types of relationships: Inheritance and Composition.
Inheritance
- It's when one class can use code from another.
- For example, saying a Car is a type of Vehicle.
- This helps reuse code and make objects behave differently based on their types.
Usage
public class Car : Vehicle
{
}
Example
Below is a Presentation class object. Text, Table, and Image classes are child classes/ derived from the base Presentation class
class Presentation
{
public string Title { get; }
}
class Text : Presentation
{
}
class Image : Presentation
{
}
class Table : Presentation
{
}
Composition
- It's when one class has another class inside it.
- For instance, a Car might have an Engine.
- The good things about Composition are that it helps reuse code, makes things flexible, and keeps them loosely connected.
Example
class Logger
{
// Method to log a message
public void Log(string message) => Console.WriteLine($"Logging: {message}");
}
class Installer
{
private readonly Logger _logger;
// Constructor to initialize Installer with a Logger instance
public Installer(Logger logger) => _logger = logger;
// Method to perform installation
public void Install()
{
_logger.Log("Installing...");
// Installation logic here
_logger.Log("Installation complete.");
}
}
class DbMigrator
{
private readonly Logger _logger;
// Constructor to initialize DbMigrator with a Logger instance
public DbMigrator(Logger logger) => _logger = logger;
// Method to perform database migration
public void Migrate()
{
_logger.Log("Migrating database...");
// Database migration logic here
_logger.Log("Migration complete.");
}
}
Composition or Inheritance. Which one to choose?
Let's consider an example to understand more.
Suppose we have a Shape class representing various shapes such as circles, squares, and triangles. We then create subclasses Circle, Square, and Triangle that inherit from the Shape class.
class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing a generic shape.");
}
}
class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle.");
}
}
class Square : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a square.");
}
}
class Triangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a triangle.");
}
}
Now, let's say we want to introduce a new shape called Rectangle. We decided to create a new subclass Rectangle that inherits from the Shape class.
class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a rectangle.");
}
}
However, what if later we realize that a rectangle has different properties compared to other shapes? For example, rectangles have a width and height, while other shapes do not.
class Rectangle : Shape
{
public int Width { get; set; }
public int Height { get; set; }
public override void Draw()
{
Console.WriteLine("Drawing a rectangle.");
}
}
In this situation, opting for inheritance from the Shape class might not be ideal due to the inconsistency in properties among different shapes.
A more effective strategy would involve employing composition over inheritance. By creating a Rectangle class that encompasses a Shape object rather than inheriting from it, we can better capture the relationship between the entities. This approach ensures a more accurate representation and mitigates the risk of encountering inheritance-related issues.
To solve the problem using composition instead of inheritance, we can create a Rectangle class that contains a Shape object as a member. This way, we can leverage the functionality of the Shape class without inheriting it directly. Here's how we can implement it.
using System;
class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing a generic shape.");
}
}
class Rectangle
{
private readonly Shape _shape;
public Rectangle(Shape shape)
{
_shape = shape;
}
public void Draw()
{
Console.WriteLine("Drawing a rectangle.");
_shape.Draw(); // Delegate drawing the generic shape to the Shape object
}
}
class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle.");
}
}
class Square : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a square.");
}
}
class Triangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a triangle.");
}
}
class Program
{
static void Main()
{
var circle = new Circle();
var square = new Square();
var triangle = new Triangle();
var rectangleWithCircle = new Rectangle(circle);
var rectangleWithSquare = new Rectangle(square);
var rectangleWithTriangle = new Rectangle(triangle);
rectangleWithCircle.Draw(); // Drawing a rectangle, Drawing a circle
rectangleWithSquare.Draw(); // Drawing a rectangle, Drawing a square
rectangleWithTriangle.Draw(); // Drawing a rectangle, Drawing a triangle
}
}
I hope this article will help you in understanding various associations between classes while designing. Wishing you successful coding!