Real-Time Examples of Abstract Factory Design Pattern in C#

Real-Time Examples of the Abstract Factory Design Pattern in C#

In this article, I will discuss the Real-Time Examples of the Abstract Factory Design Pattern in C#. Please read our previous article discussing the basic concepts of the Abstract Factory Design Pattern in C# with Examples. At the end of this article, you will understand the following Real-time Examples using the Abstract Factory Design Pattern in C#.

  1. Payment Gateways in E-commerce
  2. Cross-Platform UI Development
  3. Vehicle Manufacturing Company
  4. Cross-Platform Application Configuration
  5. Furniture Shop
  6. Managing Connections to Different Types of Databases
  7. Multi-Device User Interfaces
  8. Animal Kingdoms
  9. Multimedia Software
  10. Beverages
What is an Abstract Factory Design Pattern?

The Abstract Factory Design Pattern belongs to the Creational Design Pattern Category, and hence, it deals with Object Creation and Initialization. As discussed in our previous article, the Abstract Factory Design Pattern provides an interface for creating families of related or dependent objects without specifying their concrete class names. We can also say that Abstract Factory is a super factory or a factory of factories. That means it prevents the client from knowing which factory would be returned from the abstract factory.

How to implement the Abstract Factory Design Pattern in C#?

We must use the following components to Implement the Abstract Factory Design Pattern in C#.

  • Abstract Product: These are going to be interfaces for creating abstract products. Here, we need to define the Operations a Product should have.
  • Concrete Product: These are the classes that implement the Abstract Product interface.
  • Abstract Factory: This will be an interface for operations that will create Abstract Product objects.
  • Concrete Factory: These classes implement the Abstract Factory interface and provide implementations for the interface methods. We can use these concrete classes to create concrete product objects.
  • Client: This class will use our Abstract Factory and Abstract Product interfaces to create a family of products.

To better understand how these components are integrated, please look at the following UML diagram of the Abstract Factory Design Pattern.

How to implement the Abstract Factory Design Pattern in C#?

Real-Time Example of Abstract Factory Design Pattern in C#: Payment Gateways in E-commerce

Let’s consider a scenario where financial software needs to process payments using different methods, such as “Credit Card” and “PayPal”. The Abstract Factory pattern can help create families of related objects to process payments with each method, considering operations like payment authorization and transfer. Let us see how we can implement the above example using the Abstract Factory Design Pattern in C#:

using System;
namespace AbstractFactoryDesignPattern
{
    // Abstract Products
    public interface IPaymentAuthorization
    {
        bool AuthorizePayment(decimal amount);
    }

    public interface IPaymentTransfer
    {
        bool Transfer(decimal amount);
    }

    // Concrete Products for Credit Card
    public class CreditCardAuthorization : IPaymentAuthorization
    {
        public bool AuthorizePayment(decimal amount)
        {
            Console.WriteLine($"Authorizing payment of {amount} via Credit Card...");
            return true; // Mocked success
        }
    }

    public class CreditCardTransfer : IPaymentTransfer
    {
        public bool Transfer(decimal amount)
        {
            Console.WriteLine($"Transferring payment of {amount} via Credit Card...");
            return true; // Mocked success
        }
    }

    // Concrete Products for PayPal
    public class PayPalAuthorization : IPaymentAuthorization
    {
        public bool AuthorizePayment(decimal amount)
        {
            Console.WriteLine($"Authorizing payment of {amount} via PayPal...");
            return true; // Mocked success
        }
    }

    public class PayPalTransfer : IPaymentTransfer
    {
        public bool Transfer(decimal amount)
        {
            Console.WriteLine($"Transferring payment of {amount} via PayPal...");
            return true; // Mocked success
        }
    }

    // Abstract Factory
    public interface IPaymentFactory
    {
        IPaymentAuthorization CreateAuthorization();
        IPaymentTransfer CreateTransfer();
    }

    // Concrete Factories
    public class CreditCardPaymentFactory : IPaymentFactory
    {
        public IPaymentAuthorization CreateAuthorization() => new CreditCardAuthorization();
        public IPaymentTransfer CreateTransfer() => new CreditCardTransfer();
    }

    public class PayPalPaymentFactory : IPaymentFactory
    {
        public IPaymentAuthorization CreateAuthorization() => new PayPalAuthorization();
        public IPaymentTransfer CreateTransfer() => new PayPalTransfer();
    }

    // Client Code
    public class PaymentProcessor
    {
        private readonly IPaymentAuthorization _authorization;
        private readonly IPaymentTransfer _transfer;

        public PaymentProcessor(IPaymentFactory factory)
        {
            _authorization = factory.CreateAuthorization();
            _transfer = factory.CreateTransfer();
        }

