OOP patterns

0.0(0)
studied byStudied by 0 people
GameKnowt Play
learnLearn
examPractice Test
spaced repetitionSpaced Repetition
heart puzzleMatch
flashcardsFlashcards
Card Sorting

1/14

encourage image

There's no tags or description

Looks like no tags are added yet.

Study Analytics
Name
Mastery
Learn
Test
Matching
Spaced

No study sessions yet.

15 Terms

1
New cards

Factory Pattern

The Factory Method is a design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

2
New cards

Factory Pattern (Motivation)

The Factory Method pattern solves this by delegating the responsibility of object creation to subclasses. The main class works with a generic object interface and calls a "factory method" to create it, without knowing the exact type of object it will get.

Analogy: Think of a restaurant group (Creator). The headquarters has a standard process for opening a new restaurant (business logic). However, it doesn't build the restaurant itself. Instead, it calls on a specialized team. If it's a pizzeria (ConcreteCreator), that team builds a Pizza (Product). If it's a burger joint (ConcreteCreator), that team builds a Burger (Product). The headquarters' process remains the same, but the final product changes based on which team is used.

<p>The Factory Method pattern solves this by <strong>delegating the responsibility of object creation to subclasses</strong>. The main class works with a generic object interface and calls a "factory method" to create it, without knowing the exact type of object it will get.</p><p><strong>Analogy</strong>: Think of a restaurant group (<code>Creator</code>). The headquarters has a standard process for opening a new restaurant (<code>business logic</code>). However, it doesn't build the restaurant itself. Instead, it calls on a specialized team. If it's a pizzeria (<code>ConcreteCreator</code>), that team builds a <code>Pizza</code> (<code>Product</code>). If it's a burger joint (<code>ConcreteCreator</code>), that team builds a <code>Burger</code> (<code>Product</code>). The headquarters' process remains the same, but the final product changes based on which team is used.</p>
3
New cards

Factory Pattern (use cases)

This pattern is useful when:

  • A class can't anticipate the class of objects it must create.

  • You want to provide users of your library or framework with a way to extend its internal components.

  • You want to localize the logic for creating an object, rather than scattering it across the application.

Examples:

  • UI Toolkits: A base Dialog class has a createButton() factory method. A WindowsDialog subclass would override it to create a native WindowsButton, while a MacDialog would create a MacButton.

  • Document Editors: A generic Application class defines an abstract createDocument() method. A TextEditorApp subclass implements it to return a TextDocument, while a DrawingApp returns a CanvasDocument.

4
New cards

Factory Pattern (how to build)

Implementation: How to Build It

The pattern has four main components:

  1. Product Interface: Defines the interface for the objects the factory will create (e.g., ITransport with a deliver() method).

  2. Concrete Products: The actual classes that implement the Product interface (e.g., Truck, Ship).

  3. Creator (or Factory): An abstract class that declares the factory method, which returns an object of the Product type. It contains business logic that operates on the Product.

  4. Concrete Creators: Subclasses that extend the Creator and override the factory method to return a specific ConcreteProduct.

<p>Implementation: How to Build It </p><p></p><p>The pattern has four main components:</p><ol><li><p><strong>Product Interface</strong>: Defines the interface for the objects the factory will create (e.g., <code>ITransport</code> with a <code>deliver()</code> method).</p></li><li><p><strong>Concrete Products</strong>: The actual classes that implement the <code>Product</code> interface (e.g., <code>Truck</code>, <code>Ship</code>).</p></li><li><p><strong>Creator (or Factory)</strong>: An <code>abstract</code> class that declares the factory method, which returns an object of the <code>Product</code> type. It contains business logic that operates on the <code>Product</code>.</p></li><li><p><strong>Concrete Creators</strong>: Subclasses that extend the <code>Creator</code> and override the factory method to return a specific <code>ConcreteProduct</code>.</p></li></ol><p></p>
5
New cards

Factory pattern (example)

The TypeScript Implementation

