From harness-claude
Implements the Template Method design pattern to define algorithm skeletons in base classes with abstract steps filled by subclasses. Useful for parsers, importers, and test frameworks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:gof-template-methodThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Define an algorithm skeleton in a base class with abstract steps filled by subclasses.
Define an algorithm skeleton in a base class with abstract steps filled by subclasses.
Classic abstract base class with template method:
abstract class DataImporter {
// Template method — defines the algorithm skeleton
async import(source: string): Promise<ImportResult> {
await this.connect(source);
try {
const raw = await this.extract();
const validated = this.validate(raw);
const transformed = this.transform(validated);
const count = await this.load(transformed);
await this.notify(count);
return { success: true, count };
} catch (err) {
await this.onError(err as Error);
return { success: false, count: 0, error: (err as Error).message };
} finally {
await this.disconnect();
}
}
// Abstract steps — subclasses must implement
protected abstract connect(source: string): Promise<void>;
protected abstract extract(): Promise<unknown[]>;
protected abstract transform(records: unknown[]): Record<string, unknown>[];
// Hooks — subclasses can override (have defaults)
protected validate(records: unknown[]): unknown[] {
return records;
}
protected async load(records: Record<string, unknown>[]): Promise<number> {
// Default load implementation
console.log(`Loading ${records.length} records`);
return records.length;
}
protected async notify(count: number): Promise<void> {
console.log(`Import complete: ${count} records`);
}
protected async onError(err: Error): Promise<void> {
console.error('Import failed:', err.message);
}
protected async disconnect(): Promise<void> {} // no-op by default
}
// Concrete implementation — CSV importer
class CSVImporter extends DataImporter {
private connection: string = '';
protected async connect(source: string): Promise<void> {
this.connection = source;
console.log(`Opening CSV file: ${source}`);
}
protected async extract(): Promise<unknown[]> {
// Parse CSV rows
return [{ name: 'Alice', email: '[email protected]' }];
}
protected override validate(records: unknown[]): unknown[] {
return records.filter((r) => (r as Record<string, string>).email?.includes('@'));
}
protected transform(records: unknown[]): Record<string, unknown>[] {
return records.map((r) => ({
...(r as Record<string, unknown>),
importedAt: new Date(),
source: 'csv',
}));
}
}
// Another concrete implementation — JSON API importer
class APIImporter extends DataImporter {
private baseUrl: string = '';
protected async connect(source: string): Promise<void> {
this.baseUrl = source;
}
protected async extract(): Promise<unknown[]> {
const response = await fetch(`${this.baseUrl}/data`);
return response.json();
}
protected transform(records: unknown[]): Record<string, unknown>[] {
return records.map((r) => ({
...(r as Record<string, unknown>),
importedAt: new Date(),
source: 'api',
}));
}
protected override async notify(count: number): Promise<void> {
await fetch(`${this.baseUrl}/ack`, { method: 'POST', body: JSON.stringify({ count }) });
}
}
// Usage — call the template method, not the individual steps
const csvImporter = new CSVImporter();
await csvImporter.import('/data/users.csv');
Hook methods for optional extension points:
abstract class ReportGenerator {
generate(data: unknown[]): string {
const header = this.renderHeader();
const body = this.renderBody(data);
const footer = this.renderFooter();
// Hook — subclasses can add custom sections
const custom = this.renderCustomSection();
return [header, body, custom, footer].filter(Boolean).join('\n');
}
protected abstract renderHeader(): string;
protected abstract renderBody(data: unknown[]): string;
protected renderFooter(): string {
return '--- End of Report ---';
}
protected renderCustomSection(): string {
return '';
} // hook — optional override
}
Template Method vs. Strategy: Template Method uses inheritance — the base class controls the algorithm; subclasses override steps. Strategy uses composition — the algorithm is injected from outside. Prefer Strategy when: you want to swap algorithms at runtime, or you want to avoid deep inheritance chains. Use Template Method when: the overall structure rarely changes but specific steps vary by subclass.
Hooks vs. abstract methods: Abstract methods must be overridden — they enforce required customization. Hooks are optional — they provide extension points with sensible defaults. Use abstract for mandatory steps, hooks for optional customization.
Anti-patterns:
final (or just document it clearly)TypeScript enforcement: TypeScript doesn't have a final keyword for methods. Document non-overridable template methods with a comment or use the protected pattern:
// Convention: prefix with _ to signal "do not override"
// Or use a wrapper:
class Base {
process(): void {
// callers use this
this._doProcess(); // not intended to be overridden
}
protected _doProcess(): void {
/* ... */
}
}
refactoring.guru/design-patterns/template-method
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeDefines the skeleton of an algorithm in a base class and lets subclasses override specific steps. Useful when multiple classes share the same algorithmic structure but differ in certain steps.
Defines an algorithm skeleton in a base class with abstract steps for subclasses to override. Use when multiple classes share the same structure but differ in specific steps.
Generates Template Method pattern for PHP 8.4: abstract algorithm skeleton with customizable steps, concrete implementations, hooks, optional value objects, and unit tests. For reusable algorithms with variants like data processing.