        public bool ProcessPayment(decimal amount)
        {
            if (_authorization.AuthorizePayment(amount))
            {
                return _transfer.Transfer(amount);
            }
            return false;
        }
    }
    
    // Testing the Abstract Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Processing payment using Credit Card:");
            var creditCardFactory = new CreditCardPaymentFactory();
            var creditCardProcessor = new PaymentProcessor(creditCardFactory);
            creditCardProcessor.ProcessPayment(100.00M);

            Console.WriteLine("\nProcessing payment using PayPal:");
            var payPalFactory = new PayPalPaymentFactory();
            var payPalProcessor = new PaymentProcessor(payPalFactory);
            payPalProcessor.ProcessPayment(100.00M);

            Console.ReadKey();
        }
    }
}

In this example, the Abstract Factory pattern ensures that operations related to each payment method are cohesive and consistent. Each payment method’s authorization and transfer logic are kept separate, making extending the system with more payment methods in the future easier. When you run the above code, you will get the following output.

Real-Time Example of Abstract Factory Design Pattern in C#: Payment

Real-Time Example of Abstract Factory Design Pattern in C#: Cross-Platform UI Development

Suppose you’re building a cross-platform application that should run on Windows and MacOS. Both platforms have different styles for their UI elements. However, your application’s code should not care about which platform it’s running on. Instead, it should request the necessary UI elements and expect them to adhere to the platform’s conventions.

Using the Abstract Factory pattern, you can create an abstract factory layer that produces platform-specific UI elements. Let us see how we can implement the above example using the Abstract Factory Design Pattern in C#:

using System;
namespace AbstractFactoryDesignPattern
{
    // Abstract Products
    public interface IButton
    {
        void Click();
    }

    public interface ITextBox
    {
        void Write(string text);
    }

    // Concrete Products for Windows
    public class WindowsButton : IButton
    {
        public void Click()
        {
            Console.WriteLine("Windows Button Clicked");
        }
    }

    public class WindowsTextBox : ITextBox
    {
        public void Write(string text)
        {
            Console.WriteLine($"Text Written in Windows TextBox: {text}");
        }
    }

    // Concrete Products for MacOS
    public class MacOSButton : IButton
    {
        public void Click()
        {
            Console.WriteLine("MacOS Button Clicked");
        }
    }

    public class MacOSTextBox : ITextBox
    {
        public void Write(string text)
        {
            Console.WriteLine($"Text Written in MacOS TextBox: {text}");
        }
    }

    // Abstract Factory
    public interface IUIFactory
    {
        IButton CreateButton();
        ITextBox CreateTextBox();
    }

    // Concrete Factory for Windows
    public class WindowsUIFactory : IUIFactory
    {
        public IButton CreateButton() => new WindowsButton();

        public ITextBox CreateTextBox() => new WindowsTextBox();
    }

    // Concrete Factory for MacOS
    public class MacOSUIFactory : IUIFactory
    {
        public IButton CreateButton() => new MacOSButton();

        public ITextBox CreateTextBox() => new MacOSTextBox();
    }

    // Client
    public class Application
    {
        private readonly IButton _button;
        private readonly ITextBox _textBox;

        public Application(IUIFactory factory)
        {
            _button = factory.CreateButton();
            _textBox = factory.CreateTextBox();
        }

        public void Render()
        {
            _button.Click();
            _textBox.Write("Sample Text");
        }
    }
    
    // Testing the Abstract Factory Design Pattern
    // Client Code
    public class Client
    {
        public static void Main()
        {
            IUIFactory factory;

            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
            {
                factory = new WindowsUIFactory();
            }
            else
            {
                factory = new MacOSUIFactory();
            }

            var app = new Application(factory);
            app.Render();

            Console.ReadKey();
        }
    }
}

In this example, when you run the application on a Windows machine, you’ll see the output tailored for Windows UI elements and similarly for MacOS. The actual decision-making about which UI elements to use is abstracted away using the Abstract Factory pattern. The application code asks the factory to give it a button or a text box without knowing how it looks or behaves on the underlying platform. When you run the above code, you will get the following output.

Real-Time Example of Abstract Factory Design Pattern in C#: Cross-Platform UI Development

Real-Time Example of Abstract Factory Design Pattern in C#: Vehicle Manufacturing Company

Let’s take the example of a vehicle manufacturing company that produces cars and trucks. These vehicles can either be electric or gas-powered. The company needs a system to create different parts (e.g., engine and tires) based on the type of vehicle and its power source. Let us see how we can implement the above example using the Abstract Factory Design Pattern in C#:

using System;
namespace AbstractFactoryDesignPattern
{
    // Abstract Products
    public interface IEngine
    {
        string GetEngineType();
    }

    public interface ITire
    {
        string GetTireType();
    }

    // Concrete Products for Electric Car
    public class ElectricCarEngine : IEngine
    {
        public string GetEngineType()
        {
            return "Electric Car Engine";
        }
    }

