None of the answers really explain the Abstract Factory particularly well - probably because the concept is abstract and this is less frequently used in practice.
An easy to understand example arrises from considering the following situation.
You have a system, which interfaces with another system. I will use the example given in Design Patterns Explained by Shalloway and Trott, P194, because this situation is so rare I can't think of a better one. In their book, they give the example of having different combinations of local hardware resources. They use, as examples:
- System with high resoltion display and print driver
- System with low resolution display and print driver
There are 2 options for one variable thing (print driver, display driver), and 2 options for the other variable thing (high resolution, low resolution). We want to couple these together in such a way that we have a HighResolutionFactory
and a LowResolutionFactory
which produce for us both a print driver and display driver of the correct type.
This then is the Abstract Factory pattern:
class ResourceFactory
{
virtual AbstractPrintDriver getPrintDriver() = 0;
virtual AbstractDisplayDriver getDisplayDriver() = 0;
};
class LowResFactory : public ResourceFactory
{
AbstractPrintDriver getPrintDriver() override
{
return LowResPrintDriver;
}
AbstractDisplayDriver getDisplayDriver() override
{
return LowResDisplayDriver;
}
};
class HighResFactory : public ResourceFactory
{
AbstractPrintDriver getPrintDriver() override
{
return HighResPrintDriver;
}
AbstractDisplayDriver getDisplayDriver() override
{
return HighResDisplayDriver;
}
};
I won't detail both the print driver and display driver hierachies, just one will suffice to demonstrate.
class AbstractDisplayDriver
{
virtual void draw() = 0;
};
class HighResDisplayDriver : public AbstractDisplayDriver
{
void draw() override
{
// do hardware accelerated high res drawing
}
};
class LowResDisplayDriver : public AbstractDisplayDriver
{
void draw() override
{
// do software drawing, low resolution
}
};
Why it works:
We could have solved this problem with a bunch of if
statements:
const resource_type = LOW_RESOLUTION;
if(resource_type == LOW_RESOLUTION)
{
drawLowResolution();
printLowResolution();
}
else if(resource_type == HIGH_RESOLUTION)
{
drawHighResolution();
printHighResolution();
}
Instead of this we now can do:
auto factory = HighResFactory;
auto printDriver = factory.getPrintDriver();
printDriver.print();
auto displayDriver = factory.getDisplayDriver();
displayDriver.draw();
Essentially - We have abstracted away the runtime logic into the v-table for our classes.
My opinion on this pattern is that it isn't actually very useful. It couples together some things which need not be coupled together. The point of design patterns is usally to reduce coupling, not increase it, therefore in some ways this pattern is really an anti-pattern, but it might be useful in some contexts.
If you get to a stage where you are seriously considering implementing this, you might consider some alternative designs. Perhaps you could write a factory which returns factories, which themselves return the objects you eventually want. I imagine this would be more flexible, and would not have the same coupling issues.
Addendum: The example from Gang of Four is similarly coupled. They have a MotifFactory
and a PMFactory
. These then produce PMWindow
, PMScrollBar
and MotifWindow
, MotifScrollBar
respectively. This is a somewhat dated text now and so it might be hard to understand the context. I recall reading this chapter and the I understood little from the example beyond having two implementations of a factory base class which return different families of objects.