Loading learning content...
Understanding the Bridge Pattern's structure is essential, but recognizing where to apply it is equally important. The pattern appears throughout production software—from graphics libraries to messaging systems, from persistence layers to remote communication.
This page explores concrete, production-quality examples that demonstrate when and how to leverage the Bridge Pattern effectively.
By the end of this page, you will see Bridge Pattern applied to UI rendering systems, device control frameworks, messaging platforms, persistence layers, and report generation. Each example demonstrates the pattern's core benefit: independent evolution of abstraction and implementation hierarchies.
Graphics systems exemplify the Bridge Pattern perfectly. You have multiple shape abstractions (circle, rectangle, polygon) that need to render on multiple platforms (OpenGL, DirectX, Vulkan, software renderer). Without Bridge, you'd need M × N shape-platform combinations.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
// IMPLEMENTOR: Platform-specific rendering primitivesinterface GraphicsAPI { drawLine(x1: number, y1: number, x2: number, y2: number, color: string): void; drawArc(cx: number, cy: number, radius: number, startAngle: number, endAngle: number, color: string): void; fillPolygon(points: [number, number][], color: string): void; setLineWidth(width: number): void;} // CONCRETE IMPLEMENTORSclass OpenGLRenderer implements GraphicsAPI { drawLine(x1: number, y1: number, x2: number, y2: number, color: string): void { console.log(`[OpenGL] glBegin(GL_LINES); glVertex2f(${x1}, ${y1}); glVertex2f(${x2}, ${y2});`); } drawArc(cx: number, cy: number, radius: number, startAngle: number, endAngle: number, color: string): void { console.log(`[OpenGL] Drawing arc with tessellation at (${cx}, ${cy})`); } fillPolygon(points: [number, number][], color: string): void { console.log(`[OpenGL] glBegin(GL_POLYGON); ... ${points.length} vertices`); } setLineWidth(width: number): void { console.log(`[OpenGL] glLineWidth(${width})`); }} class CanvasRenderer implements GraphicsAPI { private ctx: string = "CanvasRenderingContext2D"; drawLine(x1: number, y1: number, x2: number, y2: number, color: string): void { console.log(`[Canvas] ctx.moveTo(${x1}, ${y1}); ctx.lineTo(${x2}, ${y2}); ctx.stroke();`); } drawArc(cx: number, cy: number, radius: number, startAngle: number, endAngle: number, color: string): void { console.log(`[Canvas] ctx.arc(${cx}, ${cy}, ${radius}, ${startAngle}, ${endAngle})`); } fillPolygon(points: [number, number][], color: string): void { console.log(`[Canvas] ctx.beginPath(); ... ctx.fill()`); } setLineWidth(width: number): void { console.log(`[Canvas] ctx.lineWidth = ${width}`); }} // ABSTRACTION: Shape base classabstract class Shape { protected renderer: GraphicsAPI; protected color: string; protected lineWidth: number = 1; constructor(renderer: GraphicsAPI, color: string) { this.renderer = renderer; this.color = color; } abstract draw(): void; abstract getArea(): number;} // REFINED ABSTRACTIONSclass Circle extends Shape { constructor( renderer: GraphicsAPI, color: string, private cx: number, private cy: number, private radius: number ) { super(renderer, color); } draw(): void { this.renderer.setLineWidth(this.lineWidth); this.renderer.drawArc(this.cx, this.cy, this.radius, 0, Math.PI * 2, this.color); } getArea(): number { return Math.PI * this.radius ** 2; }} class Rectangle extends Shape { constructor( renderer: GraphicsAPI, color: string, private x: number, private y: number, private width: number, private height: number ) { super(renderer, color); } draw(): void { const points: [number, number][] = [ [this.x, this.y], [this.x + this.width, this.y], [this.x + this.width, this.y + this.height], [this.x, this.y + this.height] ]; this.renderer.fillPolygon(points, this.color); } getArea(): number { return this.width * this.height; }} // Usage: Any shape × Any rendererconst opengl = new OpenGLRenderer();const canvas = new CanvasRenderer(); const circleGL = new Circle(opengl, "red", 100, 100, 50);const circleCanvas = new Circle(canvas, "blue", 100, 100, 50); circleGL.draw(); // Uses OpenGLcircleCanvas.draw(); // Uses CanvasSmart home systems control many device types (TV, lights, thermostat) through multiple control interfaces (basic remote, advanced remote, voice control). Bridge separates device capabilities from control complexity.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
// IMPLEMENTOR: Device capabilities interfaceinterface Device { getName(): string; isEnabled(): boolean; enable(): void; disable(): void; getStatus(): Record<string, any>;} // CONCRETE IMPLEMENTORS: Different devicesclass SmartTV implements Device { private on = false; private volume = 50; private channel = 1; getName(): string { return "Smart TV"; } isEnabled(): boolean { return this.on; } enable(): void { this.on = true; console.log("TV: Powering on..."); } disable(): void { this.on = false; console.log("TV: Powering off..."); } getStatus(): Record<string, any> { return { on: this.on, volume: this.volume, channel: this.channel }; } setVolume(v: number): void { this.volume = Math.max(0, Math.min(100, v)); } setChannel(c: number): void { this.channel = c; }} class SmartLight implements Device { private on = false; private brightness = 100; private color = "#FFFFFF"; getName(): string { return "Smart Light"; } isEnabled(): boolean { return this.on; } enable(): void { this.on = true; console.log("Light: Turning on..."); } disable(): void { this.on = false; console.log("Light: Turning off..."); } getStatus(): Record<string, any> { return { on: this.on, brightness: this.brightness, color: this.color }; } setBrightness(b: number): void { this.brightness = Math.max(0, Math.min(100, b)); } setColor(c: string): void { this.color = c; }} // ABSTRACTION: Remote control baseabstract class RemoteControl { protected device: Device; constructor(device: Device) { this.device = device; } togglePower(): void { if (this.device.isEnabled()) { this.device.disable(); } else { this.device.enable(); } } abstract showStatus(): void;} // REFINED ABSTRACTIONS: Different control complexity levelsclass BasicRemote extends RemoteControl { showStatus(): void { console.log(`[${this.device.getName()}] Power: ${this.device.isEnabled() ? 'ON' : 'OFF'}`); }} class AdvancedRemote extends RemoteControl { showStatus(): void { const status = this.device.getStatus(); console.log(`[${this.device.getName()}] Detailed Status:`); Object.entries(status).forEach(([k, v]) => console.log(` ${k}: ${v}`)); } mute(): void { if (this.device instanceof SmartTV) { (this.device as SmartTV).setVolume(0); console.log("TV muted"); } }} // Usageconst tv = new SmartTV();const light = new SmartLight(); const basicTVRemote = new BasicRemote(tv);const advancedLightRemote = new AdvancedRemote(light); basicTVRemote.togglePower();basicTVRemote.showStatus(); advancedLightRemote.togglePower();advancedLightRemote.showStatus();Enterprise messaging involves multiple message types (email, SMS, push, Slack) and multiple providers (AWS, Twilio, Firebase). Bridge enables any message type to use any provider.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
// IMPLEMENTOR: Message delivery primitivesinterface MessageGateway { sendPlainText(to: string, text: string): Promise<string>; sendRichContent(to: string, html: string, attachments?: string[]): Promise<string>; getDeliveryStatus(messageId: string): Promise<'pending' | 'sent' | 'delivered' | 'failed'>;} // CONCRETE IMPLEMENTORSclass AWSMessagingGateway implements MessageGateway { async sendPlainText(to: string, text: string): Promise<string> { console.log(`[AWS SES] Plain text to ${to}`); return `aws-${Date.now()}`; } async sendRichContent(to: string, html: string, attachments?: string[]): Promise<string> { console.log(`[AWS SES] Rich email with ${attachments?.length || 0} attachments`); return `aws-rich-${Date.now()}`; } async getDeliveryStatus(messageId: string): Promise<'pending' | 'sent' | 'delivered' | 'failed'> { return 'delivered'; }} class TwilioGateway implements MessageGateway { async sendPlainText(to: string, text: string): Promise<string> { const truncated = text.length > 160 ? text.slice(0, 157) + '...' : text; console.log(`[Twilio] SMS to ${to}: "${truncated}"`); return `twilio-${Date.now()}`; } async sendRichContent(to: string, html: string, attachments?: string[]): Promise<string> { // SMS doesn't support rich content return this.sendPlainText(to, 'View content online: https://app.example.com'); } async getDeliveryStatus(messageId: string): Promise<'pending' | 'sent' | 'delivered' | 'failed'> { return 'sent'; }} // ABSTRACTION: Message typesabstract class Message { protected gateway: MessageGateway; constructor(gateway: MessageGateway) { this.gateway = gateway; } abstract send(recipient: string): Promise<string>;} // REFINED ABSTRACTIONSclass WelcomeMessage extends Message { constructor(gateway: MessageGateway, private userName: string) { super(gateway); } async send(recipient: string): Promise<string> { const html = `<h1>Welcome, ${this.userName}!</h1><p>Thanks for joining.</p>`; return this.gateway.sendRichContent(recipient, html); }} class AlertMessage extends Message { constructor(gateway: MessageGateway, private alertText: string) { super(gateway); } async send(recipient: string): Promise<string> { return this.gateway.sendPlainText(recipient, `ALERT: ${this.alertText}`); }} // Usage: Any message × Any gatewayconst awsGateway = new AWSMessagingGateway();const twilioGateway = new TwilioGateway(); const welcomeEmail = new WelcomeMessage(awsGateway, "Alice");const alertSMS = new AlertMessage(twilioGateway, "Security breach detected"); await welcomeEmail.send("alice@example.com");await alertSMS.send("+15551234567");ORMs and data access layers use Bridge to separate domain models from database implementations. This allows the same repository logic to work with MySQL, PostgreSQL, or MongoDB.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
// IMPLEMENTOR: Database operations interfaceinterface DatabaseDriver { query<T>(sql: string, params: any[]): Promise<T[]>; execute(sql: string, params: any[]): Promise<{ affectedRows: number }>; beginTransaction(): Promise<void>; commit(): Promise<void>; rollback(): Promise<void>;} // CONCRETE IMPLEMENTORSclass PostgresDriver implements DatabaseDriver { async query<T>(sql: string, params: any[]): Promise<T[]> { console.log(`[Postgres] ${sql}`); return []; } async execute(sql: string, params: any[]): Promise<{ affectedRows: number }> { console.log(`[Postgres] ${sql}`); return { affectedRows: 1 }; } async beginTransaction(): Promise<void> { console.log("[Postgres] BEGIN"); } async commit(): Promise<void> { console.log("[Postgres] COMMIT"); } async rollback(): Promise<void> { console.log("[Postgres] ROLLBACK"); }} class MySQLDriver implements DatabaseDriver { async query<T>(sql: string, params: any[]): Promise<T[]> { console.log(`[MySQL] ${sql}`); return []; } async execute(sql: string, params: any[]): Promise<{ affectedRows: number }> { console.log(`[MySQL] ${sql}`); return { affectedRows: 1 }; } async beginTransaction(): Promise<void> { console.log("[MySQL] START TRANSACTION"); } async commit(): Promise<void> { console.log("[MySQL] COMMIT"); } async rollback(): Promise<void> { console.log("[MySQL] ROLLBACK"); }} // ABSTRACTION: Repository baseabstract class Repository<T> { protected db: DatabaseDriver; protected tableName: string; constructor(db: DatabaseDriver, tableName: string) { this.db = db; this.tableName = tableName; } async findById(id: string): Promise<T | null> { const results = await this.db.query<T>( `SELECT * FROM ${this.tableName} WHERE id = $1`, [id] ); return results[0] || null; } async findAll(): Promise<T[]> { return this.db.query<T>(`SELECT * FROM ${this.tableName}`, []); }} // REFINED ABSTRACTIONS: Domain-specific repositoriesinterface User { id: string; email: string; name: string; }interface Order { id: string; userId: string; total: number; } class UserRepository extends Repository<User> { constructor(db: DatabaseDriver) { super(db, 'users'); } async findByEmail(email: string): Promise<User | null> { const results = await this.db.query<User>( 'SELECT * FROM users WHERE email = $1', [email] ); return results[0] || null; }} class OrderRepository extends Repository<Order> { constructor(db: DatabaseDriver) { super(db, 'orders'); } async findByUser(userId: string): Promise<Order[]> { return this.db.query<Order>( 'SELECT * FROM orders WHERE user_id = $1', [userId] ); }} // Usage: Any repository × Any databaseconst postgres = new PostgresDriver();const mysql = new MySQLDriver(); const usersOnPg = new UserRepository(postgres);const ordersOnMySQL = new OrderRepository(mysql); await usersOnPg.findByEmail("test@example.com");await ordersOnMySQL.findByUser("user-123");Recognizing Bridge opportunities requires understanding problem characteristics that signal the pattern's applicability.
You have mastered the Bridge Pattern! You understand the problem it solves (abstraction-implementation coupling), its elegant solution (separate hierarchies with composition bridge), how it differs from Adapter, and when to apply it in real-world systems. This pattern is a cornerstone of flexible, maintainable software architecture.