    public class ElectricCarTire : ITire
    {
        public string GetTireType()
        {
            return "Electric Car Tire";
        }
    }

    // Concrete Products for Gas Car
    public class GasCarEngine : IEngine
    {
        public string GetEngineType()
        {
            return "Gas Car Engine";
        }
    }

    public class GasCarTire : ITire
    {
        public string GetTireType()
        {
            return "Gas Car Tire";
        }
    }

    // Concrete Products for Electric Truck
    public class ElectricTruckEngine : IEngine
    {
        public string GetEngineType()
        {
            return "Electric Truck Engine";
        }
    }

    public class ElectricTruckTire : ITire
    {
        public string GetTireType()
        {
            return "Electric Truck Tire";
        }
    }

    // Concrete Products for Gas Truck
    public class GasTruckEngine : IEngine
    {
        public string GetEngineType()
        {
            return "Gas Truck Engine";
        }
    }

    public class GasTruckTire : ITire
    {
        public string GetTireType()
        {
            return "Gas Truck Tire";
        }
    }

    // Abstract Factory
    public interface IVehicleFactory
    {
        IEngine CreateEngine();
        ITire CreateTire();
    }

    // Concrete Factories
    public class ElectricCarFactory : IVehicleFactory
    {
        public IEngine CreateEngine() => new ElectricCarEngine();
        public ITire CreateTire() => new ElectricCarTire();
    }

    public class GasCarFactory : IVehicleFactory
    {
        public IEngine CreateEngine() => new GasCarEngine();
        public ITire CreateTire() => new GasCarTire();
    }

    public class ElectricTruckFactory : IVehicleFactory
    {
        public IEngine CreateEngine() => new ElectricTruckEngine();
        public ITire CreateTire() => new ElectricTruckTire();
    }

    public class GasTruckFactory : IVehicleFactory
    {
        public IEngine CreateEngine() => new GasTruckEngine();
        public ITire CreateTire() => new GasTruckTire();
    }

    // Client Code
    public class VehicleManufacturingPlant
    {
        private readonly IEngine _engine;
        private readonly ITire _tire;

        public VehicleManufacturingPlant(IVehicleFactory factory)
        {
            _engine = factory.CreateEngine();
            _tire = factory.CreateTire();
        }

        public void DescribeVehicle()
        {
            Console.WriteLine($"Vehicle with Engine: {_engine.GetEngineType()} and Tire: {_tire.GetTireType()}");
        }
    }

    // Testing the Abstract Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            var electricCarFactory = new ElectricCarFactory();
            var electricCarPlant = new VehicleManufacturingPlant(electricCarFactory);
            electricCarPlant.DescribeVehicle();

            var gasTruckFactory = new GasTruckFactory();
            var gasTruckPlant = new VehicleManufacturingPlant(gasTruckFactory);
            gasTruckPlant.DescribeVehicle();

            Console.ReadKey();
        }
    }
}

In this example, based on the chosen factory, the system produces vehicles with the appropriate engine and tire type. The Abstract Factory pattern abstracts away the complexities of creating families of related or dependent objects, allowing the client code to remain decoupled from the specific classes that are instantiated. When you run the above code, you will get the following output.

Real-Time Example of Abstract Factory Design Pattern in C#: Vehicle Manufacturing Company

Real-Time Example of Abstract Factory Design Pattern in C#: Cross-Platform Application Configuration

Imagine you’re developing a cross-platform application that will run on both Android and iOS. Each platform has its own unique settings, services, and notification mechanisms. You can utilize the Abstract Factory pattern to avoid tight coupling with platform-specific implementations. Let us see how we can implement the above example using the Abstract Factory Design Pattern in C#:

using System;
namespace AbstractFactoryDesignPattern
{
    // Abstract Products
    public interface INotificationService
    {
        void Notify(string message);
    }

    public interface ISettings
    {
        string GetSetting(string key);
    }

    // Concrete Products for Android
    public class AndroidNotificationService : INotificationService
    {
        public void Notify(string message)
        {
            Console.WriteLine($"Android notification: {message}");
        }
    }

    public class AndroidSettings : ISettings
    {
        public string GetSetting(string key)
        {
            return $"Android setting value for {key}";
        }
    }

    // Concrete Products for iOS
    public class iOSNotificationService : INotificationService
    {
        public void Notify(string message)
        {
            Console.WriteLine($"iOS notification: {message}");
        }
    }

    public class iOSSettings : ISettings
    {
        public string GetSetting(string key)
        {
            return $"iOS setting value for {key}";
        }
    }

    // Abstract Factory
    public interface IPlatformFactory
    {
        INotificationService CreateNotificationService();
        ISettings CreateSettings();
    }