The implementation involves four key parts: the interface (the "product" contract), the concrete classes (the "products"), the factory, and the client code that uses the factory.

1. The Notification Interface

This defines the required structure for any notification type. In TypeScript, an interface is perfect for this.

2. Concrete Notification Classes

These are the specific implementations. Each class must implement the Notification interface, guaranteeing it has a send method.

3. The NotificationFactory

This class centralizes the creation logic. It has a method that takes a type and returns an object that matches the Notification interface. Using a string literal union type ('email' | 'sms' | 'push') for the type parameter provides excellent autocompletion and compile-time error checking.

<p>The TypeScript Implementation</p><p></p><p>The implementation involves four key parts: the interface (the "product" contract), the concrete classes (the "products"), the factory, and the client code that uses the factory.</p><p></p><p>1. The <code>Notification</code> Interface</p><p>This defines the required structure for any notification type. In TypeScript, an <code>interface</code> is perfect for this.</p><p></p><p>2. Concrete <code>Notification</code> Classes</p><p>These are the specific implementations. Each class must <code>implement</code> the <code>Notification</code> interface, guaranteeing it has a <code>send</code> method.</p><p></p><p>3. The <code>NotificationFactory</code></p><p>This class centralizes the creation logic. It has a method that takes a type and returns an object that matches the <code>Notification</code> interface. Using a <strong>string literal union type</strong> (<code>'email' | 'sms' | 'push'</code>) for the type parameter provides excellent autocompletion and compile-time error checking.</p>
6
New cards

Singleton Motification

In software, You want to ensure that a class has only one instance and provide a single, global point of access to it.

The Singleton pattern solves two problems at once:

  1. Guarantees a Single Instance: It ensure

    s that no matter how many times you ask for an object of a certain class, you always get the exact same one.

  2. Provides Global Access: It gives you a simple, well-known place to get that single instance, so you don't have to pass it around your entire codebase.

7
New cards

Singleton use cases

The Singleton pattern is best used for objects that manage a shared resource. Here are some classic examples:

  • Logging: You typically want a single logging object for your entire application to write messages to the same log file or console. If you had multiple loggers, they might try to write to the same file simultaneously, causing chaos.

  • Configuration Manager: Application settings (like database URLs, API keys, etc.) are often loaded from a file once at startup. A Singleton configuration object can hold these settings, ensuring all parts of the app read from the same source.

  • Database Connection Pool: Managing database connections is expensive. A Singleton can manage a pool of active connections, handing them out as needed and ensuring the pool is a single, shared resource.

  • Hardware Access: When your application needs to talk to a piece of hardware like a printer or a graphics card, you want a single object to manage that communication to prevent conflicting commands.

8
New cards

Singleton Implementation

Creating a Singleton involves three key steps:

  1. Make the constructor private. This is the most important step. A private constructor prevents other classes from creating new instances of the Singleton using the new operator. This power is reserved for the Singleton class itself.

  2. Create a private static instance of the class. The class will hold its one and only instance in a static field. This field is private so that nothing outside the class can mess with it.

  3. Provide a public static method to get the instance. This method (commonly named getInstance() or Instance) is the global access point. The first time it's called, it creates the single instance and stores it in the static field. Every subsequent call simply returns that already-created instance.

  4. Building a Singleton in a language like TypeScript is straightforward thanks to its access modifiers (public, private). Here are the essential ingredients:

    1. A private static instance variable: The class holds its own single instance in a static field. It's private so nothing outside the class can tamper with it.

    2. A private constructor: This is crucial. It prevents you or other developers from creating new instances of the class using the new keyword (e.g., new Logger()).

    3. A public static getInstance() method: This is the "front door." It's the only way to get the Singleton object. The first time it's called, it creates the instance. On every subsequent call, it simply returns the instance it already created.

This approach is called lazy initialization because the instance is only created when it's first needed.

9
New cards

Singleton Thread safety

