diff --git a/docs/creational/abstract-factory.mdx b/docs/creational/abstract-factory.mdx
index 08f92a3..04841b3 100644
--- a/docs/creational/abstract-factory.mdx
+++ b/docs/creational/abstract-factory.mdx
@@ -2,26 +2,25 @@
title: Abstract Factory
hide_title: true
description: >
- It's not a pyramid scheme, it's a factory scheme.
+ It's not a pyramid scheme, it's a factory scheme.
---
import { CreationalPatternTag } from "@site/src/components/pattern-page-features/pattern-alias-and-tag";
import TwoColumnProsCons from "@site/src/components/pattern-page-features/pros-and-cons";
-import TOCInline from '@theme/TOCInline';
+import TOCInline from "@theme/TOCInline";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import Applicability from "@site/src/components/pattern-page-features/applicability";
-
-
+
---
-The **abstract factory** pattern is a creational pattern that can help create groups of related products without specifying their concrete implementations.
+The **abstract factory** pattern is a creational pattern that can help create groups of related products without specifying their concrete implementations.
## 🧩The problem
-Let's say you are creating a armour shop, and you want to offer different styles of armour (e.g. medieval, modern and futuristic). Each style includes a set of related products like gloves, a chest plate and a helmet. You want to ensure that when a customer selects a particular style, all the armour pieces they receive are consistent in design and aesthetics. You will need a way to create families of related armour products without tightly coupling your code to specific implementations.
+Let's say you are creating a armour shop, and you want to offer different styles of armour (e.g. medieval, modern and futuristic). Each style includes a set of related products like gloves, a chest plate and a helmet. You want to ensure that when a customer selects a particular style, all the armour pieces they receive are consistent in design and aesthetics. You will need a way to create families of related armour products without tightly coupling your code to specific implementations.
## 🛠️Solutions
@@ -38,6 +37,7 @@ By applying this, the client can work with the factories and products via their
An abstract factory is like a car manufacturer that produces different models of cars (e.g., sedan, SUV, truck) without specifying the exact details of each model. The manufacturer provides a common interface for creating cars, and each model implements its own specific features and characteristics. This allows customers to choose a car model based on their preferences without needing to know the intricate details of how each car is built.
### 💡Real-world examples
+
Common practical scenarios for applying the Abstract Factory pattern include:
- GUI frameworks: Different operating systems have distinct UI components. An abstract factory can create families of related UI elements (buttons, text fields, menus) for different platforms (Windows, macOS, Linux) without specifying their concrete implementations.
@@ -45,21 +45,30 @@ Common practical scenarios for applying the Abstract Factory pattern include:
## ⚖️ Pros and Cons
-
-
## 🔍Applicability
-
-
+
## 🧭Implementation Plan
@@ -236,11 +245,11 @@ class ArmourFactory(ABC):
@abstractmethod
def create_helmet(self) -> Helmet:
pass
-
+
@abstractmethod
def create_chest_plate(self) -> ChestPlate:
pass
-
+
@abstractmethod
def create_gloves(self) -> Gloves:
pass
@@ -249,20 +258,20 @@ class ArmourFactory(ABC):
class MedievalArmourFactory(ArmourFactory):
def create_helmet(self) -> Helmet:
return MedievalHelmet()
-
+
def create_chest_plate(self) -> ChestPlate:
return MedievalChestPlate()
-
+
def create_gloves(self) -> Gloves:
return MedievalGloves()
class FuturisticArmourFactory(ArmourFactory):
def create_helmet(self) -> Helmet:
return FuturisticHelmet()
-
+
def create_chest_plate(self) -> ChestPlate:
return FuturisticChestPlate()
-
+
def create_gloves(self) -> Gloves:
return FuturisticGloves()
@@ -271,7 +280,7 @@ def equip_warrior(factory: ArmourFactory) -> None:
helmet = factory.create_helmet()
chest_plate = factory.create_chest_plate()
gloves = factory.create_gloves()
-
+
print(helmet.get_protection())
print(chest_plate.get_protection())
print(gloves.get_protection())
@@ -279,7 +288,7 @@ def equip_warrior(factory: ArmourFactory) -> None:
# Usage
if __name__ == "__main__":
equip_warrior(MedievalArmourFactory())
-
+
equip_warrior(FuturisticArmourFactory())
```
@@ -303,25 +312,61 @@ function AbstractFactoryDemo() {
const factories = {
Medieval: {
name: "Medieval Armoury",
- createHelmet: () => ({ piece: "Helmet", description: "Iron helmet with nose guard", defense: 15 }),
- createChestPlate: () => ({ piece: "Chest Plate", description: "Heavy chainmail armor", defense: 30 }),
- createGloves: () => ({ piece: "Gloves", description: "Leather gauntlets", defense: 8 }),
- emoji: "⚔️"
+ createHelmet: () => ({
+ piece: "Helmet",
+ description: "Iron helmet with nose guard",
+ defense: 15,
+ }),
+ createChestPlate: () => ({
+ piece: "Chest Plate",
+ description: "Heavy chainmail armor",
+ defense: 30,
+ }),
+ createGloves: () => ({
+ piece: "Gloves",
+ description: "Leather gauntlets",
+ defense: 8,
+ }),
+ emoji: "⚔️",
},
Futuristic: {
name: "Futuristic Tech Lab",
- createHelmet: () => ({ piece: "Helmet", description: "Energy shield with HUD", defense: 40 }),
- createChestPlate: () => ({ piece: "Chest Plate", description: "Nano-fiber combat suit", defense: 55 }),
- createGloves: () => ({ piece: "Gloves", description: "Smart-tech grip enhancers", defense: 20 }),
- emoji: "🚀"
+ createHelmet: () => ({
+ piece: "Helmet",
+ description: "Energy shield with HUD",
+ defense: 40,
+ }),
+ createChestPlate: () => ({
+ piece: "Chest Plate",
+ description: "Nano-fiber combat suit",
+ defense: 55,
+ }),
+ createGloves: () => ({
+ piece: "Gloves",
+ description: "Smart-tech grip enhancers",
+ defense: 20,
+ }),
+ emoji: "🚀",
},
Elven: {
name: "Elven Forge",
- createHelmet: () => ({ piece: "Helmet", description: "Enchanted mithril circlet", defense: 25 }),
- createChestPlate: () => ({ piece: "Chest Plate", description: "Blessed elven plate", defense: 45 }),
- createGloves: () => ({ piece: "Gloves", description: "Silken battle gloves", defense: 12 }),
- emoji: "🧝"
- }
+ createHelmet: () => ({
+ piece: "Helmet",
+ description: "Enchanted mithril circlet",
+ defense: 25,
+ }),
+ createChestPlate: () => ({
+ piece: "Chest Plate",
+ description: "Blessed elven plate",
+ defense: 45,
+ }),
+ createGloves: () => ({
+ piece: "Gloves",
+ description: "Silken battle gloves",
+ defense: 12,
+ }),
+ emoji: "🧝",
+ },
};
// Equip a full set of armour from selected factory
@@ -330,34 +375,37 @@ function AbstractFactoryDemo() {
const armourSet = [
factory.createHelmet(),
factory.createChestPlate(),
- factory.createGloves()
+ factory.createGloves(),
];
setEquippedArmour(armourSet);
};
// Calculate total defense
- const totalDefense = equippedArmour.reduce((sum, item) => sum + item.defense, 0);
+ const totalDefense = equippedArmour.reduce(
+ (sum, item) => sum + item.defense,
+ 0
+ );
const currentFactory = factories[factoryType];
return (
@@ -379,8 +428,9 @@ function AbstractFactoryDemo() {
{/* Explanation */}
- Each factory creates a family of related products (helmet, chest plate, gloves)
- that belong together. Try switching factories to see how the pattern ensures all pieces match!
+ Each factory creates a family of related products{" "}
+ (helmet, chest plate, gloves) that belong together. Try switching
+ factories to see how the pattern ensures all pieces match!
);
@@ -398,7 +448,8 @@ function AbstractFactoryDemo() {
---
## 📚Sources
+
Information used in this page was collected from various reliable sources:
- [Refactoring Guru - Abstract Factory](https://refactoring.guru/design-patterns/abstract-factory)
-- [Geeks for Geeks - Abstract Factory pattern](https://www.geeksforgeeks.org/system-design/abstract-factory-pattern/)
\ No newline at end of file
+- [Geeks for Geeks - Abstract Factory pattern](https://www.geeksforgeeks.org/system-design/abstract-factory-pattern/)
diff --git a/docs/creational/builder.mdx b/docs/creational/builder.mdx
index 3703e10..19e51ab 100644
--- a/docs/creational/builder.mdx
+++ b/docs/creational/builder.mdx
@@ -19,9 +19,9 @@ The **Builder** pattern helps to break large, complex constructors and construct
## 🧩The problem
-Imagine an object or class that is getting increasingly more complex to create. This can include initializing many properties, fields, nested objects and core logic. Such initialization logic is often buried deep within a gigantic constructor with many parameters. This can lead to code that is hard to read, maintain and understand. Additionally, if the object can be created in different ways (e.g., with different configurations), the constructor can become overloaded with multiple parameters, leading to confusion and errors.
+Imagine an object or class that is getting increasingly more complex to create. This can include initializing many properties, fields, nested objects and core logic. Such initialization logic is often buried deep within a gigantic constructor with many parameters. This can lead to code that is hard to read, maintain and understand. Additionally, if the object can be created in different ways (e.g., with different configurations), the constructor can become overloaded with multiple parameters, leading to confusion and errors.
-For example, consider a `Computer` class that can be built with various components like CPU, RAM, storage, graphics card, and peripherals. A constructor for such a class could easily become unwieldy and difficult to manage, as it has a lot of parameters you need to manage for each computer. Although they all might need storage, RAM or a graphics card, the specific types and configurations can vary widely per use-case.
+For example, consider a `Computer` class that can be built with various components like CPU, RAM, storage, graphics card, and peripherals. A constructor for such a class could easily become unwieldy and difficult to manage, as it has a lot of parameters you need to manage for each computer. Although they all might need storage, RAM or a graphics card, the specific types and configurations can vary widely per use-case.
## 🛠️Solutions
@@ -36,6 +36,7 @@ This design pattern is often used with something called a **Director**. The `Dir
Think of building a house. You have an architect (the Director) who designs the house and a construction team (the Builder) that follows the architect's plans to build it. The architect specifies what kind of house to build, while the construction team handles the actual building process step by step, such as laying the foundation, erecting walls, installing windows, and so on. The end result is a complete house built according to the architect's specifications, but the construction team takes care of the details.
### 💡Real-world examples
+
Common practical scenarios for applying the Builder pattern include:
- Creating a configuration object with numerous optional parameters.
@@ -43,20 +44,30 @@ Common practical scenarios for applying the Builder pattern include:
## ⚖️Pros and Cons
-
-
## 🔍Applicability
-
+
## 🧭Implementation Plan
@@ -77,77 +88,88 @@ To implement a Builder manually:
```ts
// Product class - the complex object to be built
class Computer {
- constructor(
- public cpu: string,
- public ram: string,
- public storage: string,
- public graphicsCard?: string,
- public peripherals?: string[]
- ) {}
-
- displaySpecs() {
- console.log(`CPU: ${this.cpu}, RAM: ${this.ram}, Storage: ${this.storage}, Graphics Card: ${this.graphicsCard || 'None'}, Peripherals: ${this.peripherals?.join(', ') || 'None'}`);
- }
-
+ constructor(
+ public cpu: string,
+ public ram: string,
+ public storage: string,
+ public graphicsCard?: string,
+ public peripherals?: string[]
+ ) {}
+
+ displaySpecs() {
+ console.log(
+ `CPU: ${this.cpu}, RAM: ${this.ram}, Storage: ${
+ this.storage
+ }, Graphics Card: ${this.graphicsCard || "None"}, Peripherals: ${
+ this.peripherals?.join(", ") || "None"
+ }`
+ );
+ }
}
// Builder interface - defines the steps to build the product
interface ComputerBuilder {
- setCPU(cpu: string): ComputerBuilder;
- setRAM(ram: string): ComputerBuilder;
- setStorage(storage: string): ComputerBuilder;
- setGraphicsCard(graphicsCard: string): ComputerBuilder;
- addPeripheral(peripheral: string): ComputerBuilder;
- build(): Computer;
+ setCPU(cpu: string): ComputerBuilder;
+ setRAM(ram: string): ComputerBuilder;
+ setStorage(storage: string): ComputerBuilder;
+ setGraphicsCard(graphicsCard: string): ComputerBuilder;
+ addPeripheral(peripheral: string): ComputerBuilder;
+ build(): Computer;
}
// Concrete Builder - implements the builder interface
class GamingComputerBuilder implements ComputerBuilder {
- private cpu!: string;
- private ram!: string;
- private storage!: string;
- private graphicsCard!: string;
- private peripherals: string[] = [];
-
- setCPU(cpu: string): ComputerBuilder {
- this.cpu = cpu;
- return this;
- }
-
- setRAM(ram: string): ComputerBuilder {
- this.ram = ram;
- return this;
- }
-
- setStorage(storage: string): ComputerBuilder {
- this.storage = storage;
- return this;
- }
-
- setGraphicsCard(graphicsCard: string): ComputerBuilder {
- this.graphicsCard = graphicsCard;
- return this;
- }
-
- addPeripheral(peripheral: string): ComputerBuilder {
- this.peripherals.push(peripheral);
- return this;
- }
-
- build(): Computer {
- return new Computer(this.cpu, this.ram, this.storage, this.graphicsCard, this.peripherals);
- }
+ private cpu!: string;
+ private ram!: string;
+ private storage!: string;
+ private graphicsCard!: string;
+ private peripherals: string[] = [];
+
+ setCPU(cpu: string): ComputerBuilder {
+ this.cpu = cpu;
+ return this;
+ }
+
+ setRAM(ram: string): ComputerBuilder {
+ this.ram = ram;
+ return this;
+ }
+
+ setStorage(storage: string): ComputerBuilder {
+ this.storage = storage;
+ return this;
+ }
+
+ setGraphicsCard(graphicsCard: string): ComputerBuilder {
+ this.graphicsCard = graphicsCard;
+ return this;
+ }
+
+ addPeripheral(peripheral: string): ComputerBuilder {
+ this.peripherals.push(peripheral);
+ return this;
+ }
+
+ build(): Computer {
+ return new Computer(
+ this.cpu,
+ this.ram,
+ this.storage,
+ this.graphicsCard,
+ this.peripherals
+ );
+ }
}
// Client code - using the builder to create a complex object
const gamingComputer = new GamingComputerBuilder()
- .setCPU("Intel i9")
- .setRAM("32GB")
- .setStorage("1TB SSD")
- .setGraphicsCard("NVIDIA RTX 3080")
- .addPeripheral("Mechanical Keyboard")
- .addPeripheral("Gaming Mouse")
- .build();
+ .setCPU("Intel i9")
+ .setRAM("32GB")
+ .setStorage("1TB SSD")
+ .setGraphicsCard("NVIDIA RTX 3080")
+ .addPeripheral("Mechanical Keyboard")
+ .addPeripheral("Gaming Mouse")
+ .build();
gamingComputer.displaySpecs();
// Output: CPU: Intel i9, RAM: 32GB, Storage: 1TB SSD, Graphics Card: NVIDIA RTX 3080, Peripherals: Mechanical Keyboard, Gaming Mouse
@@ -254,165 +276,232 @@ This sample is to get a 'feel' for the pattern. The code itself may not reflect
```jsx live
function BuilderDemo() {
- // Simple Product class
- class Computer {
- constructor(cpu, ram, storage, graphicsCard, peripherals = []) {
- this.cpu = cpu;
- this.ram = ram;
- this.storage = storage;
- this.graphicsCard = graphicsCard;
- this.peripherals = peripherals;
- }
-
- specs() {
- return `CPU: ${this.cpu}, RAM: ${this.ram}, Storage: ${this.storage}, Graphics: ${this.graphicsCard || 'None'}, Peripherals: ${this.peripherals.length ? this.peripherals.join(', ') : 'None'}`;
- }
+ // Simple Product class
+ class Computer {
+ constructor(cpu, ram, storage, graphicsCard, peripherals = []) {
+ this.cpu = cpu;
+ this.ram = ram;
+ this.storage = storage;
+ this.graphicsCard = graphicsCard;
+ this.peripherals = peripherals;
}
- // Base fluent builder
- class ComputerBuilder {
- constructor() {
- this.reset();
- }
-
- reset() {
- this._cpu = 'Intel i5';
- this._ram = '8GB';
- this._storage = '256GB SSD';
- this._graphicsCard = '';
- this._peripherals = [];
- return this;
- }
-
- setCPU(cpu) { this._cpu = cpu; return this; }
- setRAM(ram) { this._ram = ram; return this; }
- setStorage(storage) { this._storage = storage; return this; }
- setGraphicsCard(graphics) { this._graphicsCard = graphics; return this; }
- addPeripheral(p) { this._peripherals.push(p); return this; }
-
- build() {
- const product = new Computer(this._cpu, this._ram, this._storage, this._graphicsCard, [...this._peripherals]);
- this.reset(); // optional: make builder reusable
- return product;
- }
+ specs() {
+ return `CPU: ${this.cpu}, RAM: ${this.ram}, Storage: ${
+ this.storage
+ }, Graphics: ${this.graphicsCard || "None"}, Peripherals: ${
+ this.peripherals.length ? this.peripherals.join(", ") : "None"
+ }`;
}
+ }
- // React UI to interact with the builder (manual-only)
- const [cpu, setCpu] = React.useState('Intel i5');
- const [ram, setRam] = React.useState('8GB');
- const [storage, setStorage] = React.useState('256GB SSD');
- const [graphics, setGraphics] = React.useState('');
- const [peripheralInput, setPeripheralInput] = React.useState('');
- const [peripherals, setPeripherals] = React.useState([]);
- const [built, setBuilt] = React.useState(null);
- // log entries are objects {type, message} so we can render colored badges reliably
- const [log, setLog] = React.useState([]);
-
- const typeColor = {
- manual: '#1e88e5',
- info: '#43a047',
- warn: '#f57c00',
- error: '#e53935'
- };
-
- function addLog(type, message) {
- setLog(l => [...l, { type, message }]);
- }
-
- function addPeripheral() {
- if (!peripheralInput) return;
- setPeripherals(p => [...p, peripheralInput]);
- addLog('info', `Peripheral added: ${peripheralInput}`);
- setPeripheralInput('');
- }
-
- function buildManual() {
- const b = new ComputerBuilder();
- b.setCPU(cpu).setRAM(ram).setStorage(storage).setGraphicsCard(graphics || undefined);
- peripherals.forEach(p => b.addPeripheral(p));
- const c = b.build();
- setBuilt(c);
- addLog('manual', `Manual build → ${c.specs()}`);
- }
-
- // Clear log and reset the manual form and last-built product
- function clearAndReset() {
- setLog([]);
- setBuilt(null);
- setPeripherals([]);
- // reset fields to defaults
- setCpu('Intel i5');
- setRam('8GB');
- setStorage('256GB SSD');
- setGraphics('');
- }
-
- return (
-
This playground demonstrates manual construction using a Builder. There are no presets or Director in this simplified demo — try changing fields and finalizing a manual build.
+ This playground demonstrates manual construction using a Builder.
+ There are no presets or Director in this simplified demo — try
+ changing fields and finalizing a manual build.
+
+
+
+
+
+
+
Action Log
+
+
+ {log.length ? (
+ log.map((entry, i) => (
+
+
+ {entry.type}
+
+ {entry.message}
+
+ ))
+ ) : (
+
+ No actions yet
+
+ )}
+
+
+
+
+ );
}
```
## 🔗Relations to other patterns
+
- The Builder pattern is often used in conjunction with the [Factory Method](/docs/creational/factory-method) pattern, where the factory method creates a builder instance.
- You can combine [Builder](/docs/creational/builder) with [Bridge](/docs/structural/bridge): the director class plays the role of the abstraction, while different builders act as implementations.
- [Abstract Factories](/docs/creational/abstract-factory), [Builders](/docs/creational/builder), and [Prototype](/docs/creational/prototype) can be implemented as [Singleton](/docs/creational/singleton).
@@ -420,8 +509,9 @@ function BuilderDemo() {
---
## 📚Sources
+
Information used in this page was collected from various reliable sources:
- [Refactoring Guru - Builder](https://refactoring.guru/design-patterns/builder)
- [Geeks for Geeks - Builder Design Pattern](https://www.geeksforgeeks.org/system-design/builder-design-pattern/)
-- [Wikipedia - Builder Pattern](https://en.wikipedia.org/wiki/Builder_pattern)
\ No newline at end of file
+- [Wikipedia - Builder Pattern](https://en.wikipedia.org/wiki/Builder_pattern)
diff --git a/docs/creational/factory-method.mdx b/docs/creational/factory-method.mdx
index 715816b..67ffc39 100644
--- a/docs/creational/factory-method.mdx
+++ b/docs/creational/factory-method.mdx
@@ -2,17 +2,24 @@
title: Factory Method
hide_title: true
description: >
- Give the constructor a break...
+ Give the constructor a break...
---
-import { CreationalPatternTag, BehavioralPatternTag, StructuralPatternTag } from "@site/src/components/pattern-page-features/pattern-alias-and-tag";
+import {
+ CreationalPatternTag,
+ BehavioralPatternTag,
+ StructuralPatternTag,
+} from "@site/src/components/pattern-page-features/pattern-alias-and-tag";
import TwoColumnProsCons from "@site/src/components/pattern-page-features/pros-and-cons";
-import TOCInline from '@theme/TOCInline';
+import TOCInline from "@theme/TOCInline";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import Applicability from "@site/src/components/pattern-page-features/applicability";
-
+
---
@@ -31,6 +38,7 @@ The factory method pattern suggests that you create a separate method (the facto
Imagine a toy factory that produces different types of toys. Instead of having a single assembly line that creates all toys, the factory has multiple specialized assembly lines for different toy types (e.g., action figures, dolls, puzzles). Each assembly line is responsible for creating its specific type of toy. When a customer places an order, the factory uses the appropriate assembly line to produce the requested toy type. This way, the factory can easily add new toy types in the future by simply introducing new assembly lines without disrupting the existing ones.
### 💡Real-world examples
+
Common practical scenarios for applying the Factory Method pattern include:
- A logistics application that needs to create different types of transport vehicles (e.g., trucks, ships, planes) based on the cargo being shipped. Here all vehicles adhere to a common 'vehicle' (interface) type.
@@ -38,26 +46,36 @@ Common practical scenarios for applying the Factory Method pattern include:
## ⚖️ Pros and Cons
-
-
## 🔍Applicability
-
-
+ ]}
+/>
## 🧭Implementation Plan
@@ -68,7 +86,6 @@ To implement a Factory Method manually:
3. Create the concrete creator (sub-) classes, e.g. `BoxShipping` and `EnvelopeShipping`, that override the factory method to instantiate and return specific types of objects that implement the common interface.
4. Client code should call the factory method to create objects, instead of directly instantiating them. This way the client code remains decoupled from the specific classes being created.
-
### 💻Code samples
@@ -100,7 +117,8 @@ class BoxShipping implements ShippingMethod {
// Concrete Products - different shipping implementations
class EnvelopeShipping implements ShippingMethod {
calculateCost(weight: number): number {
- return Math.max(5, weight * 1.2); kg
+ return Math.max(5, weight * 1.2);
+ kg;
}
package(item: string): string {
@@ -135,7 +153,7 @@ abstract class ShippingService {
// Template method - uses the factory method
processOrder(item: string, weight: number): void {
const shipping = this.createShippingMethod();
-
+
console.log(`📦 ${shipping.package(item)}`);
console.log(`💰 Cost: $${shipping.calculateCost(weight).toFixed(2)}`);
console.log(`🚚 ${shipping.deliver()}`);
@@ -162,7 +180,11 @@ class PremiumShippingService extends ShippingService {
}
// Client code - works with any shipping service without knowing the details
-function processCustomerOrder(service: ShippingService, item: string, weight: number): void {
+function processCustomerOrder(
+ service: ShippingService,
+ item: string,
+ weight: number
+): void {
service.processOrder(item, weight);
}
@@ -173,7 +195,6 @@ const weight = 0.5; // 0.5 kg
processCustomerOrder(new StandardShippingService(), toyOrder, weight);
processCustomerOrder(new ExpressShippingService(), toyOrder, weight);
processCustomerOrder(new PremiumShippingService(), toyOrder, weight);
-
```
@@ -187,11 +208,11 @@ class ShippingMethod(ABC):
@abstractmethod
def calculate_cost(self, weight: float) -> float:
pass
-
+
@abstractmethod
def package(self, item: str) -> str:
pass
-
+
@abstractmethod
def deliver(self) -> str:
pass
@@ -200,30 +221,30 @@ class ShippingMethod(ABC):
class BoxShipping(ShippingMethod):
def calculate_cost(self, weight: float) -> float:
return weight * 2.5 # $2.50 per kg for box shipping
-
+
def package(self, item: str) -> str:
return f"{item} securely packed in a protective box with bubble wrap"
-
+
def deliver(self) -> str:
return "Delivered via ground transport (3-5 business days)"
class EnvelopeShipping(ShippingMethod):
def calculate_cost(self, weight: float) -> float:
return max(5, weight * 1.2) # Minimum $5 or $1.20 per kg
-
+
def package(self, item: str) -> str:
return f"{item} placed in a padded envelope"
-
+
def deliver(self) -> str:
return "Delivered via express mail (1-2 business days)"
class InsuredShipping(ShippingMethod):
def calculate_cost(self, weight: float) -> float:
return weight * 3.5 + 15 # Higher cost + insurance fee
-
+
def package(self, item: str) -> str:
return f"{item} packed in reinforced box with tracking and insurance"
-
+
def deliver(self) -> str:
return "Delivered with signature required and full insurance coverage"
@@ -233,11 +254,11 @@ class ShippingService(ABC):
@abstractmethod
def create_shipping_method(self) -> ShippingMethod:
pass
-
+
# Template method - uses the factory method
def process_order(self, item: str, weight: float) -> None:
shipping = self.create_shipping_method()
-
+
print(f"📦 {shipping.package(item)}")
print(f"💰 Cost: ${shipping.calculate_cost(weight):.2f}")
print(f"🚚 {shipping.deliver()}")
@@ -263,7 +284,7 @@ def process_customer_order(service: ShippingService, item: str, weight: float) -
if __name__ == "__main__":
toy_order = "Limited Edition Action Figure"
weight = 0.5 # 0.5 kg
-
+
process_customer_order(StandardShippingService(), toy_order, weight)
process_customer_order(ExpressShippingService(), toy_order, weight)
process_customer_order(PremiumShippingService(), toy_order, weight)
@@ -292,30 +313,34 @@ function FactoryMethodDemo() {
type: "Standard Box",
calculateCost: (w) => w * 2.5,
deliveryTime: "3-5 business days",
- icon: "📦"
+ icon: "📦",
});
const createExpressShipping = () => ({
type: "Express Envelope",
calculateCost: (w) => Math.max(5, w * 1.2),
deliveryTime: "1-2 business days",
- icon: "✉️"
+ icon: "✉️",
});
const createPremiumShipping = () => ({
type: "Premium Insured",
calculateCost: (w) => w * 3.5 + 15,
deliveryTime: "Next day + signature",
- icon: "🎁"
+ icon: "🎁",
});
// Factory Method - returns the appropriate shipping method
const getShippingMethod = (type) => {
- switch(type) {
- case "standard": return createStandardShipping();
- case "express": return createExpressShipping();
- case "premium": return createPremiumShipping();
- default: return createStandardShipping();
+ switch (type) {
+ case "standard":
+ return createStandardShipping();
+ case "express":
+ return createExpressShipping();
+ case "premium":
+ return createPremiumShipping();
+ default:
+ return createStandardShipping();
}
};
@@ -323,7 +348,7 @@ function FactoryMethodDemo() {
const handleProcessOrder = () => {
const shipping = getShippingMethod(shippingType);
const cost = shipping.calculateCost(weight);
-
+
const order = {
id: Date.now(),
item,
@@ -331,9 +356,9 @@ function FactoryMethodDemo() {
shipping: shipping.type,
cost: cost.toFixed(2),
delivery: shipping.deliveryTime,
- icon: shipping.icon
+ icon: shipping.icon,
};
-
+
setOrders([order, ...orders]);
};
@@ -342,9 +367,16 @@ function FactoryMethodDemo() {
return (
@@ -425,11 +457,20 @@ function FactoryMethodDemo() {
)}
-
-
- 💡 Factory Method in Action: Each shipping type is created by a
- different factory function. The getShippingMethod() decides which factory
- to use, keeping the order processing code decoupled from specific shipping implementations.
+
+
+ 💡 Factory Method in Action: Each shipping type is
+ created by a different factory function. The{" "}
+ getShippingMethod() decides which factory to use, keeping
+ the order processing code decoupled from specific shipping
+ implementations.
);
@@ -437,6 +478,7 @@ function FactoryMethodDemo() {
```
## 🔗Relations to other patterns
+
- Lots of people start with the [Factory Method](/docs/creational/factory-method) pattern prior to implementing something such as [Abstract Factory](/docs/creational/abstract-factory), [Builder](/docs/creational/builder) or [Prototype](/docs/creational/prototype) patterns. The latter patterns are more flexible, but also come with increased complexity.
- [Prototype](/docs/creational/prototype) isn't based on inheritance, so it does not have its drawbacks. On the other hand, [Prototype](/docs/creational/prototype) requires a complicated initialization of the cloned object. [Factory Method](/docs/creational/factory-method) is based on inheritance but doesn't require an initialization step.
- [Factory Method](/docs/creational/factory-method) is a specialization of [Template Method](/docs/behavioral/template-method). At the same time, a Factory Method may serve as a step in a large Template Method.
@@ -444,6 +486,7 @@ function FactoryMethodDemo() {
---
## 📚Sources
+
Information used in this page was collected from various reliable sources:
- [Refactoring Guru - Factory Method](https://refactoring.guru/design-patterns/factory-method)
diff --git a/docs/creational/prototype.mdx b/docs/creational/prototype.mdx
index 4cf64eb..a4e3d65 100644
--- a/docs/creational/prototype.mdx
+++ b/docs/creational/prototype.mdx
@@ -7,13 +7,12 @@ description: >
import { CreationalPatternTag } from "@site/src/components/pattern-page-features/pattern-alias-and-tag";
import TwoColumnProsCons from "@site/src/components/pattern-page-features/pros-and-cons";
-import TOCInline from '@theme/TOCInline';
+import TOCInline from "@theme/TOCInline";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import Applicability from "@site/src/components/pattern-page-features/applicability";
-
----
+
**Prototype** is a pattern that lets you copy existing objects without making your code dependant on their actual implementations or classes.
@@ -23,14 +22,14 @@ Let's say you want to create a copy or clone of an existing object. However, the
## 🛠️Solutions
-The Prototype pattern addresses these challenges by allowing you to create new objects by cloning existing ones, known as **prototypes**. Instead of initializing objects directly, you copy an existing object, which serves as a prototype. This is typically achieved through a method like `clone()`, which creates a copy of the object. An object which supports cloning or copying is referred to as a *prototype*. When you have lots of configurations or dozens of fields, prototyping may serve as a good alternative to subclassing.
-
+The Prototype pattern addresses these challenges by allowing you to create new objects by cloning existing ones, known as **prototypes**. Instead of initializing objects directly, you copy an existing object, which serves as a prototype. This is typically achieved through a method like `clone()`, which creates a copy of the object. An object which supports cloning or copying is referred to as a _prototype_. When you have lots of configurations or dozens of fields, prototyping may serve as a good alternative to subclassing.
### 🏛️Metaphors
Imagine a sculptor who has created a beautiful statue. Instead of sculpting a new statue from scratch each time, the sculptor makes a mold of the original statue. Whenever a new statue is needed, the sculptor simply pours material into the mold to create an exact replica. In this scenario, the original statue is the prototype, and the mold allows for easy duplication without needing to recreate the entire sculpture process.
### 💡Real-world examples
+
Common practical scenarios for applying the Prototype pattern include:
- Cloning complex objects in a game (e.g., characters, items) without knowing their exact class.
@@ -39,25 +38,33 @@ Common practical scenarios for applying the Prototype pattern include:
## ⚖️ Pros and Cons
-
-
## 🔍Applicability
-
-
+
## 🧭Implementation Plan
@@ -69,7 +76,6 @@ To implement a Prototype manually:
4. [OPTIONAL] Create a Prototype Registry, a centralized place to store and manage prototype instances, or use a Factory that utilizes prototypes for object creation.
5. Replace the direct call to the constructor with calls to the clone method.
-
### 💻Code samples
@@ -100,7 +106,6 @@ class Person implements Prototype {
const original = new Person("Alice", 30);
const copy = original.clone();
console.log(copy); // Person { name: 'Alice', age: 30 }
-
```
@@ -141,7 +146,7 @@ function PrototypeDemo() {
const animalPrototype = {
speak() {
return `Hi, I'm a ${this.type} called ${this.name}`;
- }
+ },
};
// Function to clone the prototype and create a new animal
@@ -170,7 +175,7 @@ function PrototypeDemo() {
Type:{" "}
setType(e.target.value)}
+ onChange={(e) => setType(e.target.value)}
style={{ marginRight: 8 }}
/>
@@ -178,7 +183,7 @@ function PrototypeDemo() {
Name:{" "}
setName(e.target.value)}
+ onChange={(e) => setName(e.target.value)}
style={{ marginRight: 8 }}
/>
@@ -189,17 +194,18 @@ function PrototypeDemo() {
{animal.speak()}
))}
-
- Each new animal is cloned from animalPrototype. Try changing the
- fields and creating multiple animals to see prototype sharing in action!
+
+ Each new animal is cloned from animalPrototype. Try
+ changing the fields and creating multiple animals to see prototype
+ sharing in action!
);
}
-
```
## 🔗Relations to other patterns
+
- Designs that make heavy use of [Composite](/docs/structural/composite.mdx) and [Decorator](/docs/structural/decorator.mdx) can often benefit from using [Prototype](/docs/creational/prototype). Applying the pattern lets you clone complex structures instead of re-constructing them from scratch.
- [Prototypes](/docs/creational/prototype) can help when you need to save copies of [Commands](/docs/behavioral/command.mdx) into history.
- [Abstract Factories](/docs/creational/abstract-factory) classes are often based on a set of [Factory Methods](/docs/creational/factory-method.mdx), but you can also use [Prototype](/docs/creational/prototype) to compose the methods on these classes.
@@ -208,8 +214,9 @@ function PrototypeDemo() {
---
## 📚Sources
+
Information used in this page was collected from various reliable sources:
- [Refactoring Guru - Prototype](https://refactoring.guru/design-patterns/prototype)
- [Geeks for Geeks - Prototype Design Pattern](https://www.geeksforgeeks.org/system-design/prototype-design-pattern/)
-- [Wikipedia - Prototype Pattern](https://en.wikipedia.org/wiki/Prototype_pattern)
\ No newline at end of file
+- [Wikipedia - Prototype Pattern](https://en.wikipedia.org/wiki/Prototype_pattern)
diff --git a/docs/creational/singleton.mdx b/docs/creational/singleton.mdx
index 39232ee..0b33b03 100644
--- a/docs/creational/singleton.mdx
+++ b/docs/creational/singleton.mdx
@@ -9,7 +9,7 @@ import { CreationalPatternTag } from "@site/src/components/pattern-page-features
import TwoColumnProsCons from "@site/src/components/pattern-page-features/pros-and-cons";
import Applicability from "@site/src/components/pattern-page-features/applicability";
-
+
---
@@ -43,6 +43,7 @@ one controlling authority, the government.
In this example, the government is considered a Singleton.
### 💡Real-world examples
+
Common practical scenarios for applying the Singleton pattern include:
- Application configuration managers, ensuring consistent settings across the app.
@@ -52,36 +53,47 @@ Common practical scenarios for applying the Singleton pattern include:
## ⚖️ Pros and Cons
-
-
## 🔍Applicability
-
-
+
## 🧭Implementation Plan
To implement a Singleton manually:
-1. Make the class constructor private so no external code can instantiate it directly.
-2. Create a static field to hold the single instance.
-3. Provide a static method that returns the existing instance or creates it lazily if it doesn't exist yet.
+1. Make the class constructor private so no external code can instantiate it directly.
+2. Create a static field to hold the single instance.
+3. Provide a static method that returns the existing instance or creates it lazily if it doesn't exist yet.
4. Handle thread-safety when the Singleton may be accessed concurrently.
-
### 💻Code samples
import Tabs from "@theme/Tabs";
@@ -92,20 +104,20 @@ import TabItem from "@theme/TabItem";
```ts
class Singleton {
- // Static property to hold the single instance.
- private static instance: Singleton;
-
- // Private constructor to prevent direct instantiation.
- private constructor() {}
+ // Static property to hold the single instance.
+ private static instance: Singleton;
- // Static method to either retrieve the single instance or instantiate it if it doesn't exist.
- public static getInstance(): Singleton {
- if (!Singleton.instance) {
- Singleton.instance = new Singleton();
- }
+ // Private constructor to prevent direct instantiation.
+ private constructor() {}
- return Singleton.instance;
+ // Static method to either retrieve the single instance or instantiate it if it doesn't exist.
+ public static getInstance(): Singleton {
+ if (!Singleton.instance) {
+ Singleton.instance = new Singleton();
}
+
+ return Singleton.instance;
+ }
}
```
@@ -123,7 +135,7 @@ class Singleton:
if cls._instance is None:
# Create and store the single instance.
cls._instance = super().__new__(cls)
-
+
# Return the single instance.
return cls._instance
```
@@ -147,25 +159,29 @@ function SingletonDemo() {
const singletonRef = React.useRef(null);
// Create a new singleton instance with a 6-digit id
- const createInstance = () => ({ id: (Math.floor(100000 + Math.random() * 900000)).toString() });
+ const createInstance = () => ({
+ id: Math.floor(100000 + Math.random() * 900000).toString(),
+ });
// Singleton API
const getInstance = () => {
if (!singletonRef.current) singletonRef.current = createInstance();
return singletonRef.current;
};
- const resetInstance = () => { singletonRef.current = null; };
+ const resetInstance = () => {
+ singletonRef.current = null;
+ };
// Button handlers
const handleCreate = () => {
const obj = getInstance();
setInstance(obj);
- setLog(l => [...l, `Create Instance → id: ${obj.id}`]);
+ setLog((l) => [...l, `Create Instance → id: ${obj.id}`]);
};
const handleReset = () => {
resetInstance();
setInstance(null);
- setLog(l => [...l, 'Singleton reset']);
+ setLog((l) => [...l, "Singleton reset"]);
};
const handleClearLogs = () => setLog([]);
@@ -175,8 +191,12 @@ function SingletonDemo() {
Singleton Instance Demo
{/* Create, Reset, and Clear Logs buttons */}
-
-
+
+
{/* Show instance info if exists */}
{instance && (
@@ -198,6 +218,7 @@ function SingletonDemo() {
```
## 🔗Relations to other patterns
+
- [Factory Method](/docs/creational/factory-method.mdx), [Abstract Factory](/docs/creational/abstract-factory.mdx), or [Builder](/docs/creational/builder.mdx) may rely on Singleton to ensure a single configuration or product source is used globally.
- [Facade](/docs/structural/facade.mdx) objects can themselves be singletons that encapsulate a subsystem behind a single interface.
- Unlike [Flyweight](/docs/structural/flyweight.mdx), which manages many shared objects, Singleton restricts creation to precisely one object.
@@ -205,8 +226,9 @@ function SingletonDemo() {
---
## 📚Sources
+
Information used in this page was collected from various reliable sources:
- [Refactoring Guru - Singleton](https://refactoring.guru/design-patterns/singleton)
- [Wikipedia - Singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern)
-- [W3Schools - Singleton Method Design Pattern](https://www.geeksforgeeks.org/system-design/singleton-design-pattern/)
\ No newline at end of file
+- [W3Schools - Singleton Method Design Pattern](https://www.geeksforgeeks.org/system-design/singleton-design-pattern/)
diff --git a/docs/structural/adapter.mdx b/docs/structural/adapter.mdx
index e9e84f7..8e047a5 100644
--- a/docs/structural/adapter.mdx
+++ b/docs/structural/adapter.mdx
@@ -7,12 +7,12 @@ description: >
import { StructuralPatternTag } from "@site/src/components/pattern-page-features/pattern-alias-and-tag";
import TwoColumnProsCons from "@site/src/components/pattern-page-features/pros-and-cons";
-import TOCInline from '@theme/TOCInline';
+import TOCInline from "@theme/TOCInline";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import Applicability from "@site/src/components/pattern-page-features/applicability";
-
+
---
@@ -20,7 +20,7 @@ The **adapter pattern** is a structural design pattern that allows incompatible
## 🧩The problem
-Let's say you are creating a fantastic new weather app. Instead of creating your own weather stations you'd like to implement weather data from an actual meteorological institute. You achieve this by querying their API. However, this data is returned YAML format, instead of the expected JSON which your application is built on. Thus you cannot rely on this data directly, you'll need to transform or modify it in such a way that your application can understand it. That is where the adapter design pattern comes into play.
+Let's say you are creating a fantastic new weather app. Instead of creating your own weather stations you'd like to implement weather data from an actual meteorological institute. You achieve this by querying their API. However, this data is returned YAML format, instead of the expected JSON which your application is built on. Thus you cannot rely on this data directly, you'll need to transform or modify it in such a way that your application can understand it. That is where the adapter design pattern comes into play.
## 🛠️Solutions
@@ -42,21 +42,28 @@ Common practical scenarios for applying the Adapter pattern include:
## ⚖️ Pros and Cons
-
-
## 🔍Applicability
-
-
+
## 🧭Implementation Plan
@@ -80,7 +87,7 @@ class YAMLWeatherService {
return `temperature: 26
humidity: 65
condition: sunny`;
- }
+ }
}
// Modern interface expected by the application
@@ -98,15 +105,15 @@ class WeatherAdapter implements WeatherService {
getWeather(): { temperature: number; humidity: number; condition: string } {
const yamlData = this.yamlService.getWeatherData();
-
+
// Convert YAML to JSON
- const lines = yamlData.split('\n');
+ const lines = yamlData.split("\n");
const data: any = {};
- lines.forEach(line => {
- const [key, value] = line.split(': ');
+ lines.forEach((line) => {
+ const [key, value] = line.split(": ");
data[key] = isNaN(Number(value)) ? value : Number(value);
});
-
+
return data;
}
}
@@ -141,14 +148,14 @@ class WeatherAdapter(WeatherService):
def get_weather(self):
yaml_data = self.yaml_service.get_weather_data()
-
+
# Convert YAML to dictionary
lines = yaml_data.split('\n')
data = {}
for line in lines:
key, value = line.split(': ')
data[key] = int(value) if value.isdigit() else value
-
+
return data
# Usage
@@ -175,8 +182,8 @@ function AdapterDemo() {
fetchData: () => ({
temp_celsius: "22",
humid_percent: "65",
- sky_condition: "sunny"
- })
+ sky_condition: "sunny",
+ }),
};
// Adapter that converts legacy format to modern format
@@ -186,9 +193,9 @@ function AdapterDemo() {
return {
temperature: parseInt(legacy.temp_celsius),
humidity: parseInt(legacy.humid_percent),
- condition: legacy.sky_condition
+ condition: legacy.sky_condition,
};
- }
+ },
};
const [usingAdapter, setUsingAdapter] = React.useState(false);
@@ -208,17 +215,26 @@ function AdapterDemo() {
Adapter Pattern demo
Fetch weather data using different interfaces
-
+
-
+
{data && (
-
-
{usingAdapter ? "✅ Modern Format (via Adapter)" : "⚠️ Legacy Format"}
+
+
+ {usingAdapter
+ ? "✅ Modern Format (via Adapter)"
+ : "⚠️ Legacy Format"}
+
{JSON.stringify(data, null, 2)}
@@ -226,8 +242,9 @@ function AdapterDemo() {
)}
- The adapter converts the legacy API's format (temp_celsius, humid_percent)
- into a modern format (temperature, humidity) that your app expects!
+ The adapter converts the legacy API's format (temp_celsius,{" "}
+ humid_percent) into a modern format (
+ temperature, humidity) that your app expects!
);
@@ -235,6 +252,7 @@ function AdapterDemo() {
```
## 🔗Relations to other patterns
+
- [Bridge](/docs/structural/bridge.mdx) is usually designed up-front, letting you develop parts of an application independently of each other. On the other hand, [Adapter](/docs/structural/adapter.mdx) is commonly used with an existing app to make some otherwise-incompatible classes work together nicely.
- [Adapter](/docs/structural/adapter.mdx) provides a completely different interface for accessing an existing object. On the other hand, with the [Decorator](/docs/structural/decorator.mdx) pattern the interface either stays the same or gets extended. In addition, [Decorator](/docs/structural/decorator.mdx) supports recursive composition, which isn't possible when you use [Adapter](/docs/structural/adapter.mdx).
- With [Adapter](/docs/structural/adapter.mdx) you access an existing object via different interface. With [Proxy](/docs/structural/proxy.mdx), the interface stays the same. With [Decorator](/docs/structural/decorator.mdx) you access the object via an enhanced interface.
@@ -244,7 +262,8 @@ function AdapterDemo() {
---
## 📚Sources
+
Information used in this page was collected from various reliable sources:
- [Refactoring Guru - Adapter](https://refactoring.guru/design-patterns/adapter)
-- [Wikipedia - Adapter Pattern](https://en.wikipedia.org/wiki/Adapter_pattern)
\ No newline at end of file
+- [Wikipedia - Adapter Pattern](https://en.wikipedia.org/wiki/Adapter_pattern)
diff --git a/docs/structural/bridge.mdx b/docs/structural/bridge.mdx
index 759b4c9..50a0448 100644
--- a/docs/structural/bridge.mdx
+++ b/docs/structural/bridge.mdx
@@ -1,5 +1,330 @@
---
title: Bridge
+hide_title: true
+description: >
+ Making sure the customer can't ruin your existing implementation.
---
-This is Bridge Pattern page
+import {
+ CreationalPatternTag,
+ BehavioralPatternTag,
+ StructuralPatternTag,
+} from "@site/src/components/pattern-page-features/pattern-alias-and-tag";
+import TwoColumnProsCons from "@site/src/components/pattern-page-features/pros-and-cons";
+import TOCInline from "@theme/TOCInline";
+import Tabs from "@theme/Tabs";
+import TabItem from "@theme/TabItem";
+import Applicability from "@site/src/components/pattern-page-features/applicability";
+import BridgeSolutionSvg from "@site/static/img/patterns/bridge-solution.drawio.svg";
+import BridgeProblemSvg from "@site/static/img/patterns/bridge-problem.drawio.svg";
+
+
+
+---
+
+The **Bridge pattern** is a structural design pattern that decouples an abstraction from its implementation, allowing both to vary independently.
+
+## 🧩The problem
+
+Let's say you have a customer that wants to export the contents of a database to a certain file type. In this example the customer has asked to export to `JSON` format. You create an interface called `IExporter`, you also make a concrete implementation called `JsonExporter` that handles the exporting of a user profile to a JSON file. Currently, the customer only wants to export settings to JSON format, so this implementation works fine. Eventually, since customers never know what they want, the customer comes back and suggest exporting settings and friends to `JSON` as well. You expand the `JsonExporter` class to handle both settings and friends exporting.
+
+The customer comes back, they request to also supported `CSV` format for exporting. You create a new class called `CsvExporter` that handles exporting to CSV format. You'll know need to expand this class to handle both settings and friends exporting as well. As you can see, this approach quickly leads to a combinatorial explosion of classes as you add more export formats and data types. Each entity that the customer wants to export requires a new class for each format, leading to a maintenance nightmare.
+
+
+
+
+
+## 🛠️Solutions
+
+This is exactly where the **Bridge pattern** comes in. The Bridge pattern suggests that we separate the abstraction (the entities to be exported) from its implementation (the export format). By doing so, we can create a hierarchy of abstractions and implementations that can be combined in various ways without leading to a combinatorial explosion of classes.
+
+In this structure, instead of having a single `IExporter` interface, we can have an `Exporter` abstraction that defines the export operation. Then, we can have concrete implementations for each export format, such as `JsonExporter` and `CsvExporter`. Each entity to be exported, like `Settings` and `Friends`, can then use an instance of the `Exporter` abstraction to perform the export operation.
+
+
+
+
+
+By applying composition over inheritence, we can easily add new export formats or entities to be exported without modifying existing code. This makes the system more flexible and easier to maintain.
+
+### 🏛️Metaphors
+
+Imagine a bridge that connects two separate islands, allowing people to travel between them without being tied to a specific path or route. Both sides of the river can grow and develop independently, while the bridge provides a flexible connection between them.
+
+### 💡Real-world examples
+
+Common practical scenarios for applying the X pattern include:
+
+- Graphic rendering systems where shapes (abstractions) can be drawn using different rendering engines (implementations).
+- Database access layers where the abstraction represents database operations and the implementation represents different database systems (e.g., MySQL, PostgreSQL).
+
+## ⚖️ Pros and Cons
+
+
+
+## 🔍Applicability
+
+
+
+## 🧭Implementation Plan
+
+To implement a Bridge manually:
+
+1. Identify the orthogonal dimensions in your system that can vary independently. For example, the entities to export (Settings, Friends, Profile) and the export formats (JSON, CSV, XML).
+2. Create an implementation interface that defines operations for one dimension (e.g., `ExportFormat` with an `export()` method). This will be the "bridge" that implementations will follow.
+3. Create concrete implementation classes for each variant of that dimension (e.g., `JsonExporter`, `CsvExporter`, `XmlExporter`) that implement the interface from step 2.
+4. Create an abstraction class for the other dimension that holds a reference to the implementation interface. This class delegates work to the implementation object (e.g., `DataExporter` with a reference to `ExportFormat`).
+5. Create refined abstraction subclasses for each variant of the abstraction dimension (e.g., `SettingsExporter`, `FriendsExporter`, `ProfileExporter`). Each subclass uses the implementation object to perform its specific operations.
+6. Client code can now combine any abstraction with any implementation, allowing both hierarchies to evolve independently.
+
+### 💻Code samples
+
+
+
+
+```ts
+// Implementation interface
+interface ExportFormat {
+ export(data: string): string;
+}
+
+// Concrete implementations
+class JsonExporter implements ExportFormat {
+ export(data: string): string {
+ return `{ "data": "${data}" }`;
+ }
+}
+
+class CsvExporter implements ExportFormat {
+ export(data: string): string {
+ return `data\n${data}`;
+ }
+}
+
+// Abstraction
+abstract class DataExporter {
+ constructor(protected format: ExportFormat) {}
+
+ abstract exportData(): string;
+}
+
+// Refined abstractions
+class SettingsExporter extends DataExporter {
+ exportData(): string {
+ const settingsData = "theme=dark;language=en";
+ return this.format.export(settingsData);
+ }
+}
+
+class FriendsExporter extends DataExporter {
+ exportData(): string {
+ const friendsData = "Alice,Bob,Charlie";
+ return this.format.export(friendsData);
+ }
+}
+
+// Usage
+const settingsJson = new SettingsExporter(new JsonExporter());
+console.log(settingsJson.exportData()); // { "data": "theme=dark;language=en" }
+
+const friendsCsv = new FriendsExporter(new CsvExporter());
+console.log(friendsCsv.exportData()); // data\nAlice,Bob,Charlie
+```
+
+
+
+
+```python
+from abc import ABC, abstractmethod
+
+# Implementation interface
+class ExportFormat(ABC):
+ @abstractmethod
+ def export(self, data: str) -> str:
+ pass
+
+# Concrete implementations
+class JsonExporter(ExportFormat):
+ def export(self, data: str) -> str:
+ return f'{{ "data": "{data}" }}'
+
+class CsvExporter(ExportFormat):
+ def export(self, data: str) -> str:
+ return f'data\n{data}'
+
+# Abstraction
+class DataExporter(ABC):
+ def __init__(self, format: ExportFormat):
+ self.format = format
+
+ @abstractmethod
+ def export_data(self) -> str:
+ pass
+
+# Refined abstractions
+class SettingsExporter(DataExporter):
+ def export_data(self) -> str:
+ settings_data = "theme=dark;language=en"
+ return self.format.export(settings_data)
+
+class FriendsExporter(DataExporter):
+ def export_data(self) -> str:
+ friends_data = "Alice,Bob,Charlie"
+ return self.format.export(friends_data)
+
+# Usage
+settings_json = SettingsExporter(JsonExporter())
+print(settings_json.export_data()) # { "data": "theme=dark;language=en" }
+
+friends_csv = FriendsExporter(CsvExporter())
+print(friends_csv.export_data()) # data\nAlice,Bob,Charlie
+```
+
+
+
+### 🎮Playground
+
+:::note
+
+This sample is to get a 'feel' for the pattern. The code itself may not reflect a correct implementation of the pattern.
+
+:::
+
+```jsx live
+function BridgeDemo() {
+ // Export format implementations (the "bridge" implementations)
+ const formats = {
+ JSON: (data) => `{ "data": "${data}" }`,
+ CSV: (data) => `data\n${data}`,
+ XML: (data) => `${data}`,
+ };
+
+ // Data types (abstractions)
+ const dataTypes = {
+ Settings: () => "theme=dark;language=en",
+ Friends: () => "Alice,Bob,Charlie",
+ Profile: () => "John Doe,john@example.com",
+ };
+
+ const [selectedFormat, setSelectedFormat] = React.useState("JSON");
+ const [selectedData, setSelectedData] = React.useState("Settings");
+ const [exports, setExports] = React.useState([]);
+
+ const handleExport = () => {
+ const data = dataTypes[selectedData]();
+ const result = formats[selectedFormat](data);
+ const timestamp = new Date().toLocaleTimeString();
+ setExports([
+ { dataType: selectedData, format: selectedFormat, result, timestamp },
+ ...exports,
+ ]);
+ };
+
+ return (
+
+
Bridge Pattern demo
+
+
+
+
+
+
+
+ Export History:
+ {exports.length === 0 ? (
+
+ No exports yet. Select a data type and format, then click Export.
+