    // Concrete Factories
    public class AndroidFactory : IPlatformFactory
    {
        public INotificationService CreateNotificationService() => new AndroidNotificationService();
        public ISettings CreateSettings() => new AndroidSettings();
    }

    public class iOSFactory : IPlatformFactory
    {
        public INotificationService CreateNotificationService() => new iOSNotificationService();
        public ISettings CreateSettings() => new iOSSettings();
    }

    // Client Code
    public class ApplicationConfigurator
    {
        private readonly INotificationService _notificationService;
        private readonly ISettings _settings;

        public ApplicationConfigurator(IPlatformFactory factory)
        {
            _notificationService = factory.CreateNotificationService();
            _settings = factory.CreateSettings();
        }

        public void RunSampleOperations()
        {
            string usernameSetting = _settings.GetSetting("Username");
            Console.WriteLine($"Retrieved setting: {usernameSetting}");

            _notificationService.Notify("App started!");
        }
    }
    
    // Testing the Abstract Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            IPlatformFactory platformFactory;

            if (Environment.OSVersion.Platform == PlatformID.Unix) // A simplistic way to distinguish, not accurate for all scenarios
            {
                platformFactory = new iOSFactory();
            }
            else
            {
                platformFactory = new AndroidFactory();
            }

            var appConfigurator = new ApplicationConfigurator(platformFactory);
            appConfigurator.RunSampleOperations();

            Console.ReadKey();
        }
    }
}

In this real-time example, the Abstract Factory pattern allows us to abstract away platform-specific details from our main application. This promotes the separation of concerns and makes the code more modular and easier to extend when adding support for new platforms. When you run the above code, you will get the following output.

Real-Time Example of Abstract Factory Design Pattern in C#: Cross-Platform Application Configuration

Real-Time Example of Abstract Factory Design Pattern in C#: Furniture Shop

Let’s see another real-world example: a furniture shop offering modern and vintage products. We will use the Abstract Factory pattern to produce related furniture objects for different styles without specifying the exact classes to create. Let us see how we can implement the above example using the Abstract Factory Design Pattern in C#:

using System;
namespace AbstractFactoryDesignPattern
{
    // Abstract Products
    public interface IChair
    {
        void SitOn();
    }

    public interface ISofa
    {
        void LayOn();
    }

    // Concrete Products for Modern Style
    public class ModernChair : IChair
    {
        public void SitOn()
        {
            Console.WriteLine("Sitting on a modern chair.");
        }
    }

    public class ModernSofa : ISofa
    {
        public void LayOn()
        {
            Console.WriteLine("Laying on a modern sofa.");
        }
    }

    // Concrete Products for Vintage Style
    public class VintageChair : IChair
    {
        public void SitOn()
        {
            Console.WriteLine("Sitting on a vintage chair.");
        }
    }

    public class VintageSofa : ISofa
    {
        public void LayOn()
        {
            Console.WriteLine("Laying on a vintage sofa.");
        }
    }

    // Abstract Factory
    public interface IFurnitureFactory
    {
        IChair CreateChair();
        ISofa CreateSofa();
    }

    // Concrete Factories
    public class ModernFurnitureFactory : IFurnitureFactory
    {
        public IChair CreateChair() => new ModernChair();
        public ISofa CreateSofa() => new ModernSofa();
    }

    public class VintageFurnitureFactory : IFurnitureFactory
    {
        public IChair CreateChair() => new VintageChair();
        public ISofa CreateSofa() => new VintageSofa();
    }

    // Client Code
    public class FurnitureShop
    {
        private readonly IChair _chair;
        private readonly ISofa _sofa;

        public FurnitureShop(IFurnitureFactory factory)
        {
            _chair = factory.CreateChair();
            _sofa = factory.CreateSofa();
        }

        public void ShowProducts()
        {
            _chair.SitOn();
            _sofa.LayOn();
        }
    }
    
    // Testing the Abstract Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Order for Modern Furniture:");
            var modernFactory = new ModernFurnitureFactory();
            var modernShop = new FurnitureShop(modernFactory);
            modernShop.ShowProducts();

            Console.WriteLine("\nOrder for Vintage Furniture:");
            var vintageFactory = new VintageFurnitureFactory();
            var vintageShop = new FurnitureShop(vintageFactory);
            vintageShop.ShowProducts();

            Console.ReadKey();
        }
    }
}

In this example, based on the chosen factory (Modern or Vintage), the furniture shop produces the furniture products accordingly. The Abstract Factory pattern ensures that the families of related products (in this case, furniture) are consistently created, ensuring that you won’t accidentally mix modern chairs with vintage sofas, for instance. When you run the above code, you will get the following output.

Real-Time Example of Abstract Factory Design Pattern in C#: Furniture Shop

Real-Time Example of Abstract Factory Design Pattern in C#: Managing Connections to Different Types of Databases

