Back to: Design Patterns in C# With Real-Time Examples
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#.
- Payment Gateways in E-commerce
- Cross-Platform UI Development
- Vehicle Manufacturing Company
- Cross-Platform Application Configuration
- Furniture Shop
- Managing Connections to Different Types of Databases
- Multi-Device User Interfaces
- Animal Kingdoms
- Multimedia Software
- 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.
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#: 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#: 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#: 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#: 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#: 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#: 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(); } } }
When you run the above code, you will get the following output.
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 adding new regions and associated animals easily. When you run the above code, you will get the following output.
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#: 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. When you run the above code, you will get the following output.
Advantages and Disadvantages of Abstract Factory Design Pattern in C#
The Abstract Factory design pattern is a popular creational pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. Here are the advantages and disadvantages of using the Abstract Factory design pattern:
Advantages of Abstract Factory Design Pattern in C#:
- Consistency: The pattern ensures that the created families of products are consistent and compatible. When you get a product from the factory, it’s guaranteed to work with the other products from the same family.
- Separation of Concerns: The creation logic of the products is isolated from the client code. This separation makes the system architecture cleaner and easier to maintain.
- Flexibility: By decoupling the client code from concrete product classes, you can change or extend the types of products the factory creates without altering client code.
- Scalability: It’s easy to introduce new product families or variants by simply extending the existing factories or adding new ones. This makes the system scalable in terms of product variations.
- Exchangeability: Client applications can switch between product families at runtime, allowing dynamic behavior based on configurations or environments.
Disadvantages of Abstract Factory Design Pattern in C#:
- Complexity: Introducing new classes and interfaces for factories and products can increase the system’s complexity, especially when many product families exist.
- Factory Proliferation: A new concrete factory must be created for each new family of products. This can lead to a proliferation of factory classes.
- Change Requires Subclassing: Introducing a new product across all families may require changes to the abstract factory interface and potentially all of its concrete subclasses, violating the open/closed principle.
- Initialization Overhead: The initialization process can be slightly heavier, especially when the system must decide between multiple product families during runtime.
- Indirection: While the pattern helps achieve decoupling, it also introduces another layer of abstraction, making the system harder to understand for newcomers.
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.