From harness-claude
Implements Bridge design pattern to decouple abstractions from implementations, avoiding class explosions in orthogonal hierarchies like shapes/renderers or notifications/channels. TypeScript examples provided.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Separate abstraction from implementation to allow them to vary independently.
Generates Bridge pattern for PHP 8.4 projects, creating abstraction, refined abstraction, implementor interface, concrete implementors, and unit tests in Domain/Infrastructure structure. For decoupling with multiple variation dimensions or runtime switching.
Implements JavaScript Bridge pattern to decouple abstractions from implementations, enabling independent evolution and runtime swapping. Use when orthogonal variations risk combinatorial class explosion.
Guides choosing inheritance vs composition in OOP for class hierarchies and object composition. Includes Java examples like Vehicle/Car.
Share bugs, ideas, or general feedback.
Separate abstraction from implementation to allow them to vary independently.
Classic shape + renderer bridge:
// Implementation interface — can vary independently
interface Renderer {
renderCircle(radius: number): string;
renderRectangle(width: number, height: number): string;
}
// Concrete implementations
class SVGRenderer implements Renderer {
renderCircle(radius: number): string {
return `<circle r="${radius}" />`;
}
renderRectangle(width: number, height: number): string {
return `<rect width="${width}" height="${height}" />`;
}
}
class CanvasRenderer implements Renderer {
renderCircle(radius: number): string {
return `ctx.arc(0, 0, ${radius}, 0, Math.PI * 2)`;
}
renderRectangle(width: number, height: number): string {
return `ctx.fillRect(0, 0, ${width}, ${height})`;
}
}
// Abstraction — holds a reference to the implementation
abstract class Shape {
constructor(protected renderer: Renderer) {}
abstract draw(): string;
abstract resize(factor: number): void;
}
// Refined abstractions
class Circle extends Shape {
constructor(
renderer: Renderer,
private radius: number
) {
super(renderer);
}
draw(): string {
return this.renderer.renderCircle(this.radius);
}
resize(factor: number): void {
this.radius *= factor;
}
}
class Rectangle extends Shape {
constructor(
renderer: Renderer,
private width: number,
private height: number
) {
super(renderer);
}
draw(): string {
return this.renderer.renderRectangle(this.width, this.height);
}
resize(factor: number): void {
this.width *= factor;
this.height *= factor;
}
}
// Four combinations without four classes
const svgCircle = new Circle(new SVGRenderer(), 10);
const canvasCircle = new Circle(new CanvasRenderer(), 10);
const svgRect = new Rectangle(new SVGRenderer(), 20, 10);
Notification + channel bridge (practical backend example):
// Implementation axis: delivery channel
interface MessageChannel {
send(recipient: string, subject: string, body: string): Promise<void>;
}
class EmailChannel implements MessageChannel {
async send(recipient: string, subject: string, body: string): Promise<void> {
console.log(`Email → ${recipient}: [${subject}] ${body}`);
}
}
class SlackChannel implements MessageChannel {
async send(recipient: string, subject: string, body: string): Promise<void> {
console.log(`Slack → #${recipient}: ${body}`);
}
}
// Abstraction axis: notification type
abstract class Notification {
constructor(protected channel: MessageChannel) {}
abstract notify(userId: string, data: Record<string, unknown>): Promise<void>;
// Swap channel at runtime
setChannel(channel: MessageChannel): void {
this.channel = channel;
}
}
class OrderShippedNotification extends Notification {
async notify(userId: string, data: Record<string, unknown>): Promise<void> {
await this.channel.send(
userId,
'Your order has shipped',
`Order #${data.orderId} is on its way. Tracking: ${data.tracking}`
);
}
}
class PaymentFailedNotification extends Notification {
async notify(userId: string, data: Record<string, unknown>): Promise<void> {
await this.channel.send(
userId,
'Payment failed',
`Your payment of $${data.amount} failed. Please update your payment method.`
);
}
}
Why Bridge prevents class explosion: Without Bridge, combining 3 shapes × 3 renderers requires 9 classes. With Bridge, 3 shape classes + 3 renderer classes = 6 classes total. The number of combinations grows multiplicatively without the pattern.
Bridge vs. Adapter: Adapter works with existing classes to make them compatible. Bridge is designed upfront to allow variation. If you find yourself introducing Bridge after the fact, you might actually want Adapter.
Bridge vs. Strategy: Bridge separates a class hierarchy along two dimensions. Strategy encapsulates a single algorithm. Bridge is architectural; Strategy is behavioral. A bridge can contain multiple strategy-like injections.
Anti-patterns:
Runtime switching:
let channel: MessageChannel = new EmailChannel();
const notification = new OrderShippedNotification(channel);
// Switch to Slack at runtime
notification.setChannel(new SlackChannel());
await notification.notify('ops-team', { orderId: '123', tracking: 'UPS1234' });
refactoring.guru/design-patterns/bridge