Let’s understand another real-time scenario: managing connections to different types of databases like SQL Server and Oracle. In a large organization, various applications might need different types of database connections, but they all require certain operations like “ExecuteCommand” and “CreateConnection”. Let us see how we can implement the above example using the Abstract Factory Design Pattern in C#:

using System;
namespace AbstractFactoryDesignPattern
{
    // Abstract Products
    public interface ICommand
    {
        void Execute(string query);
    }

    public interface IConnection
    {
        void OpenConnection();
    }

    // Concrete Products for SQL Server
    public class SQLServerCommand : ICommand
    {
        public void Execute(string query)
        {
            Console.WriteLine($"SQL Server executing command: {query}");
        }
    }

    public class SQLServerConnection : IConnection
    {
        public void OpenConnection()
        {
            Console.WriteLine("SQL Server connection opened.");
        }
    }

    // Concrete Products for Oracle
    public class OracleCommand : ICommand
    {
        public void Execute(string query)
        {
            Console.WriteLine($"Oracle executing command: {query}");
        }
    }

    public class OracleConnection : IConnection
    {
        public void OpenConnection()
        {
            Console.WriteLine("Oracle connection opened.");
        }
    }

    // Abstract Factory
    public interface IDatabaseFactory
    {
        ICommand CreateCommand();
        IConnection CreateConnection();
    }

    // Concrete Factories
    public class SQLServerFactory : IDatabaseFactory
    {
        public ICommand CreateCommand() => new SQLServerCommand();
        public IConnection CreateConnection() => new SQLServerConnection();
    }

    public class OracleFactory : IDatabaseFactory
    {
        public ICommand CreateCommand() => new OracleCommand();
        public IConnection CreateConnection() => new OracleConnection();
    }

    // Client Code
    public class DatabaseManager
    {
        private readonly ICommand _command;
        private readonly IConnection _connection;

        public DatabaseManager(IDatabaseFactory factory)
        {
            _command = factory.CreateCommand();
            _connection = factory.CreateConnection();
        }

        public void PerformDatabaseOperations(string query)
        {
            _connection.OpenConnection();
            _command.Execute(query);
        }
    }
    
    // Testing the Abstract Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Using SQL Server:");
            var sqlFactory = new SQLServerFactory();
            var sqlManager = new DatabaseManager(sqlFactory);
            sqlManager.PerformDatabaseOperations("SELECT * FROM Users");

            Console.WriteLine("\nUsing Oracle:");
            var oracleFactory = new OracleFactory();
            var oracleManager = new DatabaseManager(oracleFactory);
            oracleManager.PerformDatabaseOperations("SELECT * FROM Employees");

            Console.ReadKey();
        }
    }
}

In this real-time example, the Abstract Factory pattern consistently creates related database objects (connection and command) without directly tying the code to a specific database implementation. This approach makes it easier to add support for new databases in the future, ensuring flexibility and maintainability. When you run the above code, you will get the following output.

Real-Time Example of Abstract Factory Design Pattern in C#: Managing Connections to Different Types of Databases

Real-Time Example of Abstract Factory Design Pattern in C#: Multi-Device User Interfaces

Let’s understand another real-world scenario: creating multi-device user interfaces for mobile and desktop platforms. As the application grows, there might be a need to create consistent user interfaces for different devices.

The Abstract Factory pattern is handy here to ensure the creation of consistent UI elements across these devices. Let us see how we can implement the above example using the Abstract Factory Design Pattern in C#:

using System;
namespace AbstractFactoryDesignPattern
{
    // Abstract Products
    public interface IButton
    {
        void Render();
    }

    public interface IMenu
    {
        void Display();
    }

    // Concrete Products for Mobile
    public class MobileButton : IButton
    {
        public void Render()
        {
            Console.WriteLine("Rendering a mobile button.");
        }
    }

    public class MobileMenu : IMenu
    {
        public void Display()
        {
            Console.WriteLine("Displaying a mobile menu.");
        }
    }

    // Concrete Products for Desktop
    public class DesktopButton : IButton
    {
        public void Render()
        {
            Console.WriteLine("Rendering a desktop button.");
        }
    }

    public class DesktopMenu : IMenu
    {
        public void Display()
        {
            Console.WriteLine("Displaying a desktop menu.");
        }
    }

    // Abstract Factory
    public interface IUIFactory
    {
        IButton CreateButton();
        IMenu CreateMenu();
    }

    // Concrete Factories
    public class MobileUIFactory : IUIFactory
    {
        public IButton CreateButton() => new MobileButton();
        public IMenu CreateMenu() => new MobileMenu();
    }

    public class DesktopUIFactory : IUIFactory
    {
        public IButton CreateButton() => new DesktopButton();
        public IMenu CreateMenu() => new DesktopMenu();
    }

    // Client Code
    public class UserInterface
    {
        private readonly IButton _button;
        private readonly IMenu _menu;

