Builder позволяет пошагово конструировать сложные объекты. TypeScript делает его особенно полезным — каждый метод возвращает this с правильным типом:
class QueryBuilder<T> {
private table = ''
private conditions: string[] = []
private selectedFields: string[] = ['*']
private limitValue?: number
from(table: string): this {
this.table = table
return this
}
select(...fields: string[]): this {
this.selectedFields = fields
return this
}
where(condition: string): this {
this.conditions.push(condition)
return this
}
limit(n: number): this {
this.limitValue = n
return this
}
build(): string {
let query = `SELECT ${this.selectedFields.join(', ')} FROM ${this.table}`
if (this.conditions.length) query += ` WHERE ${this.conditions.join(' AND ')}`
if (this.limitValue) query += ` LIMIT ${this.limitValue}`
return query
}
}
const query = new QueryBuilder<User>()
.from('users')
.select('id', 'name', 'email')
.where('age > 18')
.where('active = true')
.limit(10)
.build()Factory централизует создание объектов, скрывая детали реализации:
interface Logger {
log(message: string): void
error(message: string): void
}
class ConsoleLogger implements Logger {
log(message: string) { console.log(`[INFO] ${message}`) }
error(message: string) { console.error(`[ERROR] ${message}`) }
}
class FileLogger implements Logger {
constructor(private filename: string) {}
log(message: string) { /* запись в файл */ }
error(message: string) { /* запись в файл */ }
}
class NullLogger implements Logger {
log() {}
error() {}
}
type LoggerType = 'console' | 'file' | 'null'
function createLogger(type: LoggerType, options?: { filename?: string }): Logger {
switch (type) {
case 'console': return new ConsoleLogger()
case 'file': return new FileLogger(options?.filename ?? 'app.log')
case 'null': return new NullLogger()
}
}type EventMap = Record<string, unknown>
class EventEmitter<Events extends EventMap> {
private listeners = new Map<keyof Events, Set<Function>>()
on<K extends keyof Events>(event: K, listener: (data: Events[K]) => void): this {
if (!this.listeners.has(event)) this.listeners.set(event, new Set())
this.listeners.get(event)!.add(listener)
return this
}
off<K extends keyof Events>(event: K, listener: (data: Events[K]) => void): this {
this.listeners.get(event)?.delete(listener)
return this
}
emit<K extends keyof Events>(event: K, data: Events[K]): this {
this.listeners.get(event)?.forEach(listener => listener(data))
return this
}
}
// Строго типизированные события:
interface StoreEvents {
change: { key: string; value: unknown }
reset: void
}
const emitter = new EventEmitter<StoreEvents>()
emitter.on('change', ({ key, value }) => console.log(key, value))Repository абстрагирует доступ к данным:
interface Repository<T, ID = number> {
findById(id: ID): Promise<T | null>
findAll(): Promise<T[]>
findWhere(predicate: (item: T) => boolean): Promise<T[]>
save(item: T): Promise<T>
delete(id: ID): Promise<void>
}
class InMemoryUserRepository implements Repository<User> {
private users = new Map<number, User>()
async findById(id: number): Promise<User | null> {
return this.users.get(id) ?? null
}
async findAll(): Promise<User[]> {
return [...this.users.values()]
}
async findWhere(predicate: (user: User) => boolean): Promise<User[]> {
return [...this.users.values()].filter(predicate)
}
async save(user: User): Promise<User> {
this.users.set(user.id, user)
return user
}
async delete(id: number): Promise<void> {
this.users.delete(id)
}
}class Database {
private static instance: Database | null = null
private constructor(private url: string) {}
static getInstance(url: string): Database {
if (!Database.instance) {
Database.instance = new Database(url)
}
return Database.instance
}
}Реализация Builder, Factory, Observer и Repository паттернов в JS с демонстрацией практических use cases
// Четыре ключевых паттерна с TypeScript-стилем в чистом JS.
// ============================================================
// 1. BUILDER — QueryBuilder для SQL-подобных запросов
// ============================================================
class QueryBuilder {
constructor() {
this._table = ''
this._fields = ['*']
this._conditions = []
this._orderBy = null
this._limitValue = null
this._joins = []
}
from(table) { this._table = table; return this }
select(...fields) { this._fields = fields; return this }
where(condition) { this._conditions.push(condition); return this }
join(table, on) { this._joins.push(`JOIN ${table} ON ${on}`); return this }
orderBy(field, dir = 'ASC') { this._orderBy = `${field} ${dir}`; return this }
limit(n) { this._limitValue = n; return this }
build() {
if (!this._table) throw new Error('Table is required')
let q = `SELECT ${this._fields.join(', ')} FROM ${this._table}`
if (this._joins.length) q += ' ' + this._joins.join(' ')
if (this._conditions.length) q += ` WHERE ${this._conditions.join(' AND ')}`
if (this._orderBy) q += ` ORDER BY ${this._orderBy}`
if (this._limitValue !== null) q += ` LIMIT ${this._limitValue}`
return q
}
}
console.log('=== Builder Pattern ===')
const query = new QueryBuilder()
.from('users')
.select('users.id', 'users.name', 'orders.total')
.join('orders', 'users.id = orders.user_id')
.where('users.active = true')
.where('orders.total > 1000')
.orderBy('orders.total', 'DESC')
.limit(20)
.build()
console.log(query)
// ============================================================
// 2. FACTORY — Logger с разными стратегиями
// ============================================================
class ConsoleLogger {
constructor(prefix = '') { this.prefix = prefix }
log(msg) { console.log(` [${this.prefix || 'INFO'}] ${msg}`) }
error(msg) { console.log(` [${this.prefix || 'ERROR'}] ${msg}`) }
warn(msg) { console.log(` [${this.prefix || 'WARN'}] ${msg}`) }
}
class BufferedLogger {
constructor() { this.buffer = [] }
log(msg) { this.buffer.push({ level: 'info', msg, time: Date.now() }) }
error(msg) { this.buffer.push({ level: 'error', msg, time: Date.now() }) }
warn(msg) { this.buffer.push({ level: 'warn', msg, time: Date.now() }) }
flush() {
const logs = [...this.buffer]
this.buffer = []
return logs
}
}
class NullLogger {
log() {} error() {} warn() {}
}
function createLogger(type, options = {}) {
switch (type) {
case 'console': return new ConsoleLogger(options.prefix)
case 'buffered': return new BufferedLogger()
case 'null': return new NullLogger()
default: throw new Error('Unknown logger type: ' + type)
}
}
console.log('\n=== Factory Pattern ===')
const logger = createLogger('console', { prefix: 'APP' })
logger.log('Приложение запущено')
logger.warn('Медленный запрос')
const buffered = createLogger('buffered')
buffered.log('Event 1')
buffered.error('Something failed')
buffered.log('Event 2')
console.log(' Buffered logs:', buffered.flush())
// ============================================================
// 3. OBSERVER — типизированный EventEmitter
// ============================================================
class TypedEventEmitter {
constructor() { this._listeners = new Map() }
on(event, listener) {
if (!this._listeners.has(event)) this._listeners.set(event, new Set())
this._listeners.get(event).add(listener)
return this
}
off(event, listener) {
this._listeners.get(event)?.delete(listener)
return this
}
once(event, listener) {
const wrapper = (data) => { listener(data); this.off(event, wrapper) }
return this.on(event, wrapper)
}
emit(event, data) {
this._listeners.get(event)?.forEach(fn => fn(data))
return this
}
}
console.log('\n=== Observer Pattern ===')
const store = new TypedEventEmitter()
store.on('change', ({ key, value }) =>
console.log(` [change] ${key} = ${JSON.stringify(value)}`)
)
store.once('reset', () => console.log(' [reset] store was reset'))
store.emit('change', { key: 'user', value: { name: 'Алексей' } })
store.emit('change', { key: 'theme', value: 'dark' })
store.emit('reset')
store.emit('reset') // не сработает — once уже отработал
// ============================================================
// 4. REPOSITORY — InMemory с типизированным поиском
// ============================================================
class InMemoryRepository {
constructor() { this._store = new Map(); this._nextId = 1 }
async save(item) {
const saved = { ...item, id: item.id ?? this._nextId++ }
this._store.set(saved.id, saved)
return saved
}
async findById(id) {
return this._store.get(id) ?? null
}
async findAll() {
return [...this._store.values()]
}
async findWhere(predicate) {
return [...this._store.values()].filter(predicate)
}
async delete(id) {
this._store.delete(id)
}
async count() { return this._store.size }
}
console.log('\n=== Repository Pattern ===')
const userRepo = new InMemoryRepository()
async function demo() {
await userRepo.save({ name: 'Алексей', role: 'admin', age: 30 })
await userRepo.save({ name: 'Мария', role: 'user', age: 25 })
await userRepo.save({ name: 'Иван', role: 'user', age: 17 })
const all = await userRepo.findAll()
console.log(' All users:', all.map(u => u.name))
const admins = await userRepo.findWhere(u => u.role === 'admin')
console.log(' Admins:', admins.map(u => u.name))
const adults = await userRepo.findWhere(u => u.age >= 18)
console.log(' Adults:', adults.map(u => u.name))
const user = await userRepo.findById(2)
console.log(' User #2:', user?.name)
await userRepo.delete(2)
console.log(' After delete count:', await userRepo.count())
}
demo()Builder позволяет пошагово конструировать сложные объекты. TypeScript делает его особенно полезным — каждый метод возвращает this с правильным типом:
class QueryBuilder<T> {
private table = ''
private conditions: string[] = []
private selectedFields: string[] = ['*']
private limitValue?: number
from(table: string): this {
this.table = table
return this
}
select(...fields: string[]): this {
this.selectedFields = fields
return this
}
where(condition: string): this {
this.conditions.push(condition)
return this
}
limit(n: number): this {
this.limitValue = n
return this
}
build(): string {
let query = `SELECT ${this.selectedFields.join(', ')} FROM ${this.table}`
if (this.conditions.length) query += ` WHERE ${this.conditions.join(' AND ')}`
if (this.limitValue) query += ` LIMIT ${this.limitValue}`
return query
}
}
const query = new QueryBuilder<User>()
.from('users')
.select('id', 'name', 'email')
.where('age > 18')
.where('active = true')
.limit(10)
.build()Factory централизует создание объектов, скрывая детали реализации:
interface Logger {
log(message: string): void
error(message: string): void
}
class ConsoleLogger implements Logger {
log(message: string) { console.log(`[INFO] ${message}`) }
error(message: string) { console.error(`[ERROR] ${message}`) }
}
class FileLogger implements Logger {
constructor(private filename: string) {}
log(message: string) { /* запись в файл */ }
error(message: string) { /* запись в файл */ }
}
class NullLogger implements Logger {
log() {}
error() {}
}
type LoggerType = 'console' | 'file' | 'null'
function createLogger(type: LoggerType, options?: { filename?: string }): Logger {
switch (type) {
case 'console': return new ConsoleLogger()
case 'file': return new FileLogger(options?.filename ?? 'app.log')
case 'null': return new NullLogger()
}
}type EventMap = Record<string, unknown>
class EventEmitter<Events extends EventMap> {
private listeners = new Map<keyof Events, Set<Function>>()
on<K extends keyof Events>(event: K, listener: (data: Events[K]) => void): this {
if (!this.listeners.has(event)) this.listeners.set(event, new Set())
this.listeners.get(event)!.add(listener)
return this
}
off<K extends keyof Events>(event: K, listener: (data: Events[K]) => void): this {
this.listeners.get(event)?.delete(listener)
return this
}
emit<K extends keyof Events>(event: K, data: Events[K]): this {
this.listeners.get(event)?.forEach(listener => listener(data))
return this
}
}
// Строго типизированные события:
interface StoreEvents {
change: { key: string; value: unknown }
reset: void
}
const emitter = new EventEmitter<StoreEvents>()
emitter.on('change', ({ key, value }) => console.log(key, value))Repository абстрагирует доступ к данным:
interface Repository<T, ID = number> {
findById(id: ID): Promise<T | null>
findAll(): Promise<T[]>
findWhere(predicate: (item: T) => boolean): Promise<T[]>
save(item: T): Promise<T>
delete(id: ID): Promise<void>
}
class InMemoryUserRepository implements Repository<User> {
private users = new Map<number, User>()
async findById(id: number): Promise<User | null> {
return this.users.get(id) ?? null
}
async findAll(): Promise<User[]> {
return [...this.users.values()]
}
async findWhere(predicate: (user: User) => boolean): Promise<User[]> {
return [...this.users.values()].filter(predicate)
}
async save(user: User): Promise<User> {
this.users.set(user.id, user)
return user
}
async delete(id: number): Promise<void> {
this.users.delete(id)
}
}class Database {
private static instance: Database | null = null
private constructor(private url: string) {}
static getInstance(url: string): Database {
if (!Database.instance) {
Database.instance = new Database(url)
}
return Database.instance
}
}Реализация Builder, Factory, Observer и Repository паттернов в JS с демонстрацией практических use cases
// Четыре ключевых паттерна с TypeScript-стилем в чистом JS.
// ============================================================
// 1. BUILDER — QueryBuilder для SQL-подобных запросов
// ============================================================
class QueryBuilder {
constructor() {
this._table = ''
this._fields = ['*']
this._conditions = []
this._orderBy = null
this._limitValue = null
this._joins = []
}
from(table) { this._table = table; return this }
select(...fields) { this._fields = fields; return this }
where(condition) { this._conditions.push(condition); return this }
join(table, on) { this._joins.push(`JOIN ${table} ON ${on}`); return this }
orderBy(field, dir = 'ASC') { this._orderBy = `${field} ${dir}`; return this }
limit(n) { this._limitValue = n; return this }
build() {
if (!this._table) throw new Error('Table is required')
let q = `SELECT ${this._fields.join(', ')} FROM ${this._table}`
if (this._joins.length) q += ' ' + this._joins.join(' ')
if (this._conditions.length) q += ` WHERE ${this._conditions.join(' AND ')}`
if (this._orderBy) q += ` ORDER BY ${this._orderBy}`
if (this._limitValue !== null) q += ` LIMIT ${this._limitValue}`
return q
}
}
console.log('=== Builder Pattern ===')
const query = new QueryBuilder()
.from('users')
.select('users.id', 'users.name', 'orders.total')
.join('orders', 'users.id = orders.user_id')
.where('users.active = true')
.where('orders.total > 1000')
.orderBy('orders.total', 'DESC')
.limit(20)
.build()
console.log(query)
// ============================================================
// 2. FACTORY — Logger с разными стратегиями
// ============================================================
class ConsoleLogger {
constructor(prefix = '') { this.prefix = prefix }
log(msg) { console.log(` [${this.prefix || 'INFO'}] ${msg}`) }
error(msg) { console.log(` [${this.prefix || 'ERROR'}] ${msg}`) }
warn(msg) { console.log(` [${this.prefix || 'WARN'}] ${msg}`) }
}
class BufferedLogger {
constructor() { this.buffer = [] }
log(msg) { this.buffer.push({ level: 'info', msg, time: Date.now() }) }
error(msg) { this.buffer.push({ level: 'error', msg, time: Date.now() }) }
warn(msg) { this.buffer.push({ level: 'warn', msg, time: Date.now() }) }
flush() {
const logs = [...this.buffer]
this.buffer = []
return logs
}
}
class NullLogger {
log() {} error() {} warn() {}
}
function createLogger(type, options = {}) {
switch (type) {
case 'console': return new ConsoleLogger(options.prefix)
case 'buffered': return new BufferedLogger()
case 'null': return new NullLogger()
default: throw new Error('Unknown logger type: ' + type)
}
}
console.log('\n=== Factory Pattern ===')
const logger = createLogger('console', { prefix: 'APP' })
logger.log('Приложение запущено')
logger.warn('Медленный запрос')
const buffered = createLogger('buffered')
buffered.log('Event 1')
buffered.error('Something failed')
buffered.log('Event 2')
console.log(' Buffered logs:', buffered.flush())
// ============================================================
// 3. OBSERVER — типизированный EventEmitter
// ============================================================
class TypedEventEmitter {
constructor() { this._listeners = new Map() }
on(event, listener) {
if (!this._listeners.has(event)) this._listeners.set(event, new Set())
this._listeners.get(event).add(listener)
return this
}
off(event, listener) {
this._listeners.get(event)?.delete(listener)
return this
}
once(event, listener) {
const wrapper = (data) => { listener(data); this.off(event, wrapper) }
return this.on(event, wrapper)
}
emit(event, data) {
this._listeners.get(event)?.forEach(fn => fn(data))
return this
}
}
console.log('\n=== Observer Pattern ===')
const store = new TypedEventEmitter()
store.on('change', ({ key, value }) =>
console.log(` [change] ${key} = ${JSON.stringify(value)}`)
)
store.once('reset', () => console.log(' [reset] store was reset'))
store.emit('change', { key: 'user', value: { name: 'Алексей' } })
store.emit('change', { key: 'theme', value: 'dark' })
store.emit('reset')
store.emit('reset') // не сработает — once уже отработал
// ============================================================
// 4. REPOSITORY — InMemory с типизированным поиском
// ============================================================
class InMemoryRepository {
constructor() { this._store = new Map(); this._nextId = 1 }
async save(item) {
const saved = { ...item, id: item.id ?? this._nextId++ }
this._store.set(saved.id, saved)
return saved
}
async findById(id) {
return this._store.get(id) ?? null
}
async findAll() {
return [...this._store.values()]
}
async findWhere(predicate) {
return [...this._store.values()].filter(predicate)
}
async delete(id) {
this._store.delete(id)
}
async count() { return this._store.size }
}
console.log('\n=== Repository Pattern ===')
const userRepo = new InMemoryRepository()
async function demo() {
await userRepo.save({ name: 'Алексей', role: 'admin', age: 30 })
await userRepo.save({ name: 'Мария', role: 'user', age: 25 })
await userRepo.save({ name: 'Иван', role: 'user', age: 17 })
const all = await userRepo.findAll()
console.log(' All users:', all.map(u => u.name))
const admins = await userRepo.findWhere(u => u.role === 'admin')
console.log(' Admins:', admins.map(u => u.name))
const adults = await userRepo.findWhere(u => u.age >= 18)
console.log(' Adults:', adults.map(u => u.name))
const user = await userRepo.findById(2)
console.log(' User #2:', user?.name)
await userRepo.delete(2)
console.log(' After delete count:', await userRepo.count())
}
demo()Реализуй паттерн "Цепочка ответственности" (Chain of Responsibility). Создай `createChain(...handlers)`, где каждый `handler` — функция `(request, next) => result`. Если handler вызывает `next(request)` — запрос передаётся следующему. Если не вызывает — обработка останавливается. Примени паттерн к системе валидации: цепочка из проверок `notEmpty`, `minLength(5)`, `isEmail`.
Функция execute(index, req) рекурсивно вызывает handlers[index](req, nextValue => execute(index+1, nextValue)). Если handler не вызывает next — он просто возвращает { ok: false, error: ... } и рекурсия не продолжается. Если index >= handlers.length — вернуть { ok: true, value: req }.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке