+TypeScript example
ExportJob owns when to create and use a writer. CsvJob and JsonJob only choose which writer to construct, so another format does not need another copy of run().
interface IWriter { write(value: string): string; }
class CsvWriter implements IWriter {
write(value: string): string { return '"' + value.replaceAll('"', '""') + '"'; }
}
class JsonWriter implements IWriter {
write(value: string): string { return JSON.stringify({ value }); }
}
// The shared workflow uses the contract returned by the creation method.
abstract class ExportJob {
protected abstract create(): IWriter;
run(): string { return this.create().write("hello"); }
}
// Change only product selection and inherit the execution sequence.
class CsvJob extends ExportJob {
protected override create(): IWriter { return new CsvWriter(); }
}
class JsonJob extends ExportJob {
protected override create(): IWriter { return new JsonWriter(); }
}
for (const job of [new CsvJob(), new JsonJob()]) console.log(job.run());
"hello"
{"value":"hello"}
Create and Write become create and write in this version. These writers return CSV and JSON strings rather than writing files.