        public UserInterface(IUIFactory factory)
        {
            _button = factory.CreateButton();
            _menu = factory.CreateMenu();
        }

        public void RenderUI()
        {
            _button.Render();
            _menu.Display();
        }
    }
    
    // Testing the Abstract Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Creating UI for Mobile:");
            var mobileFactory = new MobileUIFactory();
            var mobileUI = new UserInterface(mobileFactory);
            mobileUI.RenderUI();

            Console.WriteLine("\nCreating UI for Desktop:");
            var desktopFactory = new DesktopUIFactory();
            var desktopUI = new UserInterface(desktopFactory);
            desktopUI.RenderUI();

            Console.ReadKey();
        }
    }
}

In this scenario, the Abstract Factory pattern allows for creating related UI components (buttons and menus) tailored to the specific device type (mobile or desktop). By segregating the creation logic into distinct factories, the system remains organized and scalable, allowing easy addition of new device types in the future if required. When you run the above code, you will get the following output.

Real-Time Example of Abstract Factory Design Pattern in C#: Multi-Device User Interfaces

Real-Time Example of Abstract Factory Design Pattern in C#: Animal Kingdoms

Let’s understand another example involving animal kingdoms. Consider that you are building a simulation where animals from different regions (e.g., “Jungle” and “Arctic”) interact with their environment.

Different regions have different types of herbivores and carnivores. The Abstract Factory Pattern will be handy to ensure that the system can easily instantiate families of related animal objects based on the region. Let us see how we can implement the above example using the Abstract Factory Design Pattern in C#:

using System;
namespace AbstractFactoryDesignPattern
{
    // Abstract Products
    public interface IHerbivore
    {
        void Graze();
    }

    public interface ICarnivore
    {
        void Hunt();
    }

    // Concrete Products for Jungle
    public class Deer : IHerbivore
    {
        public void Graze()
        {
            Console.WriteLine("Deer is grazing.");
        }
    }

    public class Tiger : ICarnivore
    {
        public void Hunt()
        {
            Console.WriteLine("Tiger is hunting.");
        }
    }

    // Concrete Products for Arctic
    public class Reindeer : IHerbivore
    {
        public void Graze()
        {
            Console.WriteLine("Reindeer is grazing.");
        }
    }

    public class PolarBear : ICarnivore
    {
        public void Hunt()
        {
            Console.WriteLine("Polar bear is hunting.");
        }
    }

    // Abstract Factory
    public interface IAnimalFactory
    {
        IHerbivore CreateHerbivore();
        ICarnivore CreateCarnivore();
    }

    // Concrete Factories
    public class JungleAnimalFactory : IAnimalFactory
    {
        public IHerbivore CreateHerbivore() => new Deer();
        public ICarnivore CreateCarnivore() => new Tiger();
    }

    public class ArcticAnimalFactory : IAnimalFactory
    {
        public IHerbivore CreateHerbivore() => new Reindeer();
        public ICarnivore CreateCarnivore() => new PolarBear();
    }

    // Client Code
    public class Ecosystem
    {
        private readonly IHerbivore _herbivore;
        private readonly ICarnivore _carnivore;

        public Ecosystem(IAnimalFactory factory)
        {
            _herbivore = factory.CreateHerbivore();
            _carnivore = factory.CreateCarnivore();
        }

        public void RunFoodChain()
        {
            _herbivore.Graze();
            _carnivore.Hunt();
        }
    }
    
    // Testing the Abstract Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Jungle Ecosystem:");
            var jungleFactory = new JungleAnimalFactory();
            var jungle = new Ecosystem(jungleFactory);
            jungle.RunFoodChain();

            Console.WriteLine("\nArctic Ecosystem:");
            var arcticFactory = new ArcticAnimalFactory();
            var arctic = new Ecosystem(arcticFactory);
            arctic.RunFoodChain();

            Console.ReadKey();
        }
    }
}

In this example, the Abstract Factory pattern facilitates the instantiation of related animals (herbivores and carnivores) specific to a region (Jungle or Arctic). This organization ensures flexibility, allowing for the easy addition of new regions and associated animals in the future. When you run the above code, you will get the following output.

Real-Time Example of Abstract Factory Design Pattern in C#: Animal Kingdoms

Real-Time Example of Abstract Factory Design Pattern in C#: Multimedia Software

Let’s understand another example of a multimedia software suite that supports audio and video. Depending on the user’s needs, the software can use different formats, e.g., MP3 vs. WAV for audio and MP4 vs. AVI for video.

The Abstract Factory pattern can ensure that once a format is chosen, consistent audio and video processing tools are provided for that format. Let us see how we can implement the above example using the Abstract Factory Design Pattern in C#:

using System;
namespace AbstractFactoryDesignPattern
{
    // Abstract Products
    public interface IAudioProcessor
    {
        void ProcessAudio(string file);
    }

