CreationalTypeScriptverifiedVerified
Abstract Factory Pattern in TypeScript
Provides an interface for creating families of related objects without specifying their concrete classes.
How to Implement the Abstract Factory Pattern in TypeScript
1Step 1: Define the product interfaces
interface Button {
render(): string;
}
interface TextInput {
render(): string;
}2Step 2: Define the abstract factory interface
interface UIFactory {
createButton(): Button;
createTextInput(): TextInput;
}3Step 3: Implement concrete product families
class DarkButton implements Button {
render() { return "<button class='dark-btn'>Click</button>"; }
}
class DarkTextInput implements TextInput {
render() { return "<input class='dark-input' />"; }
}
class DarkThemeFactory implements UIFactory {
createButton() { return new DarkButton(); }
createTextInput() { return new DarkTextInput(); }
}
// Light Theme Family
class LightButton implements Button {
render() { return "<button class='light-btn'>Click</button>"; }
}
class LightTextInput implements TextInput {
render() { return "<input class='light-input' />"; }
}
class LightThemeFactory implements UIFactory {
createButton() { return new LightButton(); }
createTextInput() { return new LightTextInput(); }
}4Step 4: Build forms using any factory
function buildForm(factory: UIFactory): string {
const button = factory.createButton();
const input = factory.createTextInput();
return `Form: ${input.render()} ${button.render()}`;
}
console.log(buildForm(new DarkThemeFactory()));
console.log(buildForm(new LightThemeFactory()));// ── Cross-Database Abstract Factory ──────────────────────────────
interface ConnectionOptions {
host: string;
port: number;
database: string;
}
interface DbConnection {
readonly dialect: string;
connect(options: ConnectionOptions): Promise<void>;
disconnect(): Promise<void>;
isConnected(): boolean;
}
interface QueryResult<T = Record<string, unknown>> {
rows: T[];
rowCount: number;
}
interface QueryBuilder {
select(table: string, columns?: string[]): QueryBuilder;
where(condition: string, params?: unknown[]): QueryBuilder;
limit(count: number): QueryBuilder;
build(): { sql: string; params: unknown[] };
execute<T = Record<string, unknown>>(): Promise<QueryResult<T>>;
}
interface DatabaseFactory {
createConnection(): DbConnection;
createQueryBuilder(conn: DbConnection): QueryBuilder;
}
// PostgreSQL Family
class PgConnection implements DbConnection {
readonly dialect = "postgresql";
private connected = false;
async connect() { this.connected = true; }
async disconnect() { this.connected = false; }
isConnected() { return this.connected; }
}
class PgQueryBuilder implements QueryBuilder {
private parts = { table: "", columns: ["*"], conditions: [] as string[], params: [] as unknown[], limitCount: undefined as number | undefined };
constructor(private conn: DbConnection) {}
select(table: string, columns = ["*"]): this {
this.parts.table = table;
this.parts.columns = columns;
return this;
}
where(condition: string, params: unknown[] = []): this {
this.parts.conditions.push(condition);
this.parts.params.push(...params);
return this;
}
limit(count: number): this {
this.parts.limitCount = count;
return this;
}
build() {
let sql = `SELECT ${this.parts.columns.join(", ")} FROM ${this.parts.table}`;
if (this.parts.conditions.length) sql += ` WHERE ${this.parts.conditions.join(" AND ")}`;
if (this.parts.limitCount !== undefined) sql += ` LIMIT ${this.parts.limitCount}`;
return { sql, params: this.parts.params };
}
async execute<T = Record<string, unknown>>(): Promise<QueryResult<T>> {
if (!this.conn.isConnected()) throw new Error("Not connected");
return { rows: [] as T[], rowCount: 0 };
}
}
class PostgresFactory implements DatabaseFactory {
createConnection() { return new PgConnection(); }
createQueryBuilder(conn: DbConnection) { return new PgQueryBuilder(conn); }
}
// SQLite Family
class SqliteConnection implements DbConnection {
readonly dialect = "sqlite";
private connected = false;
async connect() { this.connected = true; }
async disconnect() { this.connected = false; }
isConnected() { return this.connected; }
}
class SqliteQueryBuilder implements QueryBuilder {
private parts = { table: "", columns: ["*"], conditions: [] as string[], params: [] as unknown[], limitCount: undefined as number | undefined };
constructor(private conn: DbConnection) {}
select(table: string, columns = ["*"]): this { this.parts.table = table; this.parts.columns = columns; return this; }
where(condition: string, params: unknown[] = []): this { this.parts.conditions.push(condition); this.parts.params.push(...params); return this; }
limit(count: number): this { this.parts.limitCount = count; return this; }
build() {
let sql = `SELECT ${this.parts.columns.join(", ")} FROM ${this.parts.table}`;
if (this.parts.conditions.length) sql += ` WHERE ${this.parts.conditions.join(" AND ")}`;
if (this.parts.limitCount !== undefined) sql += ` LIMIT ${this.parts.limitCount}`;
return { sql, params: this.parts.params };
}
async execute<T = Record<string, unknown>>(): Promise<QueryResult<T>> {
if (!this.conn.isConnected()) throw new Error("Not connected");
return { rows: [] as T[], rowCount: 0 };
}
}
class SqliteFactory implements DatabaseFactory {
createConnection() { return new SqliteConnection(); }
createQueryBuilder(conn: DbConnection) { return new SqliteQueryBuilder(conn); }
}
// Factory Registry
const factories: Record<string, () => DatabaseFactory> = {
postgresql: () => new PostgresFactory(),
sqlite: () => new SqliteFactory(),
};
function getDatabaseFactory(dialect: string): DatabaseFactory {
const create = factories[dialect];
if (!create) throw new Error(`Unsupported dialect "${dialect}"`);
return create();
}
export { getDatabaseFactory, PostgresFactory, SqliteFactory, type DatabaseFactory, type DbConnection, type QueryBuilder };Abstract Factory Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Abstract Factory Pattern in the Real World
“Imagine furnishing a room from IKEA versus a luxury boutique. Each store (factory) produces a complete set of furniture — chairs, tables, sofas — that share a consistent style. You pick the store, and every piece you get matches. You never mix a rustic IKEA chair with a baroque boutique table.”