Créationnels
Builder
Construire des objets complexes étape par étape
Problème
Quand un objet a de nombreux paramètres optionnels ou doit être construit en plusieurs étapes, le constructeur devient illisible.
// Difficile à lire, facile à se tromper dans l'ordre
const query = new QueryBuilder(
'users',
['id', 'name', 'email'],
{ role: 'admin' },
'name',
'ASC',
0,
20,
true
);Builder classique
interface Query {
table: string;
fields: string[];
conditions: Record<string, unknown>;
orderBy?: string;
orderDir?: 'ASC' | 'DESC';
offset: number;
limit: number;
withDeleted: boolean;
}
class QueryBuilder {
private query: Partial<Query> = {
fields: [],
conditions: {},
offset: 0,
limit: 20,
withDeleted: false,
};
from(table: string): this {
this.query.table = table;
return this;
}
select(...fields: string[]): this {
this.query.fields = fields;
return this;
}
where(conditions: Record<string, unknown>): this {
this.query.conditions = { ...this.query.conditions, ...conditions };
return this;
}
orderBy(field: string, dir: 'ASC' | 'DESC' = 'ASC'): this {
this.query.orderBy = field;
this.query.orderDir = dir;
return this;
}
paginate(offset: number, limit: number): this {
this.query.offset = offset;
this.query.limit = limit;
return this;
}
includeSoftDeleted(): this {
this.query.withDeleted = true;
return this;
}
build(): Query {
if (!this.query.table) throw new Error('Table is required');
return this.query as Query;
}
}
// Lisible, ordre flexible, paramètres explicites
const query = new QueryBuilder()
.from('users')
.select('id', 'name', 'email')
.where({ role: 'admin' })
.orderBy('name')
.paginate(0, 20)
.build();Builder fonctionnel
En TypeScript, le pattern Builder s'exprime souvent mieux avec des fonctions chaînées :
type RequestConfig = {
url: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
headers: Record<string, string>;
body?: unknown;
timeout: number;
};
function buildRequest(url: string) {
const config: RequestConfig = {
url,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
timeout: 5000,
};
return {
method(m: RequestConfig['method']) {
config.method = m;
return this;
},
header(key: string, value: string) {
config.headers[key] = value;
return this;
},
body(data: unknown) {
config.body = data;
return this;
},
timeout(ms: number) {
config.timeout = ms;
return this;
},
build(): RequestConfig {
return { ...config };
},
};
}
const req = buildRequest('/api/users')
.method('POST')
.header('Authorization', `Bearer ${token}`)
.body({ name: 'Alice', email: 'alice@example.com' })
.timeout(3000)
.build();En React : Builder de formulaire
type FieldConfig = {
name: string;
label: string;
type: 'text' | 'email' | 'password' | 'select';
required?: boolean;
options?: { value: string; label: string }[];
validation?: (value: string) => string | null;
};
class FormBuilder {
private fields: FieldConfig[] = [];
addTextField(name: string, label: string, required = false): this {
this.fields.push({ name, label, type: 'text', required });
return this;
}
addEmailField(name: string, label: string): this {
this.fields.push({
name, label, type: 'email', required: true,
validation: (v) => /\S+@\S+\.\S+/.test(v) ? null : 'Email invalide',
});
return this;
}
addSelectField(name: string, label: string, options: { value: string; label: string }[]): this {
this.fields.push({ name, label, type: 'select', options });
return this;
}
build(): FieldConfig[] {
return [...this.fields];
}
}
const loginFormFields = new FormBuilder()
.addEmailField('email', 'Adresse email')
.addTextField('password', 'Mot de passe', true)
.build();