    public interface IVideoProcessor
    {
        void ProcessVideo(string file);
    }

    // Concrete Products for MP3 & MP4
    public class MP3Processor : IAudioProcessor
    {
        public void ProcessAudio(string file)
        {
            Console.WriteLine($"Processing MP3 audio file: {file}");
        }
    }

    public class MP4Processor : IVideoProcessor
    {
        public void ProcessVideo(string file)
        {
            Console.WriteLine($"Processing MP4 video file: {file}");
        }
    }

    // Concrete Products for WAV & AVI
    public class WAVProcessor : IAudioProcessor
    {
        public void ProcessAudio(string file)
        {
            Console.WriteLine($"Processing WAV audio file: {file}");
        }
    }

    public class AVIProcessor : IVideoProcessor
    {
        public void ProcessVideo(string file)
        {
            Console.WriteLine($"Processing AVI video file: {file}");
        }
    }

    // Abstract Factory
    public interface IMediaFactory
    {
        IAudioProcessor CreateAudioProcessor();
        IVideoProcessor CreateVideoProcessor();
    }

    // Concrete Factories
    public class MP3MP4Factory : IMediaFactory
    {
        public IAudioProcessor CreateAudioProcessor() => new MP3Processor();
        public IVideoProcessor CreateVideoProcessor() => new MP4Processor();
    }

    public class WAVAVIFactory : IMediaFactory
    {
        public IAudioProcessor CreateAudioProcessor() => new WAVProcessor();
        public IVideoProcessor CreateVideoProcessor() => new AVIProcessor();
    }

    // Client Code
    public class MediaApplication
    {
        private readonly IAudioProcessor _audioProcessor;
        private readonly IVideoProcessor _videoProcessor;

        public MediaApplication(IMediaFactory factory)
        {
            _audioProcessor = factory.CreateAudioProcessor();
            _videoProcessor = factory.CreateVideoProcessor();
        }

        public void Run(string audioFile, string videoFile)
        {
            _audioProcessor.ProcessAudio(audioFile);
            _videoProcessor.ProcessVideo(videoFile);
        }
    }

    // Testing the Abstract Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Using MP3 & MP4 formats:");
            var mp3mp4Factory = new MP3MP4Factory();
            var mp3mp4App = new MediaApplication(mp3mp4Factory);
            mp3mp4App.Run("song.mp3", "video.mp4");

            Console.WriteLine("\nUsing WAV & AVI formats:");
            var wavaviFactory = new WAVAVIFactory();
            var wavaviApp = new MediaApplication(wavaviFactory);
            wavaviApp.Run("song.wav", "video.avi");

            Console.ReadKey();
        }
    }
}

In this real-time example, the Abstract Factory pattern ensures consistent processing tools for each combination of audio and video formats. This organized structure allows for easy scalability should you wish to add support for more formats in the future. When you run the above code, you will get the following output.

Real-Time Example of Abstract Factory Design Pattern in C#: Multimedia Software

Real-Time Example of Abstract Factory Design Pattern in C#: Beverages

Consider a drink shop that offers both Coffee and Tea products. Each category (Coffee and Tea) has different types of products (like a cappuccino or green tea) and requires different ingredients (like milk, sugar, etc.). The Abstract Factory pattern can help organize the creation of these beverage products, ensuring all necessary ingredients are available. Let us see how we can implement the above example using the Abstract Factory Design Pattern in C#:

using System;
namespace AbstractFactoryDesignPattern
{
    // Abstract Products
    public interface IBeverage
    {
        void Drink();
    }

    public interface IIngredient
    {
        void Use();
    }

    // Concrete Products for Coffee
    public class Cappuccino : IBeverage
    {
        public void Drink()
        {
            Console.WriteLine("Drinking Cappuccino!");
        }
    }

    public class Milk : IIngredient
    {
        public void Use()
        {
            Console.WriteLine("Adding milk...");
        }
    }

    // Concrete Products for Tea
    public class GreenTea : IBeverage
    {
        public void Drink()
        {
            Console.WriteLine("Drinking Green Tea!");
        }
    }

    public class Sugar : IIngredient
    {
        public void Use()
        {
            Console.WriteLine("Adding sugar...");
        }
    }

    // Abstract Factory
    public interface IBeverageFactory
    {
        IBeverage PrepareBeverage();
        IIngredient AddIngredient();
    }

    // Concrete Factories
    public class CoffeeFactory : IBeverageFactory
    {
        public IBeverage PrepareBeverage() => new Cappuccino();
        public IIngredient AddIngredient() => new Milk();
    }

    public class TeaFactory : IBeverageFactory
    {
        public IBeverage PrepareBeverage() => new GreenTea();
        public IIngredient AddIngredient() => new Sugar();
    }

