Real Time Examples Of Factory Design Patterns In C#

Real-Time C# Factory Design Pattern Example: Integration of Payment Gateway :

Example

using System;
namespace FactoryDesignPattern
{
    //Define the Product Interface
    public interface IPaymentGateway
    {
        void ProcessPayment(decimal amount);
    }

    //Concrete Implementations for the Products
    public class PayPalGateway : IPaymentGateway
    {
        public void ProcessPayment(decimal amount)
        {
            Console.WriteLine($"Processing ${amount} payment through PayPal...");
            // Actual integration and logic for PayPal
        }
    }

    public class StripeGateway : IPaymentGateway
    {
        public void ProcessPayment(decimal amount)
        {
            Console.WriteLine($"Processing ${amount} payment through Stripe...");
            // Actual integration and logic for Stripe
        }
    }
    //Factory Class to Produce the Products
    
            switch (gatewayName.ToLower())
            {
                case "paypal":
                    return new PayPalGateway();
                case "stripe":
                    return new StripeGateway();
                case "creditcard":
                    return new CreditCardGateway();
                default:
                    throw new ArgumentException("Invalid payment gateway specified");
            }
        }
    }
    
    // Testing the Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Select the payment gateway (PayPal, Stripe, CreditCard): ");
            string gatewayName = Console.ReadLine();
            catch (ArgumentException ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

With this architecture, adding new payment methods to the e-commerce system is simple. Let's say a new payment system like Bitcoin is released later on. If so, you can modify the PaymentGatewayFactory and create a new BitcoinGateway class without changing the current client code. The Factory Design Pattern ensures scalability and maintainability in such settings by encapsulating the creation process. The following output will appear when you run the code mentioned above.

Output:

Real-Time Example of Factory Design Pattern in C#: Document Conversion System

Imagine a programme that enables users to convert documents between multiple file types, such as TXT, PDF, and DOCX. The device will use the specific converter that the user asks.

Example

using System;
namespace FactoryDesignPattern
{
    //Define the Product Interface
    public interface IDocumentConverter
    {
        string Convert(string content);
        string TargetExtension { get; }
    }

    //Concrete Implementations for the Products
    public class DocxConverter : IDocumentConverter
    {
        public string Convert(string content)
        {
            Console.WriteLine("Converting content to DOCX format...");
            // Logic for DOCX conversion, simplified for this example
            return content + " [Converted to DOCX]";
        }

        public string TargetExtension => ".docx";
    }

    public class PdfConverter : IDocumentConverter
    {
        public string Convert(string content)
        {
            Console.WriteLine("Converting content to PDF format...");
            // Logic for PDF conversion, simplified for this example
            return content + " [Converted to PDF]";
        }

        public string TargetExtension => ".pdf";
    }

    // Testing the Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Enter the content to convert:");
            string content = Console.ReadLine();

            Console.WriteLine("Select the target format (DOCX, PDF, TXT):");
            string format = Console.ReadLine();

            try
            {
                IDocumentConverter converter = DocumentConverterFactory.CreateDocumentConverter(format);
                string convertedContent = converter.Convert(content);
                Console.WriteLine($"Converted content ({converter.TargetExtension}): {convertedContent}");
            }
            catch (ArgumentException ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadKey();
        }
    }
}

Thanks to its design, the system can easily support new document formats. The essential logic would remain unchanged if a new format-let's say RTF-was later released; all that would need to be done would be to implement a new RtfConverter class and update the DocumentConverterFactory. The Factory Design Pattern facilitates the expansion or modification of functionalities by ensuring modularity and the separation of concerns. The following output will appear when you run the code mentioned above.

Output:

Real-Time Example of Factory Design Pattern in C#: Logging System

Let's look at an example of a logging system in real-time. A lot of applications have to log messages to files, consoles, or distant servers. We can quickly switch between several logging schemes and separate the client code from particular logger implementations by utilizing the Factory Design Pattern.

Example

using System;

namespace FactoryDesignPattern
{
    //Define the Product Interface
    public interface ILogger
    {
        void Log(string message);
    }

    //Concrete Implementations for the Products
    public class ConsoleLogger : ILogger
    {
        public void Log(string message)
        {
            Console.WriteLine("Console: " + message);
        }
    }

    public class FileLogger : ILogger
    {
        private string _filePath;

        public FileLogger(string filePath)
        {
            _filePath = filePath;
        }

        public void Log(string message)
        {
            File.AppendAllText(_filePath, "File: " + message + Environment.NewLine);
        }
    }

    // You can also add other loggers like DatabaseLogger, CloudLogger, etc.

