Navigation

Sunday, 15 December 2024

The Factory Design Pattern in C#: With Practical Example.

 The Factory Design Pattern is one of the most commonly used patterns in software development. It provides a way to create objects without exposing the instantiation logic to the client and often involves a factory method or class responsible for the creation. This approach enhances code maintainability, reduces redundancy, and promotes the principle of single responsibility.

In this blog, we’ll explore the factory design pattern with a practical C# example, where we implement a PersonFactory to generate instances of a Person class. Along the way, we’ll discuss its significance and best practices.

Saturday, 16 November 2024

The Dependency Inversion Principle of SOLID in C#: Understanding with a Practical Example

The Dependency Inversion Principle (DIP) is a cornerstone of the SOLID principles, guiding developers to write flexible and maintainable code. It emphasizes decoupling by making high-level modules independent of low-level modules. Instead, both should rely on abstractions.


What is the Dependency Inversion Principle?

The Dependency Inversion Principle states:
1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
2. Abstractions should not depend on details. Details should depend on abstractions.

This may sound complex, but it’s essentially about reducing dependencies in your code by introducing abstractions (like interfaces or abstract classes).


Real-World Analogy

Imagine you’re setting up a home theater system.

  • High-level module: The remote control.
  • Low-level module: The television and speakers.

Instead of the remote being directly designed to work with one TV or speaker model, you program it to send universal signals. The TV and speakers are designed to interpret these signals. If you change your TV or speakers, the remote still works, because it’s not tied to any specific brand or model.


A Simple Programming Example

Let’s build an example where a NotificationService sends messages. It could send notifications through email, SMS, or other methods.

Problem Without DIP

In a tightly coupled design, the NotificationService directly depends on the concrete implementation of the EmailSender class:

   
public class EmailSender
    {
        public void SendEmail(string message)
        {
            Console.WriteLine($"Sending email: {message}");
        }
    }

    public class NotificationService
    {
        private EmailSender emailSender;

        public NotificationService()
        {
            emailSender = new EmailSender();
        }

        public void Send(string message)
        {
            emailSender.SendEmail(message);
        }
    }

Here’s what’s wrong:

  • The NotificationService depends on the EmailSender class.
  • If we want to add SMS notifications, we must modify NotificationService, breaking its existing code.

Solution With DIP

We fix this by introducing an abstraction—a INotificationSender interface. The NotificationService will depend on this interface rather than a concrete implementation.

   
public interface INotificationSender
    {
        void Send(string message);
    }

    public class EmailSender : INotificationSender
    {
        public void Send(string message)
        {
            Console.WriteLine($"Sending email: {message}");
        }
    }

    public class SmsSender : INotificationSender
    {
        public void Send(string message)
        {
            Console.WriteLine($"Sending SMS: {message}");
        }
    }

    public class NotificationService
    {
        private INotificationSender notificationSender;

        public NotificationService(INotificationSender notificationSender)
        {
            this.notificationSender = notificationSender;
        }

        public void Notify(string message)
        {
            notificationSender.Send(message);
        }
    }

Now, we can use the NotificationService with any notification sender:

   
class Program
    {
        static void Main()
        {
            var emailSender = new EmailSender();
            var smsSender = new SmsSender();

            var emailNotificationService = new NotificationService(emailSender);
            emailNotificationService.Notify("Email: Hello, World!");

            var smsNotificationService = new NotificationService(smsSender);
            smsNotificationService.Notify("SMS: Hello, World!");
        }
    }


Why Is This Better?

  1. Decoupling: The NotificationService no longer depends on specific implementations like EmailSender. It relies on the INotificationSender abstraction, which allows greater flexibility.
  2. Extensibility: Adding a new notification method (e.g., push notifications) doesn’t require changing the NotificationService. Simply create a new class implementing INotificationSender.
  3. Testability: We can mock the INotificationSender interface during unit testing to simulate different scenarios.

Key Takeaways

  • Abstractions are the glue: Always design high-level modules to depend on abstractions, not details.
  • Flexibility matters: Following DIP ensures that changes in one part of the system don’t ripple through other parts.
  • Maintainability improves: You can extend functionality without breaking existing code.

Conclusion

The Dependency Inversion Principle simplifies software design by decoupling high-level and low-level modules using abstractions. This makes your code easier to maintain, extend, and test. As demonstrated in our example, adhering to DIP results in cleaner, more modular code that adapts to change effortlessly.

I hope this blog helps you understand how to use DIP effectively in C#. If you have any questions or feedback, feel free to leave a comment below!

The Interface Segregation Principle of SOLID in C#: Simplified with Examples

The Interface Segregation Principle (ISP) is one of the five SOLID principles of object-oriented programming, aimed at creating a robust and maintainable software design. Let’s break it down into simple terms and understand how it improves software development.

Understanding Deep Copy in C# with Classes Point and Line


