Explore concise summaries of the 23 Gang of Four design patterns, including intent, participants, collaborations, consequences, and sample code in Java.
The Gang of Four (GoF) design patterns are a set of 23 foundational patterns for object-oriented software design, introduced by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides in their seminal book, “Design Patterns: Elements of Reusable Object-Oriented Software.” These patterns are divided into three categories: Creational, Structural, and Behavioral. This section provides a concise summary of each pattern, including its intent, participants, collaborations, consequences, and a sample Java code snippet to illustrate its application.
Creational patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.
Category: Creational
Intent: Ensure a class has only one instance and provide a global point of access to it.
Participants:
getInstance
method.Collaborations: The Singleton class itself manages its unique instance.
Consequences: Controlled access to the sole instance, but can introduce global state and complicate testing.
Sample Code:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Category: Creational
Intent: Define an interface for creating an object, but let subclasses alter the type of objects that will be created.
Participants:
Collaborations: Creator relies on its subclasses to define the factory method.
Consequences: Provides hooks for subclasses, but may require subclassing just to instantiate a particular class.
Sample Code:
abstract class Product {}
class ConcreteProduct extends Product {}
abstract class Creator {
public abstract Product factoryMethod();
}
class ConcreteCreator extends Creator {
@Override
public Product factoryMethod() {
return new ConcreteProduct();
}
}
Category: Creational
Intent: Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
Participants:
Collaborations: Concrete factories produce a family of products that are compatible.
Consequences: Isolates concrete classes, but can make it difficult to support new kinds of products.
Sample Code:
interface AbstractFactory {
AbstractProduct createProduct();
}
class ConcreteFactory implements AbstractFactory {
public AbstractProduct createProduct() {
return new ConcreteProduct();
}
}
interface AbstractProduct {}
class ConcreteProduct implements AbstractProduct {}
Category: Creational
Intent: Separate the construction of a complex object from its representation so that the same construction process can create different representations.
Participants:
Collaborations: The client creates a Director object and configures it with a Builder object.
Consequences: Provides control over the construction process, but requires a separate ConcreteBuilder for each type of product.
Sample Code:
class Product {
private String partA;
private String partB;
public void setPartA(String partA) { this.partA = partA; }
public void setPartB(String partB) { this.partB = partB; }
}
abstract class Builder {
protected Product product = new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public Product getResult() { return product; }
}
class ConcreteBuilder extends Builder {
public void buildPartA() { product.setPartA("Part A"); }
public void buildPartB() { product.setPartB("Part B"); }
}
class Director {
private Builder builder;
public Director(Builder builder) { this.builder = builder; }
public void construct() {
builder.buildPartA();
builder.buildPartB();
}
}
Category: Creational
Intent: Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
Participants:
Collaborations: A client asks a prototype to clone itself.
Consequences: Adds flexibility in terms of object creation, but can be complex to implement with deep copies.
Sample Code:
interface Prototype {
Prototype clone();
}
class ConcretePrototype implements Prototype {
public Prototype clone() {
return new ConcretePrototype();
}
}
Structural patterns concern class and object composition. They use inheritance to compose interfaces and define ways to compose objects to obtain new functionality.
Category: Structural
Intent: Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
Participants:
Collaborations: The client calls operations on an Adapter instance. In turn, the adapter calls Adaptee operations that carry out the request.
Consequences: Allows classes with incompatible interfaces to work together, but can introduce additional complexity.
Sample Code:
interface Target {
void request();
}
class Adaptee {
public void specificRequest() {
System.out.println("Specific request");
}
}
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
}
Category: Structural
Intent: Decouple an abstraction from its implementation so that the two can vary independently.
Participants:
Collaborations: Abstraction forwards client requests to its Implementor object.
Consequences: Decouples interface and implementation, but increases complexity.
Sample Code:
interface Implementor {
void operationImpl();
}
class ConcreteImplementorA implements Implementor {
public void operationImpl() {
System.out.println("ConcreteImplementorA operation");
}
}
abstract class Abstraction {
protected Implementor implementor;
protected Abstraction(Implementor implementor) {
this.implementor = implementor;
}
public abstract void operation();
}
class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
public void operation() {
implementor.operationImpl();
}
}
Category: Structural
Intent: Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
Participants:
Collaborations: Clients use the Component interface to interact with objects in the composition.
Consequences: Simplifies client code, but can make the design overly general.
Sample Code:
interface Component {
void operation();
}
class Leaf implements Component {
public void operation() {
System.out.println("Leaf operation");
}
}
class Composite implements Component {
private List<Component> children = new ArrayList<>();
public void add(Component component) {
children.add(component);
}
public void operation() {
for (Component child : children) {
child.operation();
}
}
}
Category: Structural
Intent: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Participants:
Collaborations: Decorators forward requests to their Component and can perform additional actions before or after forwarding.
Consequences: More flexible than inheritance, but can result in many small objects.
Sample Code:
interface Component {
void operation();
}
class ConcreteComponent implements Component {
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation();
addedBehavior();
}
private void addedBehavior() {
System.out.println("Added behavior");
}
}
Category: Structural
Intent: Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
Participants:
Collaborations: Clients communicate with the subsystem through the Facade.
Consequences: Simplifies use of the subsystem, but can hide important functionality.
Sample Code:
class SubsystemA {
public void operationA() {
System.out.println("SubsystemA operation");
}
}
class SubsystemB {
public void operationB() {
System.out.println("SubsystemB operation");
}
}
class Facade {
private SubsystemA subsystemA = new SubsystemA();
private SubsystemB subsystemB = new SubsystemB();
public void operation() {
subsystemA.operationA();
subsystemB.operationB();
}
}
Category: Structural
Intent: Use sharing to support large numbers of fine-grained objects efficiently.
Participants:
Collaborations: Clients should not instantiate ConcreteFlyweights directly.
Consequences: Reduces the number of objects, but increases complexity.
Sample Code:
interface Flyweight {
void operation(String extrinsicState);
}
class ConcreteFlyweight implements Flyweight {
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState) {
System.out.println("Intrinsic: " + intrinsicState + ", Extrinsic: " + extrinsicState);
}
}
class FlyweightFactory {
private Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
if (!flyweights.containsKey(key)) {
flyweights.put(key, new ConcreteFlyweight(key));
}
return flyweights.get(key);
}
}
Category: Structural
Intent: Provide a surrogate or placeholder for another object to control access to it.
Participants:
Collaborations: Proxy forwards requests to RealSubject.
Consequences: Adds a level of indirection, but can introduce additional complexity.
Sample Code:
interface Subject {
void request();
}
class RealSubject implements Subject {
public void request() {
System.out.println("RealSubject request");
}
}
class Proxy implements Subject {
private RealSubject realSubject;
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
realSubject.request();
}
}
Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects.
Category: Behavioral
Intent: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
Participants:
Collaborations: Each handler in the chain either handles the request or passes it to the next handler.
Consequences: Reduces coupling, but can result in unhandled requests.
Sample Code:
abstract class Handler {
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public abstract void handleRequest(String request);
}
class ConcreteHandlerA extends Handler {
public void handleRequest(String request) {
if (request.equals("A")) {
System.out.println("Handled by ConcreteHandlerA");
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
class ConcreteHandlerB extends Handler {
public void handleRequest(String request) {
if (request.equals("B")) {
System.out.println("Handled by ConcreteHandlerB");
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
Category: Behavioral
Intent: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Participants:
Collaborations: The invoker holds a command and invokes its execute method.
Consequences: Decouples the object that invokes the operation from the one that knows how to perform it, but can result in many command classes.
Sample Code:
interface Command {
void execute();
}
class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
public void execute() {
receiver.action();
}
}
class Receiver {
public void action() {
System.out.println("Receiver action");
}
}
class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void invoke() {
command.execute();
}
}
Category: Behavioral
Intent: Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
Participants:
Collaborations: The context is passed to each expression to interpret.
Consequences: Easy to change and extend the grammar, but complex grammars are hard to manage.
Sample Code:
interface Expression {
boolean interpret(String context);
}
class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data) {
this.data = data;
}
public boolean interpret(String context) {
return context.contains(data);
}
}
class OrExpression implements Expression {
private Expression expr1;
private Expression expr2;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
Category: Behavioral
Intent: Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Participants:
Collaborations: The client uses the iterator to access and traverse elements.
Consequences: Supports variations in traversal, but can be cumbersome for complex collections.
Sample Code:
interface Iterator {
boolean hasNext();
Object next();
}
class ConcreteIterator implements Iterator {
private List<Object> list;
private int position = 0;
public ConcreteIterator(List<Object> list) {
this.list = list;
}
public boolean hasNext() {
return position < list.size();
}
public Object next() {
return list.get(position++);
}
}
interface Aggregate {
Iterator createIterator();
}
class ConcreteAggregate implements Aggregate {
private List<Object> items = new ArrayList<>();
public void addItem(Object item) {
items.add(item);
}
public Iterator createIterator() {
return new ConcreteIterator(items);
}
}
Category: Behavioral
Intent: Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly.
Participants:
Collaborations: Colleagues send and receive requests from a Mediator.
Consequences: Reduces the number of communication paths, but can centralize control.
Sample Code:
interface Mediator {
void send(String message, Colleague colleague);
}
abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
}
class ConcreteColleagueA extends Colleague {
public ConcreteColleagueA(Mediator mediator) {
super(mediator);
}
public void send(String message) {
mediator.send(message, this);
}
public void receive(String message) {
System.out.println("ColleagueA received: " + message);
}
}
class ConcreteMediator implements Mediator {
private ConcreteColleagueA colleagueA;
private ConcreteColleagueB colleagueB;
public void setColleagueA(ConcreteColleagueA colleagueA) {
this.colleagueA = colleagueA;
}
public void setColleagueB(ConcreteColleagueB colleagueB) {
this.colleagueB = colleagueB;
}
public void send(String message, Colleague colleague) {
if (colleague == colleagueA) {
colleagueB.receive(message);
} else {
colleagueA.receive(message);
}
}
}
class ConcreteColleagueB extends Colleague {
public ConcreteColleagueB(Mediator mediator) {
super(mediator);
}
public void send(String message) {
mediator.send(message, this);
}
public void receive(String message) {
System.out.println("ColleagueB received: " + message);
}
}
Category: Behavioral
Intent: Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.
Participants:
Collaborations: The caretaker requests a memento from the originator, holds it, and passes it back to the originator to restore its state.
Consequences: Preserves encapsulation boundaries, but can be costly in terms of memory.
Sample Code:
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public Memento saveStateToMemento() {
return new Memento(state);
}
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
class Caretaker {
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento state) {
mementoList.add(state);
}
public Memento get(int index) {
return mementoList.get(index);
}
}
Category: Behavioral
Intent: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Participants:
Collaborations: ConcreteSubject notifies its observers whenever a change occurs.
Consequences: Promotes loose coupling, but can lead to unexpected updates.
Sample Code:
interface Observer {
void update(String state);
}
class ConcreteObserver implements Observer {
private String observerState;
public void update(String state) {
observerState = state;
System.out.println("Observer state updated to: " + observerState);
}
}
class Subject {
private List<Observer> observers = new ArrayList<>();
private String state;
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void setState(String state) {
this.state = state;
notifyObservers();
}
private void notifyObservers() {
for (Observer observer : observers) {
observer.update(state);
}
}
}
Category: Behavioral
Intent: Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
Participants:
Collaborations: Context delegates state-specific behavior to its current State object.
Consequences: Localizes state-specific behavior, but can result in many subclasses.
Sample Code:
interface State {
void handle(Context context);
}
class ConcreteStateA implements State {
public void handle(Context context) {
System.out.println("State A handling request.");
context.setState(new ConcreteStateB());
}
}
class ConcreteStateB implements State {
public void handle(Context context) {
System.out.println("State B handling request.");
context.setState(new ConcreteStateA());
}
}
class Context {
private State state;
public Context(State state) {
this.state = state;
}
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}
Category: Behavioral
Intent: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Participants:
Collaborations: Context forwards requests to its Strategy object.
Consequences: Provides flexibility in choosing algorithms, but can increase the number of objects.
Sample Code:
interface Strategy {
int execute(int a, int b);
}
class ConcreteStrategyAdd implements Strategy {
public int execute(int a, int b) {
return a + b;
}
}
class ConcreteStrategySubtract implements Strategy {
public int execute(int a, int b) {
return a - b;
}
}
class Context {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int a, int b) {
return strategy.execute(a, b);
}
}
Category: Behavioral
Intent: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
Participants:
Collaborations: ConcreteClass relies on AbstractClass to implement the invariant steps of the algorithm.
Consequences: Promotes code reuse, but can lead to a rigid class hierarchy.
Sample Code:
abstract class AbstractClass {
public final void templateMethod() {
primitiveOperation1();
primitiveOperation2();
}
protected abstract void primitiveOperation1();
protected abstract void primitiveOperation2();
}
class ConcreteClass extends AbstractClass {
protected void primitiveOperation1() {
System.out.println("ConcreteClass operation1");
}
protected void primitiveOperation2() {
System.out.println("ConcreteClass operation2");
}
}
Category: Behavioral
Intent: Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
Participants:
Collaborations: ConcreteElement calls the Visitor operation that corresponds to its class.
Consequences: Makes adding new operations easy, but can make adding new ConcreteElement classes difficult.
Sample Code:
interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
class ConcreteVisitor implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("Visited ConcreteElementA");
}
public void visit(ConcreteElementB element) {
System.out.println("Visited ConcreteElementB");
}
}
interface Element {
void accept(Visitor visitor);
}
class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
This summary of the Gang of Four design patterns provides a quick reference to the essential characteristics of each pattern. For a deeper understanding, readers are encouraged to refer to the main chapters of this book, where each pattern is explored in detail with comprehensive examples and real-world applications.