A note on Thread Safety: In a multi-threaded application, two threads might call getInstance() at the exact same time. If the instance hasn't been created yet, both might pass the if (instance == null) check and create two separate instances, violating the pattern. To fix this, you need to make the getInstance() method thread-safe, often by using a "lock" or by creating the instance eagerly (when the class is first loaded).

10
New cards

Singleton Example

When you run this code, you'll see the following output. Notice that "Logger instance created" appears only once, even though we call getInstance() twice.

--- Application Start ---
Logger instance created. This should only happen once.
[2025-08-09T00:00:42.000Z] Service A started.
[2025-08-09T00:00:42.000Z] Service B initialized.

Are both loggers the same instance? true

Printing all logs from Service A's logger:
--- All Logs ---
[2025-08-09T00:00:42.000Z] Service A started.
[2025-08-09T00:00:42.000Z] Service B initialized.
----------------
--- Application End ---

This clearly demonstrates that both serviceA_logger and serviceB_logger are references to the exact same object, successfully implementing the Singleton pattern.

<p>When you run this code, you'll see the following output. Notice that "Logger instance created" appears only once, even though we call <code>getInstance()</code> twice.</p><pre><code>--- Application Start ---
Logger instance created. This should only happen once.
[2025-08-09T00:00:42.000Z] Service A started.
[2025-08-09T00:00:42.000Z] Service B initialized.

Are both loggers the same instance? true

Printing all logs from Service A's logger:
--- All Logs ---
[2025-08-09T00:00:42.000Z] Service A started.
[2025-08-09T00:00:42.000Z] Service B initialized.
----------------
--- Application End ---
</code></pre><p>This clearly demonstrates that both <code>serviceA_logger</code> and <code>serviceB_logger</code> are references to the exact same object, successfully implementing the Singleton pattern.</p>
11
New cards

Builder pattern (motivation)

Imagine you need to create an object that has many configuration options, some required and some optional. Let's take a User profile as an example. A user must have an email and password, but their first name, last name, age, and profile picture are optional. But that way is messy and doesn't scale.

You could also have one huge constructor where you pass null or undefined for the optional fields. But this is hard to read. Which null corresponds to which field? It's very easy to mix up the order of parameters.

The Builder pattern solves this by separating the construction of a complex object from its actual representation. It lets you build an object step-by-step using simple, readable methods and then retrieve the final object.

<p>Imagine you need to create an object that has many configuration options, some required and some optional. Let's take a <code>User</code> profile as an example. A user <em>must</em> have an email and password, but their first name, last name, age, and profile picture are optional. But that way is messy and doesn't scale.</p><p>You could also have one huge constructor where you pass <code>null</code> or <code>undefined</code> for the optional fields. But this is hard to read. Which <code>null</code> corresponds to which field? It's very easy to mix up the order of parameters.</p><p>The <strong>Builder pattern</strong> solves this by separating the construction of a complex object from its actual representation. It lets you build an object <strong>step-by-step</strong> using simple, readable methods and then retrieve the final object.</p>
12
New cards

Builder pattern (use cases)

When Is It a Good Idea? 🛠

The Builder pattern shines when you need to construct an object with many optional parts, or when the construction process is complex.

  • Building Query Strings: Constructing a complex SQL (like query builder in typeorm), or URL query with many optional parameters (WHERE, ORDER BY, LIMIT, etc.).

    • queryBuilder.select("users").where("age > 21").limit(10).build();

  • Creating Complex Objects: As mentioned, creating User profiles, Configuration settings, or any object with a lot of attributes.

  • Assembling Documents: Building an HTML or XML document where you add elements like headers, paragraphs, and tables step-by-step.

  • The "Meal" Analogy: Think of ordering a custom pizza or a sandwich at a deli. You don't tell the cashier the entire order in one long, confusing sentence. You say, "I'll have a pizza... on thin crust... add mushrooms... add extra cheese." You build it piece by piece. The builder pattern does the same for objects.

13
New cards

Builder pattern (Implementation)