In object-oriented programming, a common task involves creating a copy of an object. However, it's essential to distinguish between a shallow copy and a deep copy. In this post, we'll explore deep copying through a practical example, diving into the code to see how deep copying works with a custom Point and Line class in C#.

Shallow vs. Deep Copy

  1. Shallow Copy: A shallow copy of an object is a new object instance but contains references to the same objects within it. Changes made to the referenced objects in the copy reflect in the original object.

  2. Deep Copy: A deep copy creates a new object instance and also duplicates the objects referenced by it. As a result, modifying the deep-copied object does not affect the original.

Implementing Deep Copy with Point and Line

In our code, we have two classes, Point and Line, which demonstrate deep copying. Let’s break down the code.


public class Point
{
    public int X, Y;

    public Point DeepCopy()
    {
        return new Point { X = this.X, Y = this.Y };
    }

    public override string ToString()
    {
        return $"({X}, {Y})";
    }
}


The Point class has two integer properties, X and Y. It also has a DeepCopy method, which returns a new Point object with the same values for X and Y.

Line Class

The Line class contains two Point properties, Start and End, representing the start and end points of the line. In the Line class, we define a DeepCopy method to create a new Line with separate Start and End Point objects.


public class Line
{
    public Point Start, End;

    public Line DeepCopy()
    {
        return new Line
        {
            Start = this.Start.DeepCopy(),
            End = this.End.DeepCopy()
        };
    }

    public override string ToString()
    {
        return $"Start: {Start}, End: {End}";
    }
}


The DeepCopy method in the Line class uses the DeepCopy method in the Point class. This way, Start and End get new Point instances, ensuring that the copied line is independent of the original.

Testing Deep Copy in Main

The Program class demonstrates how DeepCopy works in practice. Here, line1 is our original line with specific start and end points.

var line1 = new Line
{
    Start = new Point { X = 1, Y = 2 },
    End = new Point { X = 3, Y = 4 }
};

When we create a deep copy of line1 with line1.DeepCopy(), we get a new Line object, line2. Changing the coordinates in line2 does not affect line1, showing that both lines are independent objects.



    var line2 = line1.DeepCopy();
    line2.Start.X = 5;
    line2.Start.Y = 6;


Complete Code:


    using System;

    namespace Coding.Exercise
    {
        public class Point
        {
            public int X, Y;

            public Point DeepCopy()
            {
                return new Point { X = this.X, Y = this.Y };
            }

            public override string ToString()
            {
                return $"({X}, {Y})";
            }
        }

        public class Line
        {
            public Point Start, End;

            public Line DeepCopy()
            {
                return new Line
                {
                    Start = this.Start.DeepCopy(),
                    End = this.End.DeepCopy()
                };
            }

            public override string ToString()
            {
                return $"Start: {Start}, End: {End}";
            }
        }

        public class Program
        {
            public static void Main()
            {
                var line1 = new Line
                {
                    Start = new Point { X = 1, Y = 2 },
                    End = new Point { X = 3, Y = 4 }
                };

                var line2 = line1.DeepCopy();
                line2.Start.X = 5;
                line2.Start.Y = 6;

                Console.WriteLine("Original Line: " + line1);
                Console.WriteLine("Copied Line: " + line2);
            }
        }
    }



Output:


Original Line: Start: (1, 2), End: (3, 4)
Copied Line: Start: (5, 6), End: (3, 4)

The original line (line1) remains unchanged, while the copied line (line2) has the modified Start coordinates, confirming that a true deep copy was made.

Conclusion

The DeepCopy method is essential when working with objects that reference other objects, helping to create fully independent copies. This example highlights how to implement deep copying for a class that contains references to other objects, ensuring data integrity and independence.

I hope this blog helps you understand how to use DeepCopy effectively in C#. If you have any questions or feedback, feel free to leave a comment below!

Monday, 11 November 2024

Creating a Dynamic Code Builder in C#: A Fluent API Approach

In software development, creating repetitive classes or structures can be tedious. Imagine if we could dynamically generate boilerplate code through an elegant and straightforward approach. In this blog, we will explore a simple implementation in C# using a class called CodeBuilder to generate class definitions on the fly. This approach uses the Builder Pattern to provide a fluent and intuitive API.


Why Use a Code Builder?

When working on large projects, developers often find themselves needing to create multiple classes with similar structures. A CodeBuilder helps reduce redundancy, saves time, and keeps the codebase clean and maintainable. This builder not only allows for easy creation of class definitions but also enhances code readability.

Code Walkthrough

Let's break down the CodeBuilder implementation step by step.


// Main class with entry point
using ConsoleAppForPattern;
using System.Text;

class Program
{


    public class CodeBuilder
    {
        private readonly string _className;
        private readonly List<Tuple<string, string>> _fields = new List<Tuple<string, string>>();

        public CodeBuilder(string className)
        {
            _className = className;
        }

        public CodeBuilder AddField(string fieldName, string fieldType)
        {
            _fields.Add(new Tuple<string, string>(fieldType, fieldName));
            return this;
        }

        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.AppendLine($"public class {_className}");
            sb.AppendLine("{");