    //Factory Class to Produce the Products
    public static class LoggerFactory
    {
        public static ILogger CreateLogger(string type)
        {
            switch (type.ToLower())
            {
                case "console":
                    return new ConsoleLogger();
                case "file":
                    return new FileLogger("log.txt"); 
                // For simplicity, file path is hardcoded
                // You can extend here with other logger types
                default:
                    throw new ArgumentException("Invalid logger type");
            }
        }
    }
    
    // Testing the Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            ILogger logger;

            logger = LoggerFactory.CreateLogger("console");
            logger.Log("This is a console log!");

            logger = LoggerFactory.CreateLogger("file");
            logger.Log("This message is written to a file.");

            // The beauty of this approach is that the client code remains unchanged
            // even if we introduce new logger types in the future.

            Console.ReadKey();
        }
    }
}

Here, we've demonstrated how to quickly switch between logging schemes without modifying the client code by utilizing the Factory Design Pattern. Let's say that later on, a new requirement-like logging into a database-appears. The client code and current logger implementations may then be left alone as we expand our LoggerFactory to accommodate the new logger type.

Real-Time Example of Factory Design Pattern in C#: A Simple System to Handle Notifications

Let's examine an alternative real-time scenario: a basic notification system. Under this situation, an application could have to notify users by push, SMS, or email notifications, among other formats.

Example

using System;
namespace FactoryDesignPattern
{
    //Define the Product Interface
    public interface INotificationSender
    {
        void SendNotification(string message);
    }

    //Concrete Implementations for the Products
    public class EmailNotification : INotificationSender
    {
        public void SendNotification(string message)
        {
            Console.WriteLine($"Sending EMAIL notification: {message}");
            // Logic to send email here...
        }
    }

    public class PushNotification : INotificationSender
    {
        public void SendNotification(string message)
        {
            Console.WriteLine($"Sending PUSH notification: {message}");
            // Logic to send push notification here...
        }
    }

    //Factory Class to Produce the Products
    public static class NotificationFactory
    {
        public static INotificationSender CreateNotificationSender(string type)
        {
            switch (type.ToLower())
            {
                case "email":
                    return new EmailNotification();
                case "sms":
                    return new SMSNotification();
                case "push":
                    return new PushNotification();
                default:
                    throw new ArgumentException("Invalid notification type");
            }
        }
    }
    
    // Testing the Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            INotificationSender notificationSender;

            notificationSender = NotificationFactory.CreateNotificationSender("email");
            notificationSender.SendNotification("This is an email notification!");

            notificationSender = NotificationFactory.CreateNotificationSender("sms");
            notificationSender.SendNotification("This is an SMS notification!");

            notificationSender = NotificationFactory.CreateNotificationSender("push");
            notificationSender.SendNotification("This is a push notification!");

            // As with other factory examples, adding new notification methods 
            // would only require extending the factory, without altering the client code.

            Console.ReadKey();
        }
    }
}

The Factory Design Pattern allows us to abstract away the specifics of constructing several notification senders in this real-time example. Because of this, we can simply grow our factory in the future if we need to add more notification types, all the while keeping the client code original and consistent. The following output will appear when you run the code mentioned above.

Output:

Real-Time Example of Factory Design Pattern in C#: Discounts in an E-commerce Application

Let's see one more actual, global example of the Factory Design Pattern in operation. Imagine an online business where a variety of discounts are applied to products based entirely on different circumstances.

Example

using System;
namespace FactoryDesignPattern
{
    //Define the Product Interface
    public interface IDiscountStrategy
    {
        decimal ApplyDiscount(decimal price);
    }

    //Concrete Implementations for the Products
    public class SeasonalDiscount: IDiscountStrategy
    {
        public decimal ApplyDiscount(decimal price)
        {
            return price * 0.90m; // 10% seasonal discount
        }
    }

    public class ClearanceDiscount : IDiscountStrategy
    {
        public decimal ApplyDiscount(decimal price)
        {
            return price * 0.70m; // 30% clearance discount
        }
    }

    public class MemberDiscount : IDiscountStrategy
    {
        public decimal ApplyDiscount(decimal price)
        {
            return price * 0.95m; // 5% member discount
        }
    }

    //Factory Class to Produce the Products
    public static class DiscountFactory
    {
        public static IDiscountStrategy CreateDiscountStrategy(string type)
        {
            switch (type.ToLower())
            {
                case "seasonal":
                    return new SeasonalDiscount();
                case "clearance":
                    return new ClearanceDiscount();
                case "member":
                    return new MemberDiscount();
                default:
                    throw new ArgumentException("Invalid discount type");
            }
        }
    }
    
    // Testing the Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            IDiscountStrategy discountStrategy;

            decimal originalPrice = 100.0m;

            discountStrategy = DiscountFactory.CreateDiscountStrategy("seasonal");
            Console.WriteLine($"Seasonal Discounted Price: {discountStrategy.ApplyDiscount(originalPrice)}");

