Design patterns are typical solutions to commonly occurring problems in software design. They are like pre-made blueprints that can be customized to solve a recurring design problem in any code.
All patterns can be categorized by their intent, or purpose.
Creational Patterns
This pattern provides ways to create object creation in a manner that increases flexibility and reuse of code.
Factory Method
This pattern provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
Instead of new
keyword, this pattern uses a method to instantiate the object.
Example: Generate buttons for cross platform apps.
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public class ShapeFactory {
// variables, constructors, etc
public Shape createShape(String type) {
switch(type) {
case "CIRCLE":
return new Circle();
case "SQUARE":
return new Square();
default:
return null
}
}
}
|
1
2
3
4
5
6
| public class Demo {
public static void main(String args[]) {
ShapeFactory shapeFactory = new ShapeFactory();
Shape circle = shapeFactory.createShape("CIRCLE");
}
}
|
Real world code examples:
DateTimeFormatterFactory.java
NumberFormat.java
Calendar.java
Builder
Create an object step by step using methods, rather than using a construtor. It provides a way of creating complex object in a step by step manner.
Example: adding parameters for a SQL statement
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public class Employee {
private Integer id;
private String name;
private Title jobTitle;
// other variables, constructors, etc
public Employee setId(Integer id) {
this.id = id;
return this;
}
public Employee setName(String name) {
this.name = name;
return this;
}
public Employee setJobTitle(Title jobTitle) {
this.jobTitle = jobTitle;
return this;
}
}
|
1
2
3
4
5
6
| public class Demo {
public static void main(String args[]) {
Employee employee = new Employee();
employee.setId(123).setName("John Doe").setJobTitle(Title.RECEPTIONIST)));
}
}
|
Real world code examples:
StringBuilder.java
MapSqlParameterSource.java
Prototype
It simply clones an object. Used as a way to give inheritance without the complexity of having many classes inherit from each other.
Example: Create environment specific confg
Java
1
2
3
4
| public interface Shape {
public Shape clone();
public void draw();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public class Circle implements Shape, Cloneable {
public int radius;
public Circle() {}
// copy constructor
public Circle(Circle target) {
super(target);
if (target != null) {
this.radius = target.radius;
}
}
@Override
public Shape clone() {
return new Circle(this);
}
@Override
public void draw() {
System.out.println("Drawing a circle.");
}
}
|
1
2
3
4
5
6
7
| public class Demo {
public static void main(String args[]) {
Circle circle = new Circle();
circle.radius = 15;
Circle anotherCircle = (Circle) circle.clone();
}
}
|
A real world code example:
CLICommand.java
CLIAction.java
Singleton
It can only be instantiated once.
Examples: logger, database config, settings, etc
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // thread safe
public final class Singleton {
private static volatile Singleton instance;
public String value;
private Singleton() {}
public static Singleton getInstance() {
Singleton result = instance;
if (result != null) {
return result;
}
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
}
|
1
2
3
4
5
| public class Demo {
public static void main(String args[]) {
Singleton singleton = Singleton.getInstance();
}
}
|
Real world code examples:
SQLErrorCodesFactory.java
Runtime.java
Structural Patterns
This pattern explains how to assemble objects and classes into larger structures, while keeping these structures flexible and efficient.
Facade
High level class/method/API to hide complexity in the code base. A facade class contains the low level systems as dependencies which then simplifies their operation.
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class AudioFile {
private String name;
private String codecType;
public AudioFile(String name) {
this.name = name;
this.codecType = name.substring(name.indexOf(".") + 1);
}
public String getCodecType() {
return codecType;
}
public String getName() {
return name;
}
}
|
1
| public interface Codec {}
|
1
2
3
| public class MP3Codec implements Codec {
public String type = "mp3";
}
|
1
2
3
| public class FlacCodec implements Codec {
public String type = "flac";
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| public class CodecFactory {
public static Codec extract(AudioFile file) {
String type = file.getCodecType();
switch(type) {
case "mp3":
return new MP3Codec();
case "flac":
return new FlacCodec();
default:
return null
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
| public class BitrateReader {
public static AudioFile read(AudioFile file, Codec codec) {
System.out.println("BitrateReader: reading file...");
return file;
}
public static AudioFile convert(AudioFile buffer, Codec codec) {
System.out.println("BitrateReader: writing file...");
return buffer;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public class AudioConversionFacade {
public File convertVideo(String fileName, String format) {
AudioFile file = new AudioFile(fileName);
Codec sourceCodec = CodecFactory.extract(file);
Codec destinationCodec;
if (format.equals("mp3")) {
destinationCodec = new MP3Codec();
} else {
destinationCodec = new FlacCodec();
}
AudioFile buffer = BitrateReader.read(file, sourceCodec);
AudioFile result = BitrateReader.convert(buffer, destinationCodec);
System.out.println("VideoConversionFacade: conversion completed.");
return result;
}
}
|
Real world code examples:
JdbcClient.java
DefaultJdbcClient.java
HttpClientFacade.java
Proxy
Basically to substitute an original object with another object.
Java
1
2
3
| public interface CommandExecutor {
public void runCommand(String cmd) throws Exception;
}
|
1
2
3
4
5
6
7
8
9
| public class CommandExecutorImpl implements CommandExecutor {
@Override
public void runCommand(String cmd) throws IOException {
Runtime.getRuntime().exec(cmd);
System.out.println("'" + cmd + "' command executed.");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public class CommandExecutorProxy implements CommandExecutor {
private boolean isAdmin;
private CommandExecutor executor;
public CommandExecutorProxy(String user, String pwd) {
if("Admin".equals(user) && "@dmin".equals(pwd)) isAdmin=true;
executor = new CommandExecutorImpl();
}
@Override
public void runCommand(String cmd) throws Exception {
if(isAdmin) {
executor.runCommand(cmd);
} else if(cmd.trim().startsWith("rm")){
throw new Exception("rm command is not allowed for non-admin users.");
} else {
executor.runCommand(cmd);
}
}
}
|
1
2
3
4
5
6
7
| public class Demo {
public static void main(String args[]) {
CommandExecutor executor = new CommandExecutorProxy("user", "user!");
executor.runCommand("ls -ltr");
executor.runCommand("rm -rf abc.pdf");
}
}
|
Real world examples:
SessionProxy.java
EventListenerProxy.java
Behavioral Patterns
This pattern takes care of effective communication and the assignment of responsibilities between objects.
Iterator
To iterate over collections in loops.
Java
1
2
3
| public enum Title {
SOFTWARE_ENGINEER, SENIOR_SOFTWARE_ENGINEER, MANAGER;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| public class Employee {
private Integer id;
private String name;
private Title jobTitle;
public Employee(int id, String name, Title title) {
this.id = id;
this.name = name;
this.title = title;
}
// other variables, constructors, getter, setter, etc
}
|
1
2
3
4
5
| public interface EmployeeCollection {
public void addEmployee(Employee employee);
public void removeEmployee(Employee employee);
public EmployeeIterator iterator(Title title);
}
|
1
2
3
4
| public interface EmployeeIterator {
public boolean hasNext();
public Employee next();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| public class EmployeeCollectionImpl implements EmployeeCollection {
private List<Employee> employees;
public EmployeeCollectionImpl() {
employees = new ArrayList<>();
}
@Override
public void addEmployee(Employee employee) {
this.employees.add(employee);
}
@Override
public void removeEmployee(Employee employee) {
this.employees.remove(employee);
}
@Override
public EmployeeIterator iterator(Title title) {
return new EmployeeIteratorImpl(title, this.employees);
}
private class EmployeeIteratorImpl implements EmployeeIterator {
private Title title;
private List<Employee> employees;
private int position;
public EmployeeIteratorImpl(Title title, List<Employee> employeeList) {
this.title = title;
this.employees = employeeList;
}
@Override
public boolean hasNext() {
while (position < employees.size()) {
Employee employee = employees.get(position);
if (employee.getTitle().equals(this.title)
return true;
else
position++;
}
return false;
}
@Override
public Employee next() {
Employee employee = employees.get(position);
position++;
return employee;
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| public class Demo {
public static void main(String args[]) {
EmployeeCollection employees = new EmployeeCollectionImple();
employees.addEmployee(new Employee(1, "John Doe", Title.SOFTWARE_ENGINEER));
employees.addEmployee(new Employee(2, "Jane Doe", Title.MANAGER));
EmployeeIterator iterator = employees.iterator(Title.MANAGER);
while (iterator.hasNext()) {
Employee employee = iterator.next();
System.out.println(employee.toString());
}
}
}
|
Real world examples:
ArrayList.java
HashMap.java
Observer
Used by several objects to subscribe to changes to a particular object. (one to many)
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| public class EventManager {
Map<String, List<EventListener>> listeners new HashMap<>();
public EventManager(String... eventTypes) {
for (String eventType : eventTypes) {
this.listeners.put(eventType, new ArrayList<>());
}
}
public void subscribe(String eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.add(listener);
}
public void unsubscribe(String eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.remove(listener);
}
public void notify(String eventType, File file) {
List<EventListener> users = listeners.get(eventType);
for (EventListener listener : users) {
listener.update(eventType, file);
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| public class FileEditor {
public EventManager events;
private File file;
public FileEditor() {
this.events = new EventManager("open", "save");
}
public void openFile(String filePath) {
this.file = new File(filePath);
event.notify("open", file);
}
public void saveFile() {
if (this.file != null) {
events.notify("save", file);
}
}
}
|
1
2
3
| public interface EventListener {
void update(String eventType, File file);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| public class EmailNotificationListener implements EventListener {
private String email;
public EmailNotificationListener(String email) {
this.email = email;
}
@Override
public void update(String eventType, File file) {
System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| public class LogListener implements EventListener {
private File log;
public LogListener(String fileName) {
this.log = new File(fileName);
}
@Override
public void update(String eventType, File file) {
System.out.println("Save to log " + log + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
}
}
|
1
2
3
4
5
6
7
8
9
10
| public class Demo {
public static void main(String args[]) {
Editor editor = new Editor();
editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt"));
editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));
editor.openFile("test.txt");
editor.saveFile();
}
}
|
Real world examples:
HttpSessionBindingListener.java
DestructionCallbackBindingListener.java
A middle man between two objects/classes. (many to many)
Example: Air traffic controller that sits between the runways & airplains to provide coordination & communication.
Java
1
2
3
4
| public interface Mediator {
void addUser(User user);
void sendMessage(User user, String message);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public class ChatMediator implements Mediator {
private List<User> users;
public ChatMediator() {
this.users = new ArrayList<>();
}
@Override
void addUser(User user) {
this.users.add(user);
}
@Override
void sendMessage(User sender, String message) {
for (User user : this.users) {
if (user != sender)
user.receive(message);
}
}
}
|
1
2
3
4
5
| public interface User {
void setMediator(Mediator mediator);
void send(String message);
void receive(String message);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| public class UserImpl implements User {
private Mediator mediator;
private String name;
public UserImpl(String name){
this.name = name;
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
void send(String message) {
mediator.sendMessage(this, message);
}
@Override
void receive(String message) {
System.out.println(this.name + " - Received message: " + message);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class Demo {
public static void main(String args[]) {
User user1 = new UserImpl(mediator, "John");
User user2 = new UserImpl(mediator, "Jane");
User user3 = new UserImpl(mediator, "Jake");
Mediator mediator = new ChatMediator();
mediator.addUser(user1);
mediator.addUser(user2);
mediator.addUser(user3);
user1.send("Hello!");
}
}
|
Real world examples:
ExecutorService.java
Method.java
State
Where an object behaves differently based on finite number of states.
Examples: Finite State Machine
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public class Package {
private PackageState state = new OrderedState();
public void previousState() {
state.prev(this);
}
public void nextState() {
state.next(this);
}
public void printStatus() {
state.printStatus();
}
// other variables, constructors, getter, setter, etc
}
|
1
2
3
4
5
| public interface PackageState {
void prev(Package pkg);
void next(Package pkg);
void printStatus();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class OrderedState implements PackageState {
@Override
public void prev(Package pkg) {
System.out.println("The package is in its root state.");
}
@Override
public void next(Package pkg) {
pkg.setState(new ShippedState());
}
@Override
public void printStatus() {
System.out.println("Package ordered.");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class ShippedState implements PackageState {
@Override
public void prev(Package pkg) {
pkg.setState(new OrderedState());
}
@Override
public void next(Package pkg) {
System.out.println(new DeliveredState());
}
@Override
public void printStatus() {
System.out.println("Package shipped.");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class DeliveredState implements PackageState {
@Override
public void prev(Package pkg) {
pkg.setState(new ShippedState());
}
@Override
public void next(Package pkg) {
System.out.println("Package delivered to post office, not received yet.");
}
@Override
public void printStatus() {
System.out.println("Package delivered.");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| public class Demo {
public static void main(String args[]) {
Package pkg = new Package();
pkg.printStatus();
pkg.nextState();
pkg.printStatus();
pkg.nextState();
pkg.printStatus();
}
}
|
Real world examples:
LifecycleImpl.java
Phase.java
PartGenerator.java
PartGenerator.java
References