            foreach (var field in _fields)
            {
                sb.AppendLine($"  public {field.Item1} {field.Item2};");
            }

            sb.AppendLine("}");
            return sb.ToString();
        }

    }

    static void Main(string[] args)
    {
        var cb = new CodeBuilder("Person").AddField("Name", "string")
                                          .AddField("Age", "int");
        Console.WriteLine(cb);      
    }

}



Understanding the CodeBuilder Class

  1. Class Fields

    • _className stores the name of the class being built.

    • _fields is a list that holds tuples representing field types and names.

  2. Constructor

    • The constructor accepts the class name as a parameter, initializing _className.

  3. AddField Method

    • AddField(string fieldName, string fieldType) adds a new field to the class by adding a tuple to the _fields list.

    • The method returns this to enable fluent chaining of multiple calls.

  4. ToString Method

    • This method constructs the final class definition using StringBuilder. It iterates through the fields and generates their respective declarations.

Usage Example

In the Main method, we create an instance of CodeBuilder to generate a class named Person with fields Name (of type string) and Age (of type int).

 
static void Main(string[] args)
    {
        var cb = new CodeBuilder("Person").AddField("Name", "string").AddField("Age", "int");
        Console.WriteLine(cb);      
    }


The output will be:

public class Person
{
  public string Name;
  public int Age;
}


Benefits of Using CodeBuilder

  • Fluent Interface: The AddField method returns the instance of CodeBuilder, allowing us to chain calls and build the class definition in a natural, readable way.

  • Reduced Repetition: This approach eliminates repetitive boilerplate code and keeps the focus on defining what's unique about each class.

  • Easy Maintenance: If fields need to be added or changed, it is straightforward to update the CodeBuilder call without modifying the entire class structure manually.


Practical Applications

The CodeBuilder class can be particularly useful in scenarios where class definitions are dynamic or driven by external configurations (e.g., JSON schemas or database structures). It is a handy tool for prototyping or creating Domain-Specific Languages (DSLs) for code generation.

Conclusion

The CodeBuilder class is a simple yet powerful demonstration of the Builder Pattern, providing an easy way to dynamically generate class definitions in C#. By leveraging fluent interfaces, it makes the process intuitive and highly maintainable. Whether you're dealing with dynamic structures or simply want to reduce boilerplate, this approach can significantly enhance your productivity as a developer.

Feel free to expand upon this implementation by adding methods to generate properties instead of fields, or by introducing additional modifiers like private or readonly to make it more versatile.


Saturday, 2 November 2024

Liskov Substitution Principle (LSP) of SOLID in C#

 

Introduction

The Liskov Substitution Principle (LSP) is one of the five SOLID principles of object-oriented design, which aims to create flexible, extensible, and maintainable software. LSP is named after Barbara Liskov, a computer scientist who introduced this principle in the 1980s. This blog will walk you through the LSP concept in C# with code examples using a console application.


What is the Liskov Substitution Principle?

The Liskov Substitution Principle states that: "Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program."

In simpler terms, if you have a base class, you should be able to replace it with any of its derived classes without the code misbehaving. This keeps your software flexible and ensures that substituting derived classes doesn't introduce unexpected bugs or inconsistencies.

Why LSP Matters

Adhering to LSP provides multiple benefits:

  • Predictable Code Behavior: Your code behaves consistently regardless of which subclass is being used.
  • Scalability: It’s easier to extend functionality with new classes without breaking existing code.
  • Reduced Bugs: Violating LSP can lead to unexpected behavior or errors, so following it helps make code reliable and maintainable.

Friday, 1 November 2024

The Single Responsibility Principle (SRP) of SOLID in C#

 

What is the Single Responsibility Principle (SRP)?

  • Definition of SRP: “A class should have only one reason to change.”
  • Purpose of SRP: Explain how SRP aims to limit each class to a single responsibility to avoid coupling multiple roles into a single class.
  • Benefits of SRP: Mention maintainability, flexibility, scalability, and testability.

Example Scenario for SRP in a Console Application

  • Context: Describe a scenario where SRP might be applied, such as a simple console application to manage employee data.

  • Initial Code Without SRP: Show an example of a single class handling multiple responsibilities, like creating an employee, logging data, and sending a notification.


namespace ConsoleAppForPattern
{
    internal class EmpManager
    {
        public void AddEmployee(string name)
        {
            Console.WriteLine($"Employee {name} added.");
            LogEmployeeAddition(name);
            SendWelcomeEmail(name);
        }

        private void LogEmployeeAddition(string name)
        {
            Console.WriteLine($"Log: Employee {name} was added to the system.");
        }

        private void SendWelcomeEmail(string name)
        {
            Console.WriteLine($"Email: Welcome {name} to the company!");
        }
    }
}

  • Issues with this Code: Explain how the EmployeeManager class violates SRP by handling employee creation, logging, and email notifications in one place. Each of these tasks could change for different reasons, so they should be separated.