What are top must know design patterns?

Design patterns are typical solutions to common problems in software design and architecture. They represent best practices, evolved over time by experienced software developers, to address recurring design challenges. Design patterns provide a standardized and efficient approach to software development, ensuring code is more understandable, flexible, and maintainable.

Categories of Design Patterns:

Design patterns can be broadly classified into three categories, each serving different aspects of software design and development:

  1. Creational Patterns: These patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. The basic form of object creation could result in design problems or added complexity to the design. Creational patterns solve this problem by somehow controlling this object creation. Examples include:

    • Singleton
    • Factory Method
    • Abstract Factory
    • Builder
    • Prototype
  2. Structural Patterns: These patterns focus on how classes and objects are composed to form larger structures. Structural patterns ease the design by identifying a simple way to realize relationships among entities. Examples include:

    • Adapter (or Wrapper)
    • Composite
    • Proxy
    • Flyweight
    • Facade
    • Bridge
    • Decorator
  3. Behavioral Patterns: These patterns are concerned with algorithms and the assignment of responsibilities between objects. They characterize complex control flow that's difficult to follow at run-time. Behavioral patterns shift your focus away from flow of control to let you concentrate just on the way objects are interconnected. Examples include:

    • Observer
    • Strategy
    • Command
    • Iterator
    • State
    • Visitor
    • Mediator
    • Memento
    • Chain of Responsibility
    • Template Method

Importance of Design Patterns:

  • Solve Common Problems: Design patterns provide proven solutions to common coding problems, allowing developers to avoid potential pitfalls and make the design process quicker and more efficient.
  • Facilitate Communication: Design patterns offer a shared vocabulary for developers, making it easier to communicate design ideas and understand existing designs.
  • Improve Code Quality: By promoting best practices, design patterns contribute to the development of well-structured, reusable, and maintainable code.
  • Enhance Flexibility: Many design patterns focus on creating software that is easily adaptable to change, which is crucial in the fast-paced world of technology.
  • Speed Up Development Process: Reusing design patterns helps speed up the developmental phase by providing tested and proven development paradigms.

Creational Design Patterns

Creational design patterns are fundamental design patterns that deal with object creation mechanisms, aiming to create objects in a manner suitable to the situation. The basic form of object creation could result in design problems or added complexity to the design. Creational design patterns solve this problem by somehow controlling this object creation. Here are some of the most famous Creational Design Patterns:

1. Singleton Pattern

  • Description: Ensures that a class has only one instance and provides a global point of access to it.
  • Example: A database connection pool. Creating multiple instances of a connection pool can lead to excessive connections. The Singleton pattern ensures that only one connection pool is created and it can be accessed globally.
    public class DatabaseConnection { private static DatabaseConnection instance; private DatabaseConnection() {} public static synchronized DatabaseConnection getInstance() { if (instance == null) { instance = new DatabaseConnection(); } return instance; } }

2. Factory Method Pattern

  • Description: Defines an interface for creating an object, but lets subclasses alter the type of objects that will be created.
  • Example: A logistics management system where the transport type is decided at runtime. The Factory Method lets subclasses decide which class to instantiate.
    abstract class Logistics { public abstract Transport createTransport(); public void planDelivery() { Transport transport = createTransport(); transport.deliver(); } } class RoadLogistics extends Logistics { @Override public Transport createTransport() { return new Truck(); } }

3. Abstract Factory Pattern

  • Description: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
  • Example: A UI toolkit where components like buttons and checkboxes need to look consistent across devices. Different factories can create these components for different operating systems.
    interface GUIFactory { Button createButton(); Checkbox createCheckbox(); } class WinFactory implements GUIFactory { public Button createButton() { return new WinButton(); } public Checkbox createCheckbox() { return new WinCheckbox(); } }

4. Builder Pattern

  • Description: Allows constructing complex objects step by step. This pattern lets you produce different types and representations of an object using the same construction code.
  • Example: A meal planner where a meal consists of several items like burgers, drinks, and sides. The Builder pattern can account for the variations in the meal.
    class MealBuilder { private Meal meal = new Meal(); public MealBuilder addBurger(String type) { /* ... */ } public MealBuilder addDrink(String type) { /* ... */ } public Meal build() { return meal; } }

