Definition

We defined the responsibility of the Factory Pattern in a previous article to be creating objects without exposing the creation logic to the code that requires the object to be created.

The Abstract Factory Pattern is not that much of a difference regarding responsibilities. The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes or constructors, which makes that pattern, of course, fall under the creational category.

As discussed before, the Factory Pattern takes care of creating objects from the same family. When we have constructors that relate to each other such as Car, Bus, and Truck, we define a default VehicleFactory that take care of creating objects of those constructors without dealing with the creation ourselves. So the client code will be clean and unaware of the creation tidbits.

Now the Abstract Factory Pattern, as mentioned, will provide a constructor for creating families of related objects, without specifying concrete classes or constructors. If that's too abstract (pun intended), let's take an example.

Example

Let's assume there is a factory (also pun intended) that produces different parts for cars such as wheels, doors, and hoods for multiple different models of Porsche brand such as Cayman, Boxster, and Panamera. The machine that creates those parts is the Abstract Factory in this case.

function carFactory() {
    this.createCar = function (model) {
        var car;

        switch(model) {
            case 'Cayman':
                car = new Cayman();
                break;
            case 'Boxster':
                car = new Boxster();
                break;
            case 'Panamera':
                car = new Panamera();
                break;
            default:
                car = new Cayman();
                break;
        }
        
        if (typeof car.printModel === 'undefined') {
            car.printModel = function () {
                console.log('This car model is:', car.model);
            }
        }
 
        return car;
    }
}

function Cayman() {
    this.model = 'Cayman';

    this.createDoor = function (side) {
        return CaymanDoor(side);
    };

    this.createHood = function () {
        return CaymanHood();
    };
}

function Boxster() {
    this.model = 'Boxster';

    this.createDoor = function (side) {
        return BoxsterDoor(side);
    };

    this.createHood = function () {
        return BoxsterHood();
    };
}

function Panamera() {
    this.model = 'Panamera';

    this.createDoor = function (side) {
        return PanameraDoor(side);
    };

    this.createHood = function () {
        return PanameraHood();
    };
}

function CaymanDoor(side) {
    build = function() {
        console.log(`Build a ${side} door for Cayman`);
    }

    return {
        build: build
    }
}

function BoxsterDoor(side) {
    build = function() {
        console.log(`Build a ${side} door for Boxster`);
    }

    return {
        build: build
    }
}

function PanameraDoor(side) {
    build = function() {
        console.log(`Build a ${side} door for Panamera`);
    }

    return {
        build: build
    }
}

function CaymanHood() {
    build = function() {
        console.log(`Build a hood for Cayman`);
    }

    return {
        build: build
    }
}

function BoxsterHood() {
    build = function() {
        console.log(`Build a hood for Boxster`);
    }

    return {
        build: build
    }
}

function PanameraHood() {
    build = function() {
        console.log(`Build a hood for Panamera`);
    }

    return {
        build: build
    }
}

var factory = new carFactory();

var boxsterCar = factory.createCar('Boxster');

boxsterCar.printModel();

boxsterCar.createDoor('right').build();
boxsterCar.createDoor('left').build();

boxsterCar.createHood().build();

You might notice that there is a lot of repetitive pieces of code here, but the intention was to show a use case of the Abstract Factory Pattern. Also, in the real world, the build, createDoor, and createHood methods are different than these simple implementation.