The abstract factory pattern is useful when we have families of related classes, and we want to instantiate them without relying on the implementation. However, what's wrong with using the factory method pattern in such a situation?
Let's say that we want to construct cross-platform UI elements, e.g. TextBox
and Button
for Windows and macOS and treat them abstractly. This is the typical situation in which we use the abstract factory pattern, and we can do so by defining the following:
AbstractUIElementsFactory
interfaceWindowsUIElementsFactory
implementsAbstractUIElementsFactory
MacUIElementsFactory
implementsAbstractUIElementsFactory
TextBox
abstract classMacTextBox
extendsTextBox
WindowsTextBox
extendsTextBox
Button
abstract classMacButton
extendsButton
WindowsButton
extendsButton
and the application would decide which concrete factory to create (based on some OS discovery mechanism) and pass it to a UIApplication
class, which instantiates a TextBox
and a Button
, and calls display
on them (which are abstract methods that simply return a String
).
The code for this situation:
package abstractFactory;
abstract class Button {
public abstract void display();
}
class MacButton extends Button {
public void display() {
System.out.println("macButton");
}
}
class WindowsButton extends Button {
@Override
public void display() {
System.out.println("winButton");
}
}
abstract class TextBox {
public abstract void display();
}
class MacTextBox extends TextBox {
@Override
public void display() {
System.out.println("macTextBox");
}
}
class WinTextBox extends TextBox {
@Override
public void display() {
System.out.println("winTextBox");
}
}
interface UICreatorAbstractFactory {
Button getButton();
TextBox getTextBox();
}
class MacFactory implements UICreatorAbstractFactory {
@Override
public Button getButton() {
return new MacButton();
}
@Override
public TextBox getTextBox() {
return new MacTextBox();
}
}
class WindowsFactory implements UICreatorAbstractFactory {
@Override
public Button getButton() {
return new WindowsButton();
}
@Override
public TextBox getTextBox() {
return new WinTextBox();
}
}
class UIApplication {
private UICreatorAbstractFactory factory;
UIApplication(UICreatorAbstractFactory _factory) {
factory = _factory;
}
public void displayUI() {
factory.getButton().display();
factory.getTextBox().display();
}
}
public class Main {
public static void main(String[] args) {
new UIApplication(new MacFactory()).displayUI();
}
}
This implementation allows us to get UI elements transparently from factory implementations and also UI elements implementations, which is largely why we would use the pattern.
Using the same TextBox
, Button
, and their derivatives, we can have a factory method implementation with two factory methods in the creator, UICreator
, each of which returns an abstract UI element. And we derive the creator and make two specializations WindowsUICreator
, and MacUICreator
, and each of which returns the appropriate concrete UI element, as follows:
abstract class UICreator {
public void displayUI() {
getButton().display();
getTextBox().display();
}
protected abstract Button getButton();
protected abstract TextBox getTextBox();
}
class WindowsUICreator extends UICreator {
@Override
protected Button getButton() {
return new WindowsButton();
}
@Override
protected TextBox getTextBox() {
return new WinTextBox();
}
}
class MacUICreator extends UICreator {
@Override
protected Button getButton() {
return new MacButton();
}
@Override
protected TextBox getTextBox() {
return new MacTextBox();
}
}
public class Main {
public static void main(String[] args) {
new MacUICreator().displayUI();
}
}
What are the downsides of this design? I believe it provides the needed decoupling by not having to deal with any concrete classes, and also provides the proper extensibility in the sense that we can introduce new UI elements and give them new factory methods, or newly supported OSs and implement concrete creators for them. And if we can use the factory method pattern in the exact situation the abstract factory pattern was designed for, I don't understand why do we have it at all?