    // Client Code
    public class BeverageMaker
    {
        private readonly IBeverage _beverage;
        private readonly IIngredient _ingredient;

        public BeverageMaker(IBeverageFactory factory)
        {
            _beverage = factory.PrepareBeverage();
            _ingredient = factory.AddIngredient();
        }

        public void ServeBeverage()
        {
            _ingredient.Use();
            _beverage.Drink();
        }
    }
    
    // Testing the Abstract Factory Design Pattern
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Ordering Coffee:");
            var coffeeFactory = new CoffeeFactory();
            var coffeeMaker = new BeverageMaker(coffeeFactory);
            coffeeMaker.ServeBeverage();

            Console.WriteLine("\nOrdering Tea:");
            var teaFactory = new TeaFactory();
            var teaMaker = new BeverageMaker(teaFactory);
            teaMaker.ServeBeverage();

            Console.ReadKey();
        }
    }
}

In this example, the Abstract Factory pattern helps segregate the creation of beverages and their ingredients based on their category (Coffee or Tea). This ensures consistency and allows for easy extension if the drink shop wants to introduce new beverage categories in the future. When you run the above code, you will get the following output.

Real-Time Example of Abstract Factory Design Pattern in C#: Beverages

When to use Abstract Factory Design Pattern Real-Time Applications in C#?

The Abstract Factory design pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. The decision to use the Abstract Factory pattern in real-world applications depends on certain scenarios and requirements. Here are situations where you might consider employing the Abstract Factory pattern in C# applications:

  • System Needs to Be Independent of How Its Objects Are Created, Composed, and Represented: When isolating your application’s concrete classes, Abstract Factory can access specific families of objects without directly instantiating them.
  • System Should Be Configured with One of the Multiple Families of Products: If your software needs to support multiple sets or families of products (like UI controls for different operating systems or communication interfaces for different protocols), an Abstract Factory can help configure the application with the right family.
  • Products in a Family Need to Be Used Together: If mixing products from different families (like using a macOS button with a Windows window) doesn’t make sense, an Abstract Factory ensures the client uses products from the same family.
  • Adding New Families of Products: If there’s a requirement in the future to add new families of products without modifying existing code, the Abstract Factory pattern supports this scenario by allowing you to create new concrete factories for the new products.
  • Hide Object Creation and Composition: When you want to hide the logic of creating and composing objects and provide a way to get instances of objects from a family, the Abstract Factory can be used.
  • Enforce Standards and Compatibility: In cases where certain objects need to be compatible or adhere to certain standards (e.g., a particular protocol version), Abstract Factory can ensure that clients get compatible objects.

Real-time scenarios where Abstract Factory might be beneficial:

  • GUI Libraries: Different OS platforms have different appearances and behaviors for GUI components like buttons, text boxes, and windows. An application might use an abstract factory to produce GUI components that match the OS it’s running on (e.g., Windows, macOS, Linux)
  • Database Providers: When developing database-driven applications, the code might need to support different database servers like SQL Server, MySQL, or Oracle. Using an Abstract Factory, you can create a family of database commands, connections, and parameter objects for each database server. This ensures the application can easily switch between databases without many code changes.
  • Communication Frameworks: An application might need to communicate with other systems using different protocols (e.g., HTTP, FTP, WebSockets). An Abstract Factory can create families of related objects tailored for each protocol, like message formatters, message senders, and response parsers. Each protocol might have its way of creating messages, processing responses, and handling errors.
  • Cloud Provider Integrations: Different cloud providers might offer storage, computing, or database solutions, each with its own configurations and operations. Abstract Factory can remove the differences, allowing an application to switch between cloud providers seamlessly.
  • Payment Gateways in E-commerce: When an e-commerce platform supports multiple payment gateways (like PayPal, Stripe, or direct bank transfer), an abstract factory can help create a set of related objects for each gateway, like payment processor, payment validator, and transaction logger.
  • Games and Simulation: Different environments or levels might have different sets of characters or objects in game development or simulations. An abstract factory can help instantiate families of related objects for each environment or level.
  • Theming and Skinning: Applications allowing users to switch between different themes or skins might use the Abstract Factory pattern to create a consistent set of UI elements that belong to a particular theme.

While the Abstract Factory pattern can be highly beneficial in these scenarios, it’s essential to ensure that it fits the requirements of your project. Overusing design patterns or applying them in unsuitable situations can lead to unnecessary complexity. Always evaluate the actual needs of your application against the strengths and trade-offs of the pattern.

In the next article, I will discuss the Builder Design Pattern in C# with Examples. Here, in this article, I try to explain Real-Time Examples of Abstract Factory Design Patterns in C#. I hope you enjoy this Abstract Factory Design Pattern Real-Time Examples using C# article.

Leave a Reply

Your email address will not be published. Required fields are marked *