            discountStrategy = DiscountFactory.CreateDiscountStrategy("clearance");
            Console.WriteLine($"Clearance Discounted Price: {discountStrategy.ApplyDiscount(originalPrice)}");

            discountStrategy = DiscountFactory.CreateDiscountStrategy("member");
            Console.WriteLine($"Member Discounted Price: {discountStrategy.ApplyDiscount(originalPrice)}");

            Console.ReadKey();
        }
    }
}

With this configuration, we have to extend the DiscountFactory and build a new discount strategy class each time a new kind of discount is added. The client code stays the same and doesn't have to know the particulars of how each discount is applied. This illustrates how the Factory Design Pattern provides flexibility in object generation and enables a clear separation of concerns. The following output will appear when you run the code mentioned above.

Output:

Factory Design Patterns: Real-Time Applications in C#

The following are some situations or real-world uses for the Factory Design Pattern:

  • User Interface Control Creation: The Factory pattern can be used to produce controls (such as buttons, text boxes, or custom components) dynamically based on user actions or setups in applications with complex user interfaces, like Windows forms or WPF applications.
  • Database Access: Instead of hard-coding the database type in the business logic, a factory can be used to instantiate the right database connection and command objects in applications that communicate with several databases (such as SQL Server, Oracle, or MySQL).
  • Logging Frameworks: Based on the application settings or environment, a factory can offer suitable logging objects (such as file loggers, database loggers, or cloud-based loggers) for applications that need logging functionality.
  • Configuration Management: A factory can build configuration objects that are specific to the current environment and abstract the details from the rest of the application, which is useful when managing several configurations (such as development, staging, and production).
  • Systems for Processing Payments: Based on the chosen payment method, a factory can instantiate the appropriate payment processing class in systems that must process payments through several gateways (such as PayPal, Stripe, or credit card).
  • Game creation: Characters, weaponry, and other game pieces can be dynamically created using a factory pattern, depending on the current state of the game or user decisions.
  • Plugin or Module Systems: For programs that support plugins or modules, the Factory pattern can instantiate the relevant plugin based on user input or configuration files.
  • API Integration: A factory can supply the appropriate API client depending on the service needed when integrating with different external APIs (such as social media, weather, or geolocation services).
  • When the production of an object (such as threads or database connections) is costly, object pooling allows a factory to manage and reuse these objects efficiently.
  • Advantages of Factory Design Pattern in C#:

  • Loose Coupling: The Factory Pattern separates the product's use and implementation. This implies that the client code need not know the details of how to make the product.
  • Scalability and Flexibility: Adding new concrete items without modifying the client code is simple, making the application more scalable and versatile.
  • Single Responsibility Principle: In accordance with the Single Responsibility Principle, the factory class contains the logic for creating the goods. Code becomes neater and more structured as a result.
  • Open/Closed Principle: The Factory Pattern allows you to add new product categories without changing the factory or client code already in place, which is consistent with the Open/Closed Principle.
  • Handle Complexity: This feature is helpful for developing things that require more than just simple instantiation because it allows you to manage and centralize complex creation logic.
  • Authority over Instantiation: Factories have authority over the process and timing of object creation. In a factory, for example, you can use prototype, pool, and singleton patterns.
  • Disadvantages of Factory Design Pattern in C#:

  • Complexity: Adding a factory pattern to the code can make it more complicated, especially if an object generation function alone would be sufficient.
  • Indirection: The code occasionally becomes more difficult to comprehend and debug due to the additional layer of abstraction, particularly for people who are not experienced with the pattern.
  • Overhead: The extra method call and object formation overhead may be a disadvantage in applications where efficiency is a top priority.
  • Dependency on Factories: When producing objects, the client code still relies on the factory, which can cause issues such as the overabundance of factory classes in complicated systems.
  • Refactoring Existing Code: It can be difficult and necessitate extensive reworking to integrate the Factory Pattern into existing code that wasn't created with this pattern in mind.
  • Requires Proper Design: Improper use or application of the Factory Pattern (e.g., utilizing an excessive number of factories or the wrong abstraction) can cause problems with maintenance and make future changes more challenging.
  • Conclusion:

In conclusion, the Factory Design Pattern is an effective tool for flexible and maintainable object creation in C# programming. This pattern facilitates code reusability and scalability by allowing for the separation of client code and concrete classes by enclosing the object generation process within a factory class. The Factory Design Pattern is useful in real-world situations when it comes to designing various forms, shapes, or other items whose precise kind may change according to specific circumstances. Developers can design more modular and extendable code and improve the overall quality and maintainability of their software projects by utilizing this pattern.

Input Required

This code uses input(). Please provide values below: