Design Patterns
- Posted by Yomna Anwar
- On August 23, 2022
What are design patterns?
Design patterns are general solutions providing a way to solve problems that commonly arise during software development. These solutions are proven through tests, trial and errors to be effective in improving overall code efficiency and readability. A design pattern is a template or a general concept that a developer can apply while developing a software, it is not a solution that can be transformed directly into code.
Why use design patterns?
Using design patterns to aid with development can help improve code quality, accelerate the process, and prevents the need to create a new pattern every time a design problem arises.
Types of design patterns
According to a book titled ‘Design Patterns – Elements of Reusable Object-Oriented Software’ published in 1994 by four authors (Known as Gang of Four) there are 23 different design patterns. These design patterns can mainly be classified into three categories:
- Creational Design Patterns
- Structural Design Patterns
- Behavioral Design Patterns
Each category is different in terms of complexity, level of detail and the scale of system being designed.
Creational Design Patterns
Creational Design Patterns tackles how to handle the creation of new objects. These patterns increase flexibility and reusability of code.
Examples of creational design patterns include:
- Factory Method Pattern
- Singleton Pattern
- Prototype Pattern
Structural Design Patterns
Structural Design Patterns focuses on how objects connect and relate with one another. These patterns help create structures using multiple objects and classes.
Examples of structural design patterns include:
- Facade Pattern
- Adapter Pattern
- Composite Pattern
- Proxy Pattern
- Decorator Pattern
Behavioral Design Patterns
Behavioral Design Patterns addresses how each object has a different purpose and work towards a common goal. These patterns focus mainly on the communication between objects and responsibility distribution between one object and the other.
Examples of behavioral design patterns include:
- Template Method Pattern
- Chain of Responsibility Pattern
- Command Pattern
- Observer Pattern
- Strategy Pattern
Differences between similar design patterns
Strategy Pattern Vs Factory Method Pattern
Strategy Pattern
The Strategy design pattern is a type of behavioral design patterns that is used when representing multiple objects each having a different strategy of performing a behavior. In this pattern there is a context object that preforms that behavior based on desired strategy.
UML Diagram
Figure 1 Khosravi, Khashayar & Guéhéneuc, Yann-Gaël. (2022). A Quality Model for Design Patterns.
When to use Strategy Pattern?
This pattern can be used when needed to switch between different variations of algorithms performing a certain behavior on runtime.
Advantages | Disadvantages |
Easily alternate between different strategies on runtime | Clients must know the different strategies and when to use the appropriate one |
Makes code cleaner by separating different functional logic into different classes (each class has a different strategy) | Increased number of objects in an application |
Example:
// ParkingStratetgy.java public interface ParkingStratetgy { public int parkCar(int carLicenseNumber); } // ParrallelPark.java public class ParrallelPark implements ParkingStratetgy { @Override public String parkCar (int carLicenseNumber) { return “Car with license plate: “+carLicenseNumber+” parked parallelly”; } } // PerpendicularPark.java public class PerpendicularPark implements ParkingStratetgy { @Override public String parkCar (int carLicenseNumber) { return “Car with license plate: “+carLicenseNumber+” parked perpendicularly”; } } // ParkingSpace.java public class ParkingSpace { private ParkingStratetgy parkingStratetgy; public parkCar (ParkingStratetgy parkingStratetgy){ this. parkingStratetgy = parkingStratetgy; } public String parkCar(int carLicenseNumber){ return parkingStratetgy. parkCar(carLicenseNumber); } } //StrategyPatternDemo.java public class StrategyPatternDemo { public static void main(String[] args) { ParkingSpace parkingSpace = new ParkingSpace(new PerpendicularPark ()); System.out.println(parkingSpace. parkCar (123)); parkingSpace = new ParkingSpace (new ParrallelPark ()); System.out.println(parkingSpace. parkCar (756)); } } //Output // Car with license plate: 123 parked perpendicularly // Car with license plate: 756 parked parallelly
Factory Method Pattern
The Factory Method design pattern is a type of creational design patterns that is used to create an object by defining an interface that lets subclasses decide on the object type being created. This pattern abstracts creation logic from the clients.
UML Diagram
Figure 2 Fojtik, Rostislav. (2014). Design Patterns in the Teaching of Programming. Procedia – Social and Behavioral Sciences. 143. 10.1016/j.sbspro.2014.07.493.
When to use Factory Method Pattern?
This pattern can be used when wanting a class to differ instantiation to its subclasses rather than the class itself.
Advantages | Disadvantages |
Provides loose coupling | Increased number of integrated classes |
Easier addition of new classes | In some cases, clients must subclass the creator class to create a concrete product object. |
Example:
//Notification.java public interface Notification { void notifyUser(); } //SMSNotification.java public class SMSNotification implements Notification { @Override public void notifyUser() { // TODO Auto-generated method stub System.out.println("Sending an SMS notification"); } } //EmailNotification.java public class EmailNotification implements Notification { @Override public void notifyUser() { // TODO Auto-generated method stub System.out.println("Sending an e-mail notification"); } } //PushNotification.java public class PushNotification implements Notification { @Override public void notifyUser() { // TODO Auto-generated method stub System.out.println("Sending a push notification"); } } //NotificationFactory.java public class NotificationFactory { public Notification createNotification(String channel) { if (channel == null || channel.isEmpty()) return null; switch (channel) { case "SMS": return new SMSNotification(); case "EMAIL": return new EmailNotification(); case "PUSH": return new PushNotification(); default: throw new IllegalArgumentException("Unknown channel "+channel); } } } //NotificationService.java public class NotificationService { public static void main(String[] args) { NotificationFactory notificationFactory = new NotificationFactory(); Notification notification = notificationFactory.createNotification("SMS"); notification.notifyUser(); } }
Similarities between Strategy Pattern and Factory Method Pattern
Both patterns have a main class that lets subclasses or implementation classes determine the way this main class works. In the case of Factory method pattern subclasses hold the creational logic of the main class. While in Strategy pattern, classes that implement the main class determine the behavior of certain functionalities of the main class.
Differences between Strategy Pattern and Factory Method Pattern
Strategy Pattern | Factory Method Pattern |
Behavioral pattern, used to implement certain operations in different ways depending on the object type | Creational pattern, used to create objects of different types |
Based on delegation | Based on Inheritance |
Composite Pattern Vs Decorator Pattern
Composite Pattern
The Composite design pattern is composing objects into tree structures, allowing clients to interact with separate objects and compositions of objects in a similar matter.
UML Diagram
Figure 3 Chirila, Ciprian-Bogdan & Crescenzo, Pierre & Lahire, Philippe. (2005). Towards Reengineering: An Approach Based on Reverse Inheritance. Application to Java.
When to use Composite Pattern?
This pattern can be used when needed to treat a group of objects the same way as a single object.
Advantages | Disadvantages |
Can be expanded easily | Challenging implementation of component interfaces |
Accurately represents heavily nested object structures | Adjustment of composite properties is complicated |
Example:
//Department.java public interface Department { void printDepartmentName(); } //FinancialDepartment.java public class FinancialDepartment implements Department { private Integer id; private String name; public void printDepartmentName() { System.out.println(getClass().getSimpleName()); } } //SalesDepartment.java public class SalesDepartment implements Department { private Integer id; private String name; public void printDepartmentName() { System.out.println(getClass().getSimpleName()); } } //HeadDepartment.java public class HeadDepartment implements Department { private Integer id; private String name; private List childDepartments; public HeadDepartment(Integer id, String name) { this.id = id; this.name = name; this.childDepartments = new ArrayList<>(); } public void printDepartmentName() { childDepartments.forEach(Department::printDepartmentName); } public void addDepartment(Department department) { childDepartments.add(department); } public void removeDepartment(Department department) { childDepartments.remove(department); } }
Decorator Pattern
The Decorator design pattern is wrapping a class with another class that provides additional functionalities without altering the original class methods signatures.
UML Diagram
Figure 4 Kniesel, Günter & Binun, Alexander & Chatzigeorgiou, Alexander & Guéhéneuc, Yann-Gaël & Tsantalis, Nikolaos. (2009). DPDX – A Common Exchange Format for Design Pattern Detection Tools.
When to use Decorator Pattern?
This pattern is mainly used to alter project code to add functionalities without changing the existing core classes. It can also be used when wanting to reduce the number of classes needed to offer a combination of behaviors.
Advantages | Disadvantages |
Functionalities are resource optimized | Challenging debugging process |
Readable program code | Uses high number of objects |
Example:
//VisitEngine.java public interface VisitEngine { String getEngineeringOfficeId(); String getEngineeringOfficeCityId(); int getRemainingCapacity(); List calculateVisits(); } //GroupOneDecorator.java public class GroupOneDecorator implements VisitEngine { private VisitEngine engine; private int groupCapacity; private Queue groupOneLicenses; private int remainingCapacity; private List generatedVisits; public GroupOneDecorator(VisitEngine engine, int groupCapacity, Queue groupOneLicenses) { this.engine = engine; this.groupCapacity = groupCapacity; this.groupOneLicenses = groupOneLicenses; generatedVisits = new ArrayList<>(); } @Override public List calculateVisits() { List calculatedVisits = this.engine.calculateVisits(); generatedVisits.addAll(calculatedVisits); remainingCapacity += groupCapacity; return generatedVisits; } @Override public String getEngineeringOfficeId() { return engine.getEngineeringOfficeId(); } @Override public String getEngineeringOfficeCityId() { return engine.getEngineeringOfficeCityId(); } @Override public int getRemainingCapacity() { return remainingCapacity; } public int getGroupCapacity() { return this.groupCapacity; } } //GroupTwoDecorator.java public class GroupTwoDecorator implements VisitEngine { private VisitEngine engine; private int groupCapacity; private Queue groupTwoLicenses; private int remainingCapacity; private List generatedVisits; public GroupTwoDecorator(VisitEngine engine, int groupCapacity, Queue groupTwoLicenses) { this.engine = engine; this.groupCapacity = groupCapacity; this.groupTwoLicenses = groupTwoLicenses; this.generatedVisits = new ArrayList<>(); } @Override public List calculateVisits() { List calculatedVisits = this.engine.calculateVisits(); this.groupCapacity += engine.getRemainingCapacity(); generatedVisits.addAll(calculatedVisits); remainingCapacity = groupCapacity; return generatedVisits; } @Override public String getEngineeringOfficeId() { return engine.getEngineeringOfficeId(); } @Override public String getEngineeringOfficeCityId() { return engine.getEngineeringOfficeCityId(); } @Override public int getRemainingCapacity() { return remainingCapacity; } }
Similarities between Decorator Pattern and Composite Pattern
Both patterns aim to embed objects within another. The composite pattern embeds classes in a tree like structure while decorator pattern a class embeds another by wrapping to provide extra functionalities.
Differences between Decorator Pattern and Composite Pattern
Composite Pattern | Decorator Pattern |
Combine and represent multiple objects as one. (Can have a one-to-many relationship). | Enhances a single object. (Is a one-to-one relationship). |
Focuses on aggregation and representation of objects. | Designed to add functionalities and embellish an object. |