The pattern introduces a few key roles:

  1. The Product: This is the complex object you want to build (e.g., the Pizza class). Its constructor is often made private or internal, so it can only be created by the Builder.

  2. The Builder: This is the star of the show. It's a class that has methods for configuring the Product step-by-step (e.g., setSize(), addTopping()).

  3. Method Chaining (Fluent Interface): The builder's methods typically return this;, which allows you to chain calls together in a readable, fluent way.

  4. The build() Method: The builder has a final method, usually called build(), that assembles all the pieces and returns the finished Product.

<p>The pattern introduces a few key roles:</p><ol><li><p><strong>The </strong><code>Product</code>: This is the complex object you want to build (e.g., the <code>Pizza</code> class). Its constructor is often made private or internal, so it can only be created by the <code>Builder</code>.</p></li><li><p><strong>The </strong><code>Builder</code>: This is the star of the show. It's a class that has methods for configuring the <code>Product</code> step-by-step (e.g., <code>setSize()</code>, <code>addTopping()</code>).</p></li><li><p><strong>Method Chaining (Fluent Interface)</strong>: The builder's methods typically <code>return this;</code>, which allows you to chain calls together in a readable, fluent way.</p></li><li><p><strong>The </strong><code>build()</code><strong> Method</strong>: The builder has a final method, usually called <code>build()</code>, that assembles all the pieces and returns the finished <code>Product</code>.</p></li></ol><p></p>
14
New cards

Builder pattern (Example)

3. The Client Code (main.ts)

Now, let's see how easy and readable it is to create pizzas!

TypeScript

// main.ts

import { PizzaBuilder } from './PizzaBuilder';

console.log("--- Let's make some pizzas! ---");

// Build a complex pizza using the fluent interface
console.log("\nBuilding a 'Supreme' pizza:");
const supremePizza = new PizzaBuilder("large")
    .setCrust("thin")
    .addTopping("pepperoni")
    .addTopping("mushrooms")
    .addTopping("olives")
    .build();

supremePizza.describe();


// Build a simple pizza
console.log("\nBuilding a simple 'Cheese' pizza:");
const cheesePizza = new PizzaBuilder("medium")
    .build();

cheesePizza.describe();

Output of the Example:

--- Let's make some pizzas! ---

Building a 'Supreme' pizza:
A large, thin crust pizza with:
- pepperoni
- mushrooms
- olives

Building a simple 'Cheese' pizza:
A medium, classic crust pizza with:
- No toppings.

Notice how clean the creation process is. Each line is self-documenting. You can't misplace a parameter, and you can clearly see what options have been set for each pizza. This is the power of the Builder pattern.

<p>3. The Client Code (<code>main.ts</code>)</p><p>Now, let's see how easy and readable it is to create pizzas!</p><p>TypeScript</p><pre><code class="language-TypeScript">// main.ts

import { PizzaBuilder } from './PizzaBuilder';

console.log("--- Let's make some pizzas! ---");

// Build a complex pizza using the fluent interface
console.log("\nBuilding a 'Supreme' pizza:");
const supremePizza = new PizzaBuilder("large")
    .setCrust("thin")
    .addTopping("pepperoni")
    .addTopping("mushrooms")
    .addTopping("olives")
    .build();

supremePizza.describe();


// Build a simple pizza
console.log("\nBuilding a simple 'Cheese' pizza:");
const cheesePizza = new PizzaBuilder("medium")
    .build();

cheesePizza.describe();
</code></pre><p></p><p>Output of the Example:</p><p></p><pre><code>--- Let's make some pizzas! ---

Building a 'Supreme' pizza:
A large, thin crust pizza with:
- pepperoni
- mushrooms
- olives

Building a simple 'Cheese' pizza:
A medium, classic crust pizza with:
- No toppings.
</code></pre><p>Notice how clean the creation process is. Each line is self-documenting. You can't misplace a parameter, and you can clearly see what options have been set for each pizza. This is the power of the Builder pattern.</p>
15
New cards