DEV Community

Higor Diego
Higor Diego

Posted on

Pattern - Abstract Factory

Abstract Factory

The Abstract Factory design pattern was formalized and described as one of the 23 GoF (Gang of Four) patterns in the book "Design Patterns: Elements of Reusable Object-Oriented Software" written by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides in 1994.

The Abstract Factory pattern is a software design pattern that allows you to create families of related or dependent objects without specifying their concrete classes. It's similar to the Factory Method pattern, but it's one level up, that is, instead of creating just one object, the Abstract Factory pattern creates a series of related objects.

The Abstract Factory has an interface to create each type of object you want, but it lets the subclasses decide which concrete classes to use. This allows you to change the family of created objects without affecting the classes that use those objects.

An example use of Abstract Factory would be creating themes for an application, where there are many different themes with buttons, dialogs and other UI elements. Each theme is a family of related objects, and Abstract Factory would allow you to create a new family of objects for a new theme without affecting the classes that use those objects.

The Abstract Factory is slightly more complex than the Factory Method pattern, and is used in situations where there are multiple families of related objects or dependents. It is useful for working with complex systems that can be broken down into smaller, easier-to-understand systems.

One of the main advantages of using the Abstract Factory pattern include:

  • Abstraction of object creation: As mentioned before, the Abstract Factory allows you to create objects of different types without needing to know the concrete class that will be used to create them. This allows you to change the family of created objects without affecting the classes that use those objects.
  • Scalability and flexibility: The Abstract Factory pattern helps keep code scalable and flexible. New object families can be added easily without affecting existing code.
  • Isolation of design logic: Abstract Factory allows you to isolate the design logic of objects from system logic, making them easier to modify and maintain.
  • Code reuse: The concrete classes created by Abstract Factory can be reused elsewhere in the system.

Below is an example of the Abstract Factory pattern for creating different types of database connections in a software with nodejs.


class Connection {
    constructor(type) {
        this.type = type
    }
    connect() {
        // lógica para conectar
    }
    query() {
        // lógica para fazer consultas
    }
    close() {
        // lógica para fechar a conexão
    }
}

class MySQLConnection extends Connection {
    constructor() {
        super('mysql')
    }
}

class MongoDBConnection extends Connection {
    constructor() {
        super('mongodb')
    }
}

class ConnectionFactory {
    createConnection(type) {
        switch (type) {
            case 'mysql':
                return new MySQLConnection()
            case 'mongodb':
                return new MongoDBConnection()
            default:
                throw new Error('Unknown connection type')
        }
    }
}

const factory = new ConnectionFactory()
const mysqlConnection = factory.createConnection('mysql')
mysqlConnection.connect()
mysqlConnection.query('SELECT * FROM users')
mysqlConnection.close()

const mongodbConnection = factory.createConnection('mongodb')
mongodbConnection.connect()
mongodbConnection.query({})
mongodbConnection.close()

Enter fullscreen mode Exit fullscreen mode

The "Connection" class is an abstract class that defines the interface for database connections. It has methods for connecting, querying, and closing the connection.

The "MySQLConnection" and "MongoDBConnection" classes are the concrete classes that extend the "Connection" class and implement the specific logic for each type of connection.

They also define the connection type by calling super in the constructor.

The "ConnectionFactory" class is the Abstract Factory itself, it has the "createConnection" method which is responsible for creating the connection instances according to the passed type.

At the end of the code, an instance of the "ConnectionFactory" class is created and the createConnection method is called, passing 'mysql' or 'mongodb' and thus a specific connection instance is created for each type. After that, the connect, query and close methods are called for the created instances.

Simple, right?

Imagine another scenario in which your team manager informed you that there is a need to build a dashboard whose purpose is to list users and their requests through an internal API.

To solve the following problem follow the example below:

class API {
    constructor(baseURL) {
        this.baseURL = baseURL;
    }

    fetch(endpoint) {
        return fetch(`${this.baseURL}/${endpoint}`)
                    .then(response => response.json())
    }
}

class UsersAPI extends API {
    constructor() {
        super('https://my-app.com/api/users');
    }
    getUsers() {
        return this.fetch('users');
    }
}

class OrdersAPI extends API {
    constructor() {
        super('https://my-app.com/api/orders');
    }
    getOrders() {
        return this.fetch('orders');
    }
}

class APIFactory {
    createAPI(type) {
        switch (type) {
            case 'users':
                return new UsersAPI();
            case 'orders':
                return new OrdersAPI();
            default:
            throw new Error('Unknown API type');
        }
    }
}

const factory = new APIFactory();
const usersAPI = factory.createAPI('users');
usersAPI.getUsers().then(console.log);

const ordersAPI = factory.createAPI('orders');
ordersAPI.getOrders().then(console.log);

Enter fullscreen mode Exit fullscreen mode

The API class is an abstract class that defines the interface for the APIs. It has a constructor that receives the API's base URL and a fetch method that makes a GET request to the API, passing the specific endpoint.

The UsersAPI and OrdersAPI classes are concrete classes that extend the API class and implement logic specific to each API type. They also define the base URL of the API through the call to super() in the constructor.

The APIFactory class is the Abstract Factory itself, it has the createAPI method which is responsible for creating API instances according to the passed type.

At the end of the code, an instance of the APIFactory class is created and the createAPI method is called, passing 'users' or 'orders' and thus a specific API instance is created for each type. After that, the getUsers() or getOrders() method are called for the created instances, and these methods already return a Promise that handles the API return.

You can use the Abstract Factory pattern when:

  • You have several families of related or dependent objects, and you need to create objects from these families without specifying their concrete classes;
  • You want to provide a simple way to create objects, but you want to hide your application's creation logic;
  • You want to provide a way to change the families of created objects without affecting the classes that use those objects;
  • When there are multiple combinations of object types and structures, and these combinations need to be created in an organized way;
  • If you have a complex system that can be broken down into smaller, easier-to-understand systems, and you want to encapsulate the logic behind creating these smaller systems;
  • If you are working with object-oriented systems.

Conclusion

This pattern helps keep code scalable and flexible, as new object families can be added easily without affecting existing code. It also allows the object creation logic to be isolated from the system logic, making it easier to modify and maintain.

Hope this helps, until next time.

Top comments (0)