Hello friends, Design pattern is one of the skill which people told me to learn to become a senior software engineer as this was heavily tested during a senior software engineer interview.
While I learned because of that later I found that how crucial Design patterns are for writing clean code which can survive in production.
For those who don't know, Design patterns are reusable solutions to common problems encountered in software design.
They encapsulate best practices and provide a blueprint for creating flexible, maintainable, and scalable software.
In the last few articles, I have shared many popular system design questions like API Gateway vs Load Balancer and Horizontal vs Vertical Scaling, Forward proxy vs reverse proxy as well as 50 system design problems and today I am going to share the 10 design patterns for programming interviews.
Btw, if you haven't read those article, you can also read them, especially if you want to learn System Design, another in-demand skill for senior software engineers.
If you are learning these concepts as part of interview prep then I also suggest you check out sites like ByteByteGo, DesignGurus.io, Exponent, Educative, Codemia.io, InterviewReddy.io and Udemy which have many great System design courses
P.S. Keep reading until the end. I have a bonus for you.
10 Essential OOP Design Patterns for Programming Interviews
So, here are the 10 important design patterns you can learn for programming job interviews, while it may take less time to read this article but if you want to try code examples, it may take even more than 10 minutes but 10 minutes is good enough to read and understand these code examples.
1. Singleton Pattern
The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This is particularly useful when exactly one object is needed to coordinate actions across the system.
Here is a code example of Singleton Pattern
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Impact
This pattern ensures a single point of control for a specific resource, preventing unnecessary instantiation and ensuring efficient resource utilization.
While many people consider Singleton as anti pattern as it not easy to unit test and dependency injection is preferred over Singleton, its still a good pattern to know.
Here is the UML diagram of Singleton pattern
2. Factory Method Pattern
The Factory Method pattern defines an interface for creating an object, but lets subclasses alter the type of objects that will be created.
It provides an interface for creating instances of a class, with its subclasses deciding which class to instantiate.
And, here is the sample code for this pattern:
public interface Product {
void create();
}
public class ConcreteProduct implements Product {
@Override
public void create() {
// Implementation
}
}
public interface Creator {
Product factoryMethod();
}
public class ConcreteCreator implements Creator {
@Override
public Product factoryMethod() {
return new ConcreteProduct();
}
}
Impact
This design pattern promotes loose coupling by allowing the client code to interact with the objects through interfaces, making it easier to extend and maintain.
Here is a UML diagram of Factory method pattern:
3. Observer Pattern
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
This is commonly used in implementing distributed event handling systems.
Example:
public interface Observer {
void update(String message);
}
public class ConcreteObserver implements Observer {
@Override\
public void update(String message) {
// Implementation
}
}
public class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
Impact
This pattern Enables a clean separation between the subject and its observers, facilitating easy extensibility and maintainability.
Here is a the UML diagram of Observer pattern:
4. Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows a client to choose the appropriate algorithm at runtime.
Here is the code example of this pattern:
public interface PaymentStrategy {
void pay(int amount);
}
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
// Implementation
}
}
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
// Implementation
}
}
public class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
Impact
This design pattern allows algorithms to vary independently from clients using them, promoting flexibility and ease of algorithm replacement.
Here is the UML diagram of Strategy design pattern in Java:
5. Decorator Pattern
The Decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Example:
public interface Component {
void operation();
}
public class ConcreteComponent implements Component {
@Override
public void operation() {
// Implementation
}
}
public abstract class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
// Additional functionality
}
}
Impact
This pattern enables the dynamic addition of responsibilities to objects, avoiding the need for an unwieldy number of subclasses.
Here is the UML diagram of decorator design pattern:
6. Adapter Pattern
The Adapter pattern allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, making them compatible without changing their code.
Here is a code example of Adapter pattern in Java:
public interface Target {
void request();
}
public class Adaptee {
public void specificRequest() {
// Implementation
}
}
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
Impact
This pattern facilitates the integration of existing systems by allowing them to work together despite incompatible interfaces.
Here is the UML diagram of Adapter pattern in Java:
7. Command Pattern
The Command pattern encapsulates a request as an object, thereby allowing for parameterization of clients with different requests, queuing of requests, and logging of the parameters.
Here is a code example of this pattern:
public interface Command {
void execute();
}
public class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action();
}
}
public class Receiver {
public void action() {
// Implementation
}
}
public class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
}
Impact
This pattern promotes loose coupling between the sender and receiver of a request, allowing for extensibility and flexibility in command handling.
Here is the UML diagram of Command pattern in Java:
8. Composite Pattern
The Composite pattern composes objects into tree structures to represent part-whole hierarchies. It allows clients to treat individual objects and compositions of objects uniformly.
Here is a code example for implementing Composite pattern in Java:
public interface Component {
void operation();
}
public class Leaf implements Component {
@Override
public void operation() {
// Implementation
}
}
public class Composite implements Component {
private List<Component> components = new ArrayList<>();
public void addComponent(Component component) {
components.add(component);
}
@Override
public void operation() {
for (Component component : components) {
component.operation();
}
}
}
**Impact
**This pattern greatly simplifies the client code by allowing it to treat individual objects and compositions uniformly.
Here is the UML diagram of Composite pattern in Java:
9. Chain of Responsibility Pattern
The Chain of Responsibility pattern passes a request along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain.
Here is the code example of this pattern:
public abstract class Handler {
private Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public void handleRequest() {
if (successor != null) {
successor.handleRequest();
}
}
}
public class ConcreteHandler extends Handler {
@Override
public void handleRequest() {
// Handle the request
super.handleRequest();
}
}
Impact
This pattern decouples senders and receivers, allowing multiple objects to handle a request without the sender needing to know which object will ultimately process it.
Here is the UML diagram of Chain of Responsibility pattern in Java:
10. State Pattern
The State pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
Here is the code example of state design pattern in Java:
public interface State {
void handle();
}
public class ConcreteStateA implements State {
@Override
public void handle() {
// Implementation for state A
}
}
public class ConcreteStateB implements State {
@Override
public void handle() {
// Implementation for state B
}
}
public class Context {
private State currentState;
public void setState(State state) {
currentState = state;
}
public void request() {
currentState.handle();
}
}
Impact
The state design pattern Simplifies complex object behavior by allowing it to change its internal state dynamically.
Here is the UML diagram of State design pattern in Java:
Conclusion
That's all about the 10 essential design patterns for Programming interviews. Design patterns are powerful tools in a developer's toolkit, offering solutions to common software design challenges.
Understanding and implementing these patterns can significantly improve the maintainability, extensibility, and scalability of your code.
While mastering these patterns may take time, incorporating them judiciously into your projects will undoubtedly lead to more robust and flexible software architectures.
Further Learning
- Design Patterns in Java Explore practical implementations of design patterns in Java.
- Basics Software Architecture Learn foundational concepts in software architecture.
- Grokking the OOP Interview Prepare for object-oriented programming interviews with real-world scenarios.
- Java Design Patterns and Architecture (FREE) Access a free guide to Java design patterns and their applications.
- Design Pattern Library A rich library of design pattern examples and explanations.
- Patterns by Example Learn patterns through concrete examples for better understanding.
Bonus
As promised, here is the bonus for you, a free book. I just found a new free book to learn Distributed System Design, you can also read it here on Microsoft --- https://info.microsoft.com/rs/157-GQE-382/images/EN-CNTNT-eBook-DesigningDistributedSystems.pdf
image_credit --- twitter
Top comments (1)
Hi, thanks for sharing!