5. Prototype Pattern

  • Description: Creates new objects by copying an existing object, known as the prototype. This pattern is useful when the creation of an object is more expensive than copying an existing instance.
  • Example: In a game where multiple instances of a complex tree object are needed. Instead of creating each tree from scratch, a prototype of each tree type can be cloned with different properties.
    class Tree implements Cloneable { private String type; private Color color; private Position position; public Tree clone() { // Create a deep copy of this tree object. } }

Creational design patterns are essential for writing flexible and maintainable code. They not only abstract the instantiation process but also encapsulate it, making the system independent of how its objects are created, composed, and represented.

Structural Design Patterns

Structural design patterns are concerned with how classes and objects are composed to form larger structures. They simplify the design by identifying a simple way to realize relationships between entities. Here are some of the most famous Structural Design Patterns, each accompanied by an example:

1. Adapter Pattern (Wrapper)

  • Description: Allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
  • Example: Consider a scenario where a new application needs to use an existing ThirdPartyBillingSystem class that expects a list of customer details in XML format, but the application uses customer data in JSON format. An Adapter can be created to convert JSON to XML.
    // Existing interface (XML input) interface BillingSystem { void processBilling(String customerDataXml); } // Adapter class BillingAdapter implements BillingSystem { private JsonBillingSystem jsonBillingSystem = new JsonBillingSystem(); @Override public void processBilling(String customerDataJson) { String customerDataXml = convertJsonToXml(customerDataJson); jsonBillingSystem.processBilling(customerDataXml); } private String convertJsonToXml(String json) { // Conversion logic return "<xml>"; } }

2. Composite Pattern

  • Description: Composes objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.
  • Example: A graphic design application where Graphic objects (like circles, rectangles) can be composed into complex structures (like diagrams). Both single objects and compositions can be manipulated (moved, scaled) in the same way.
    interface Graphic { void move(int x, int y); void draw(); } class CompositeGraphic implements Graphic { private List<Graphic> childGraphics = new ArrayList<>(); public void add(Graphic graphic) { childGraphics.add(graphic); } @Override public void move(int x, int y) { for (Graphic graphic : childGraphics) { graphic.move(x, y); } } @Override public void draw() { for (Graphic graphic : childGraphics) { graphic.draw(); } } }

3. Proxy Pattern

  • Description: Provides a surrogate or placeholder for another object to control access to it. This might be for security reasons, to add overhead processing, or to delay the instantiation of a heavy object.
  • Example: An ImageLoader proxy that controls access to HighResolutionImage objects. The proxy loads images only when they are actually needed to be rendered on the screen, thus saving system resources.
    interface Image { void display(); } class HighResolutionImage implements Image { public HighResolutionImage(String imagePath) { // Load image from disk (heavy operation) } @Override public void display() { // Display the image } } class ImageLoaderProxy implements Image { private String imagePath; private HighResolutionImage highResImage; public ImageLoaderProxy(String imagePath) { this.imagePath = imagePath; } @Override public void display() { if (highResImage == null) { highResImage = new HighResolutionImage(imagePath); } highResImage.display(); } }

4. Facade Pattern

  • Description: Provides a simplified interface to a complex system of classes, library, or framework. This pattern is often used to create a simple interface for a complex subsystem.
  • Example: A HomeTheaterFacade simplifies the complexity of components such as the projector, sound system, and lights by providing a simple method to activate a "movie night" mode.
    class HomeTheaterFacade { private Projector projector; private SoundSystem soundSystem; private Lights lights; public HomeTheaterFacade(Projector projector, SoundSystem soundSystem, Lights lights) { this.projector = projector; this.soundSystem = soundSystem; this.lights = lights; } public void watchMovie() { lights.dim(); soundSystem.turnOn(); projector.turnOn(); // Setup more components... } }

5. Flyweight Pattern

  • Description: Reduces the cost of creating and manipulating a large number of similar objects. It achieves this by sharing common parts of the object state among multiple objects instead of keeping all of the data in each object.

  • Example: In a text editor that can handle thousands of characters, a Character flyweight stores intrinsic state (like character code) that is shared. Extrinsic state (like position, font, and size) can be passed in when the character is displayed.

    class Character { private char glyph; // Intrinsic state public Character(char glyph) { this.glyph = glyph; } public void display(int fontSize, int positionX, int positionY) { // Display character with extrinsic state } }

Structural design patterns focus on simplifying the structure by identifying relationships between entities, making designs easier to understand, maintain, and extend. They play a crucial role in ensuring software architecture is sound, scalable, and efficient.

Behavioral Design Patterns

Behavioral design patterns are concerned with algorithms and the assignment of responsibilities between objects. They help manage, simplify, and centralize the interactions between objects, making the interactions more flexible, efficient, and decoupled. Here are some of the most famous Behavioral Design Patterns, each accompanied by an example:

1. Observer Pattern

  • Description: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
  • Example: A weather station (subject) broadcasting weather updates to multiple display devices (observers) like a phone app, a digital billboard, and a weather website. Each display device updates its display when the weather station publishes new weather data.
    interface Observer { void update(float temperature, float humidity, float pressure); } interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); } class WeatherStation implements Subject { private List<Observer> observers; private float temperature; private float humidity; private float pressure; // methods to manage observers and notify them }

2. Strategy Pattern

  • Description: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
  • Example: A navigation app calculating routes by various strategies like the shortest route, the least traffic route, and the scenic route. The app can switch between these strategies based on the user's preference without changing the route calculation process.
    interface RouteStrategy { List<Point> calculateRoute(Point A, Point B); } class NavigationApp { private RouteStrategy routeStrategy; public void setRouteStrategy(RouteStrategy routeStrategy) { this.routeStrategy = routeStrategy; } public void buildRoute(Point A, Point B) { List<Point> route = routeStrategy.calculateRoute(A, B); // Display the route } }

3. Command Pattern

  • Description: Encapsulates a request as an object, thereby allowing for parameterization of clients with queues, requests, and operations. It also allows for the support of undoable operations.
  • Example: A smart home system where commands like turning on the lights, adjusting the thermostat, or locking the doors are encapsulated as objects. These command objects can be scheduled, executed later, or even undone.
    interface Command { void execute(); } class LightOnCommand implements Command { private Light light; public LightOnCommand(Light light) { this.light = light; } @Override public void execute() { light.on(); } } class RemoteControl { private Command command; public void setCommand(Command command) { this.command = command; } public void pressButton() { command.execute(); } }

4. State Pattern

  • Description: Allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
  • Example: An online order system where an order can be in various states (New, Approved, Packed, Shipped, Delivered). The behavior of the order changes based on its current state, without the client having to check and manage state-specific logic.
    interface State { void next(Order order); void prev(Order order); void printStatus(); } class Order { private State state; public Order() { state = new NewState(); } public void nextState() { state.next(this); } public void previousState() { state.prev(this); } public void setState(State state) { this.state = state; } public void printStatus() { state.printStatus(); } }

5. Mediator Pattern

  • Description: Defines an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.
  • Example: A chat room acting as a mediator between users. Users send messages to the chat room, which then forwards the messages to other users. The users don't communicate directly with each other.
    interface ChatRoomMediator { void showMessage(User user, String message); } class ChatRoom implements ChatRoomMediator { @Override public void showMessage(User user, String message) { System.out.println(user.getName() + ": " + message); } }

Behavioral design patterns are essential for managing complex interactions and communications between objects in software development. They help in defining not just the roles objects play but also the way they communicate, leading to more organized, maintainable, and decoupled codebases.

Conclusion

Design patterns are essential tools for software developers, helping to solve common design issues in a highly efficient and reusable way. They encapsulate best practices, derived from the experience of skilled developers, and provide a solid foundation for complex software architecture and system design. Familiarity with design patterns enables developers to navigate the design process more smoothly and to build robust, scalable, and maintainable software systems.

TAGS
System Design Fundamentals
CONTRIBUTOR
Design Gurus Team
Explore Answers
Related Courses
Image
Grokking the Coding Interview: Patterns for Coding Questions
Image
Grokking Data Structures & Algorithms for Coding Interviews
Image
Grokking System Design Fundamentals