Monday, January 8, 2024

Creational patterns

1. Singleton Pattern
2. Factory Pattern
3. Abstract Factory Pattern
4. Builder Pattern
5. Prototype Pattern


Singleton Pattern

Singleton is a creational design pattern that ensures only a single instance of a class exists throughout an application and provides access to that instance from anywhere in the codebase. It is useful in situations where multiple instances of a class are undesirable or a single point of control or coordination is required. Typical examples include logging systems, database connection managers, caches, and thread pools, among others.

Example: A database connection manager in a web application can be implemented using the Singleton pattern.

  • Use this pattern when you need to ensure control access to a resource that might return inconsistencies if it is changed by two different objects at the same time. (Ex. Databases, state of an object)
  • The singleton pattern only allows a class or object to have a single instance and it uses a global variable to store that instance. You can use lazy loading to make sure that there is only one instance of the class because it will only create the class when you need it.

Advantages of the Singleton Design Pattern

  • Single Instance: The Singleton Pattern guarantees that a class has only one instance throughout the application. This can be useful in scenarios in which multiple instances must be avoided, such as when managing shared resources or configurations.
  • Global Access: Because a Singleton instance is globally accessible, it is possible to access its methods and properties from anywhere in the application. This eliminates the need to pass objects’ instances or manage complex dependencies.
  • Lazy Initialization: The Singleton pattern supports lazy initialization, which means that the instance is created only when it is accessed for the first time. This can improve performance by deferring instantiation until it is required.
  • Thread Safety: The Singleton implementation can be made thread-safe, allowing multiple threads to access the instance without causing concurrency issues. This is particularly critical in multi-threaded applications.

Real-world examples of Singleton Design Pattern

  • Logger
  • Database Connection
  • Cache Manager

Factory Method Pattern

Factory Method is a Creational Design Pattern that allows us to create objects without specifying the exact class that will be instantiated. Instead of creating objects directly, we use a factory method to create and return them.

Advantages of the Factory Method Design Pattern

  • Loose coupling: The Factory Method Design Pattern promotes loose coupling between classes. The Creator class doesn’t need to know the exact class that will be instantiated. This makes the code more modular and easier to maintain.
  • Encapsulation: The Factory Method Design Pattern encapsulates the object creation process. This makes it easier to modify the creation process without affecting the rest of the code.
  • Code reuse: The Factory Method Design Pattern promotes code reuse by allowing multiple classes to share a common interface.
  • Extensibility: The Factory Method Design Pattern is extensible. It allows new ConcreteProduct classes to be added without modifying the existing code.

The Factory Method pattern is useful when

  • We don’t know the exact class of object that will be created at runtime.
  • We want to encapsulate the object creation process.
  • We want to promote loose coupling between classes.
  • We want to promote code reuse.

 

Abstract Factory

Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.

The Abstract Factory pattern is helpful when your code needs to deal with different groups of related items, but you want to avoid depending on the specific types of those items. These types might not be known in advance or you might want to keep room for adding more types in the future.

Builder

Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

Advantages

  • Reduces the number of parameters in the constructor and provides readable method calls.
  • Allows object instantiation in a complete state.
  • Simplifies the building process of immutable objects.

Disadvantages

  • It increases the number of lines of code but offers design flexibility and improved code readability.
  • Requires creating separate ConcreteBuilder classes for each product type.

 Prototype

Prototype is a creational design pattern that lets you copy existing objects without making your code dependent on their classes.

The Prototype pattern can be useful when the new object we need to create exhibits only minor differences from an existing one. So, instead of making completely new objects each time, we can set up examples with the right settings in advance, and then copy them when we need more.

Software Design Patterns

 Software Design Patterns:

Software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design.

Importance of Design patterns?

  • It makes code reusable, bug-free, and clean.
  • Speed up development process.
  • Changes or modification become easier.
  • Reduce common problems of developers they face during development process.
  • Improve object-oriented skill.
  • Easy to understand the flow of code.
  • Less code so easy to maintain.

Types of Design Patterns

There are 3 types of design patterns depending on their behavior.

  • Creational patterns: Provide a structure to create objects easily, provide certain flexibility on its creation, and reuse existing code.
  • Structural patterns: Provide a structure that helps assemble objects and classes into larger structures.


SOLID Principles

What are SOLID Design Principles?

SOLID is an acronym that stands for:

  • Single Responsibility Principle (SRP)
  • Open-Closed Principle (OCP)
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle (ISP)
  • Dependency Inversion Principle (DIP)

 So, what is SOLID and how does it help us write better code? Simply put, Martin and Feathers’ design principles encourage us to create more maintainable, understandable, and flexible software. Consequently, as our applications grow in size, we can reduce their complexity and save ourselves a lot of headaches further down the road!


  1. Single Responsibility Principle (SRP):
    • Definition: A class should have only one reason to change, meaning that it should have only one responsibility or job.
    • Implications:
      • Each class should encapsulate a single functionality or responsibility.
      • A class should not be forced to change for more than one reason.
  2. Open/Closed Principle (OCP):
    • Definition: Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
    • Implications:
      • New functionality should be added through extensions (e.g., inheritance, interfaces) without modifying existing code.
      • Existing code should be closed for modification to prevent unintended side effects.
  3. Liskov Substitution Principle (LSP):
    • Definition: Subtypes must be substitutable for their base types without altering the correctness of the program.
    • Implications:
      • Derived classes should extend the behavior of the base class without changing its contract.
      • Code that uses a base class should be able to use its derived classes without knowing it.
  4. Interface Segregation Principle (ISP):
    • Definition: A class should not be forced to implement interfaces it does not use. Clients should not be forced to depend on interfaces they do not use.
    • Implications:
      • Interfaces should be small and specific to the needs of the classes that implement them.
      • Clients should not be burdened with methods they do not need.
  5. Dependency Inversion Principle (DIP):
    • Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.
    • Implications:
      • Depend on abstractions (e.g., interfaces) rather than concrete implementations.
      • High-level modules and low-level modules should both depend on the same abstractions.