I'm carrying on with my earlier post about various design patterns. Information about the Abstract Factory design pattern can be found in this article. The Abstract Factory Pattern involves defining an interface or abstract class for creating families of related or dependent objects without specifying their concrete sub-classes. This allows a class to return to a factory of classes. Consequently, the Abstract Factory Pattern is considered one level higher than the Factory Pattern.
The Abstract Factory can be advantageous under the specified criteria
- In a graphical user interface (GUI) application, it may be necessary to create user interface components for different operating systems such as Windows, macOS, and Linux. The use of an Abstract Factory can facilitate the creation of these components without being tied to specific classes in the code.
- A software application that supports various databases like SQL, PostgreSQL, and Oracle can benefit from using an Abstract Factory. This allows for easy switching between databases by simply changing the factory, without the need to modify the client code.
- In a notification system with different types of notifications such as email, SMS, and push notifications, the Abstract Factory can ensure the consistent creation of all notifications, maintaining a uniform pattern or interface.
- If an application is designed to support multiple UI themes, an Abstract Factory can be used to create components for each theme. This ensures that all components for a specific theme are used together, especially when the product variations are known at compile time.
- When dealing with a complex system where different subsystems or modules need to create related objects, an Abstract Factory can encapsulate these creation responsibilities, simplifying the object creation process and improving code maintainability.
The Abstract Factory Pattern isolates client code from concrete implementation classes, facilitating the exchange of object families. It promotes consistency among objects.
Usage of the Abstract Factory Pattern
- When the system needs to be independent of the creation, composition, and representation of its objects.
- When a family of related objects must be used together, enforcing this constraint.
- When you want to provide a library of objects that hides implementations and only exposes interfaces.
- When the system needs to be configured with one of multiple families of objects.
- When The client code is kept separate from concrete (implementation) classes by using the Abstract Factory Pattern.
- It facilitates object-family exchanges.
- It encourages uniformity between objects.
Let's look at a real-world example of Abstract Factory. Our requirement is to create database connections and execute commands for different types of databases (SQL Server, PostgreSQL, and Oracle).In this scenario, it is recommended to utilize the Abstract Factory design pattern.
Abstract Factory Components
èProduct Interfaces
- IDatabaseConnection
- Defines the SetConnection() method for database connections.
- IDatabaseCommand
- Defines the ExecuteCommand() method for database commands.
è Concrete Product Classes
- SqlServerConnection
- Implements IDatabaseConnection.
- Provides implementation for setting up a connection to SQL Server.
- PostgreSqlConnection
- Implements IDatabaseConnection.
- Provides implementation for setting up a connection to PostgreSQL.
- OracleConnection
- Implements IDatabaseConnection.
- Provides implementation for setting up a connection to Oracle.
- SqlServerCommand
- Implements IDatabaseCommand.
- Provides implementation for executing a command in SQL Server.
- PostgreSqlCommand
- Implements IDatabaseCommand.
- Provides implementation for executing a command in PostgreSQL.
- OracleCommand
- Implements IDatabaseCommand.
- Provides implementation for executing a command in Oracle.
èAbstract Factory Interface
IDatabaseProcessFactory: Defines methods CreateDatabaseConnection() and CreateDatabaseCommand() to create abstract products.
èConcrete Factory Classes
- SqlServerDbFactory
- Implements IDatabaseProcessFactory.
- Creates SqlServerConnection and SqlServerCommand.
- PostgreSqlDbFactory
- Implements IDatabaseProcessFactory.
- Creates PostgreSqlConnection and PostgreSqlCommand.
- OracleDbFactory
- Implements IDatabaseProcessFactory.
- Creates OracleConnection and OracleCommand.
èDatabaseClient Class
- AbstractDatabaseClient
- Uses IDatabaseProcessFactory to create and manage IDatabaseConnection and IDatabaseCommand.
- Contains the method GetAllProcess() to set up the connection and execute the command.
A diagrammatic representation of all the components of the Abstract Factory Design Pattern.
Implementation of Abstract Factory Pattern
Step 1. The Product Interfaces define the methods that the concrete product classes must implement. Create Product Interfaces like below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DesignPatternExamples.AbstractFactory
{
public interface IDatabaseConnection
{
void SetConnection(); // This interface defines a method for setting up a database connection
}
public interface IDatabaseCommand
{
void ExecuteCommand(); // This interface defines a method for executing a database command.
}
}
Step 2. The concrete product classes implement these interfaces to provide specific functionalities for different types of databases (SQL Server, PostgreSQL, Oracle). Create a concrete implementation of product interfaces like below.
- Implements IDatabaseConnection, IDatabaseCommand.
- Provides the logic to establish a connection and execute a command with different types of databases(SQL Server, PostgreSQL, Oracle) databases.
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DesignPatternExamples.AbstractFactory
{
// Concrete Product for SQL Server Connection
public class SqlServerConnection : IDatabaseConnection
{
public void SetConnection()
{
Console.WriteLine("Establishing a connection with SQL Server database...");
}
}
// Concrete Product for PostgreSQL Connection
public class PostgreSqlConnection : IDatabaseConnection
{
public void SetConnection()
{
Console.WriteLine("Establishing a connection with PostgreSql database...");
}
}
// Concrete Product for Oracle Connection
public class OracleConnection : IDatabaseConnection
{
public void SetConnection()
{
Console.WriteLine("Establishing a connection with Oracle database...");
}
}
// Concrete Product for SQL Server Command
public class SqlServerCommand : IDatabaseCommand
{
public void ExecuteCommand()
{
Console.WriteLine("Running SQL Server command...");
}
}
// Concrete Product for PostgreSQL Command
public class PostgreSqlCommand : IDatabaseCommand
{
public void ExecuteCommand()
{
Console.WriteLine("Running PostgreSQL command...");
}
}
// Concrete Product for Oracle Command
public class OracleCommand : IDatabaseCommand
{
public void ExecuteCommand()
{
Console.WriteLine("Running Oracle command...");
}
}
}
The IDatabaseConnection and IDatabaseCommand interfaces establish the guidelines that all concrete product classes need to adhere to.
- Specific connection logic is provided by concrete classes such as SqlServerConnection, PostgreSqlConnection, and OracleConnection through the implementation of the IDatabaseConnection interface.
- Concrete classes like SqlServerCommand, PostgreSqlCommand, and OracleCommand implement the IDatabaseCommand interface to offer specific command execution logic.
- This approach enables the system to generate various database connections and commands consistently, guaranteeing conformity to a shared interface.
Step 3. The Abstract Factory Interface outlines a series of procedures for generating abstract products (specifically, database connections and commands). This interface establishes an agreement for producing interconnected objects without explicitly stating their specific classes. Create an Abstract Factory Interface like below.
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DesignPatternExamples.AbstractFactory
{
public interface IDatabaseProcessFactory
{
IDatabaseConnection CreateDatabaseConnection();
IDatabaseCommand CreateDatabaseCommand();
}
}
The interface IDatabaseProcessFactory declares two methods.
- CreateDatabaseConnection: This method is responsible for creating and returning an instance of a class that implements the IDatabaseConnection interface.
- CreateDatabaseCommand: This method is responsible for creating and returning an instance of a class that implements the IDatabaseCommand interface.
Explanation of the above implementation
- The purpose of the CreateDatabaseConnection() method is to generate an actual object of a class that follows the IDatabaseConnection interface. The concrete factory classes such as SqlServerDbFactory, PostgreSqlDbFactory, and OracleDbFactory will be responsible for supplying the exact implementation for this method, producing objects like SqlServerConnection, PostgreSqlConnection, or OracleConnection.
- The CreateDatabaseCommand() method is designed to produce a specific object of a class that adheres to the IDatabaseCommand interface. The concrete factory classes will be responsible for providing the precise implementation for this method, resulting in objects like SqlServerCommand, PostgreSqlCommand, or OracleCommand.
Step 4. Implementations of the concrete factories that implement the IDatabaseProcessFactory interface. Create Concrete Factory Classes like below.
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DesignPatternExamples.AbstractFactory
{
// Concrete Factory for SQL Server
public class SqlServerDbFactory : IDatabaseProcessFactory
{
public IDatabaseCommand CreateDatabaseCommand()
{
return new SqlServerCommand();
}
public IDatabaseConnection CreateDatabaseConnection()
{
return new SqlServerConnection();
}
}
// Concrete Factory for PostgreSQL
public class PostgreSqlDbFactory : IDatabaseProcessFactory
{
public IDatabaseCommand CreateDatabaseCommand()
{
return new PostgreSqlCommand();
}
public IDatabaseConnection CreateDatabaseConnection()
{
return new PostgreSqlConnection();
}
}
// Concrete Factory for Oracle
public class OracleDbFactory : IDatabaseProcessFactory
{
public IDatabaseCommand CreateDatabaseCommand()
{
return new OracleCommand();
}
public IDatabaseConnection CreateDatabaseConnection()
{
return new OracleConnection();
}
}
}
Explanation of the above implementation
- The IDatabaseProcessFactory interface defines a blueprint for creating related objects (database connections and commands) without specifying their concrete classes.
- This allows the system to be flexible and scalable, as new database types can be added by simply creating new concrete factories that implement the IDatabaseProcessFactory interface.
- Concrete factory classes implement the IDatabaseProcessFactory interface to create specific instances of database connections and commands for different database systems.
Step 5. The AbstractDatabaseClient class encapsulates the client code in the given example. It leverages the Abstract Factory pattern to handle database connections and commands without requiring knowledge of the specific product types. This approach enhances loose coupling and flexibility. Create a Client code class like below.
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DesignPatternExamples.AbstractFactory
{
public class AbstractDatabaseClient
{
private readonly IDatabaseConnection _connection;
private readonly IDatabaseCommand _command;
public AbstractDatabaseClient(IDatabaseProcessFactory factory)
{
_connection = factory.CreateDatabaseConnection();
_command = factory.CreateDatabaseCommand();
}
public void GetAllProcess()
{
_connection.SetConnection();
_command.ExecuteCommand();
}
}
}
Explanation of the implementation
- AbstractDatabaseClient: The client class that interacts with the database connections and commands.
- Constructor: Initializes the database connection and command using a factory implementing IDatabaseProcessFactory.
- GetAllProcess: Uses the created connection and command instances to perform database operations.
Step 6. Implement AbstractDatabaseClient like below.
// Using SQL Server Database Factory
IDatabaseProcessFactory sqlFactory = new SqlServerDbFactory();
AbstractDatabaseClient client = new AbstractDatabaseClient(sqlFactory);
client.GetAllProcess();
// Using PostgreSQL Database Factory
IDatabaseProcessFactory postgreFactory = new PostgreSqlDbFactory();
client = new AbstractDatabaseClient(postgreFactory);
client.GetAllProcess();
// Using Oracle Database Factory
IDatabaseProcessFactory oracleFactory = new OracleDbFactory();
client = new AbstractDatabaseClient(oracleFactory);
client.GetAllProcess();
// Output:
// Establishing a connection with SQL Server database...
// Running SQL Server command...
// Establishing a connection with PostgreSQL database...
// Running PostgreSQL command...
// Establishing a connection with Oracle database...
// Running Oracle command...
Working process of Abstract Factory
- Interfaces (IDatabaseConnection, IDatabaseCommand, IDatabaseProcessFactory)
- Define the abstract methods that concrete products and factories must implement.
- Concrete Products (SqlServerConnection, PostgreSqlConnection, OracleConnection, SqlServerCommand, PostgreSqlCommand, OracleCommand):
- Implement the methods defined in the interfaces for specific databases.
- Concrete Factories (SqlServerDbFactory, PostgreSqlDbFactory, OracleDbFactory)
- Implement the factory interface to create the concrete products.
- Client (AbstractDatabaseClient)
- Uses the factory to create and work with the products without needing to know the specific types of the products it is dealing with. This makes the client code more flexible and scalable.
By using the Abstract Factory pattern, the system becomes flexible and extensible, allowing new database types to be added with minimal changes to the client code.
Repository Path: https://github.com/OmatrixTech/DesignPatterExamples
To be continued.