git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/gof-adapter-pattern" ~/.claude/skills/intense-visions-harness-engineering-gof-adapter-pattern-4041a0 && rm -rf "$T"
agents/skills/codex/gof-adapter-pattern/SKILL.mdGOF Adapter Pattern
Wrap incompatible interfaces to make them work together without modifying source code.
When to Use
- You want to use an existing class but its interface doesn't match what you need
- You're integrating a third-party library with a fixed interface into a system that expects a different one
- You're wrapping a legacy component to work with new code
- You need to keep the original class unchanged (read-only dependency, vendor code)
Instructions
Object adapter (composition — preferred in TypeScript):
// What your system expects interface Logger { info(message: string, context?: object): void; error(message: string, error?: Error): void; warn(message: string, context?: object): void; } // What you have (third-party SDK, fixed interface) class PinoLogger { log(level: 'info' | 'warn' | 'error', payload: { msg: string; [key: string]: unknown }): void { console.log(JSON.stringify({ level, ...payload })); } } // Adapter wraps PinoLogger to satisfy Logger interface class PinoLoggerAdapter implements Logger { constructor(private readonly pino: PinoLogger) {} info(message: string, context?: object): void { this.pino.log('info', { msg: message, ...context }); } error(message: string, error?: Error): void { this.pino.log('error', { msg: message, err: error ? { message: error.message, stack: error.stack } : undefined, }); } warn(message: string, context?: object): void { this.pino.log('warn', { msg: message, ...context }); } } // Usage — client depends only on Logger interface const logger: Logger = new PinoLoggerAdapter(new PinoLogger()); logger.info('Server started', { port: 3000 });
Adapting an async legacy API:
// Legacy callback-based interface interface LegacyStorage { readFile(path: string, callback: (err: Error | null, data: Buffer) => void): void; writeFile(path: string, data: Buffer, callback: (err: Error | null) => void): void; } // Modern promise-based interface expected by new code interface AsyncStorage { read(path: string): Promise<Buffer>; write(path: string, data: Buffer): Promise<void>; } class LegacyStorageAdapter implements AsyncStorage { constructor(private readonly legacy: LegacyStorage) {} read(path: string): Promise<Buffer> { return new Promise((resolve, reject) => { this.legacy.readFile(path, (err, data) => { if (err) reject(err); else resolve(data); }); }); } write(path: string, data: Buffer): Promise<void> { return new Promise((resolve, reject) => { this.legacy.writeFile(path, data, (err) => { if (err) reject(err); else resolve(); }); }); } }
Two-way adapter (adapt in both directions):
// When you need to satisfy two incompatible interfaces simultaneously class BidirectionalAdapter implements InterfaceA, InterfaceB { constructor( private readonly a: ComponentA, private readonly b: ComponentB ) {} // InterfaceA methods delegate to ComponentA doSomethingA(): void { this.a.operationA(); } // InterfaceB methods delegate to ComponentB doSomethingB(): void { this.b.operationB(); } }
Details
Adapter vs. Facade: Both simplify usage, but serve different purposes. An Adapter makes one interface compatible with another — the client expects a specific interface. A Facade simplifies a complex subsystem — the client doesn't care about the interface, just wants it simpler. If you're translating interfaces, use Adapter. If you're hiding complexity, use Facade.
Adapter vs. Decorator: Both wrap an object. An Adapter changes the interface. A Decorator adds behavior while keeping the same interface.
Anti-patterns:
- Leaky adapter — letting the adaptee's types bleed into the adapter's public API
- Adapter that does too much business logic — adapters should translate, not transform
- Multiple adapters stacked — signals you need a proper abstraction layer, not more wrapping
Testing with adapters: Mock the target interface, not the adaptee. This is exactly the value of the pattern — tests use the interface, not the concrete third-party dependency.
// In tests, use a mock Logger instead of PinoLoggerAdapter const mockLogger: Logger = { info: jest.fn(), error: jest.fn(), warn: jest.fn(), };
Source
refactoring.guru/design-patterns/adapter
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.