From harness-claude
Implements Template Method pattern: define algorithm skeleton in base class with abstract steps overridden by subclasses. Use for data importers, parsers, report generators to avoid duplication.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Define an algorithm skeleton in a base class with abstract steps filled by subclasses.
Implements Template Method pattern in JavaScript: define algorithm skeleton in base class, let subclasses override specific steps. Use for shared structures with varying 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.
Implements Template Method and Strategy patterns in Laravel to stabilize core workflows, enabling extension via new classes instead of editing logic or switches.
Share bugs, ideas, or general feedback.
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: 'alice@example.com' }];
}
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