TypeScript'te Creational Design Pattern'ler: Singleton, Factory, Builder, Prototype
Singleton, Factory, Builder ve Prototype pattern'lerinin TypeScript'te evrimi: ES modülleri singleton'ı ne zaman, factory function'lar class'ı ne zaman geçer.
TypeScript’te nesne yaratmak artık nadiren bir Gang of Four pattern’i gerektiriyor. Varsayılan, dilin kendi özelliği: tek bir paylaşılan instance için ES modülü, koşullu yaratım için factory function, kopyalama için object spread ya da structuredClone(). Bir modül export’unun zaten hallettiği işi class hiyerarşisiyle sarmalamak gereksiz soyutlama getirir, test edilebilirliği zedeler ve niyeti gizler.
Singleton, Factory, Builder ve Prototype hala yerini hak ediyor ama tetikleyici, ders kitaplarının anlattığından dar: birçok nesnenin tutarlı biçimde uygulaması gereken ortak konfigürasyon ya da adım adım doğrulanması gereken bir kurulum.
Class Olmadan Singleton#
Singleton pattern bir class’ın sadece bir instance’ının olmasını ve global erişimi garanti eder. 1994’te bu gerçek bir problemi çözüyordu. Bugün TypeScript’te çoğu durumda anti-pattern; birkaç dar istisnası var.
Klasik Problem#
Herkesin öğrendiği ders kitabı singleton’ı:
class DatabaseConnection {
private static instance: DatabaseConnection;
private constructor() {
// Private constructor direkt instantiation'ı önler
}
static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
}
query(sql: string): Promise<any> {
// Database operasyonları
}
}
// Kullanım
const db = DatabaseConnection.getInstance();
await db.query('SELECT * FROM users');
Bu çalışır ama testi zorlaştırır. Instance’ı kolayca mock’layamazsın, farklı konfigürasyonlar inject edemezsin ve her test global state’i paylaşır.
Doğal Singleton Olarak ES Modülleri#
ES modülleri ilk import’tan sonra cache’lenir. Aynı modülü birden fazla kez import etmek aynı instance’ı döndürür:
// db-connection.ts
class DatabaseConnection {
constructor(private config: DatabaseConfig) {
// Setup logic
}
query(sql: string): Promise<any> {
// Database operasyonları
}
}
// Single instance export edilir
export const db = new DatabaseConnection({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
});
// other-file.ts
import { db } from './db-connection';
await db.query('SELECT * FROM users');
// another-file.ts
import { db } from './db-connection'; // Aynı instance
Modül sistemi pattern’in karmaşıklığı olmadan singleton davranışı sağlar. Instance bir kez yaratılır, import’lar arasında paylaşılır ve modül mocking ile test edilebilir.
Modül Singleton’ları Nerede Çöker#
Modül seviyesindeki singleton’lar tek, uzun ömürlü bir instance varsayar ve bu varsayım üç ortamda çöker: development’ta hot module replacement (HMR) modülü reload eder ve yeni bir instance yaratır, server-side rendering (SSR) her request için izole bir instance ister, testler ise paylaşılan instance üzerinden bir çalıştırmadan diğerine state sızdırır. Her çağrıda taze bir instance yaratan bir factory function ya da DI container üçünü de çözer:
// SSR için: Request başına instance yarat
export function createRequestContext(req: Request): RequestContext {
return new RequestContext(req);
}
// Middleware request başına context yaratır
app.use((req, res, next) => {
req.context = createRequestContext(req);
next();
});
// Her request isolated context'e sahip
Dependency Injection Container’lar#
Kompleks uygulamalar için dependency injection container’lar nesne lifecycle’larını yönetir:
import { injectable, inject, container } from 'tsyringe';
@injectable()
class DatabaseConnection {
constructor(
@inject('DatabaseConfig') private config: DatabaseConfig
) {
// Setup logic
}
}
// Singleton olarak kaydet
container.registerSingleton(DatabaseConnection);
// Herhangi bir class'ta kullanım
@injectable()
class UserRepository {
constructor(private db: DatabaseConnection) {
// Otomatik olarak singleton instance alır
}
}
DI container’lar dependency injection faydalarıyla singleton semantiği verir. Test kolaylaşır: container’daki implementation’ı değiştirirsin.
Logger ve Feature Flag Konfigürasyonu#
Bazı senaryolar explicit singleton pattern’den gerçekten faydalanır:
Configured transport’lar ile logger:
class Logger {
private static instance: Logger;
private transports: Transport[] = [];
private constructor() {}
static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
addTransport(transport: Transport): void {
this.transports.push(transport);
}
log(level: string, message: string): void {
this.transports.forEach(t => t.write(level, message));
}
}
// App başlangıcında bir kez initialize et
const logger = Logger.getInstance();
logger.addTransport(new ConsoleTransport());
logger.addTransport(new FileTransport('/var/log/app.log'));
// Konfigürasyon olmadan her yerde kullan
logger.log('info', 'Application started');
Feature flag manager:
class FeatureFlags {
private static instance: FeatureFlags;
private flags = new Map<string, boolean>();
private constructor() {}
static getInstance(): FeatureFlags {
if (!FeatureFlags.instance) {
FeatureFlags.instance = new FeatureFlags();
}
return FeatureFlags.instance;
}
async initialize(): Promise<void> {
// Remote config'den flag'leri yükle
const response = await fetch('/api/feature-flags');
const data = await response.json();
data.forEach((flag: any) => this.flags.set(flag.name, flag.enabled));
}
isEnabled(flagName: string): boolean {
return this.flags.get(flagName) ?? false;
}
}
// Başlangıçta initialize et
await FeatureFlags.getInstance().initialize();
// Her yerde kontrol et
if (FeatureFlags.getInstance().isEnabled('new-dashboard')) {
// Yeni dashboard'u göster
}
Inject Edilebilir Dependency’lerde Singleton#
Inject edilmesi gereken dependency’ler için singleton kullanma:
// YAPMA: Test etmesi zor, implementation değiştiremezsin
class ApiClient {
private static instance: ApiClient;
private baseUrl = 'https://api.prod.com';
private constructor() {}
static getInstance(): ApiClient {
if (!ApiClient.instance) {
ApiClient.instance = new ApiClient();
}
return ApiClient.instance;
}
}
// YAP: Dependency'leri constructor parametresi olarak al
class ApiClient {
constructor(
private config: ApiConfig,
private httpClient: HttpClient
) {}
async get(endpoint: string): Promise<any> {
return this.httpClient.get(`${this.config.baseUrl}${endpoint}`);
}
}
// Production
const apiClient = new ApiClient(
{ baseUrl: 'https://api.prod.com' },
new HttpClient()
);
// Testing
const apiClient = new ApiClient(
{ baseUrl: 'http://localhost:3000' },
new MockHttpClient()
);
Factory Function’lar vs Factory Class’lar#
Factory pattern nesne yaratma mantığını encapsulate eder. TypeScript’te seçeneklerin var: factory function’lar, factory class’lar veya discriminated union’lar.
Factory Function’lar Ne Zaman Yeterli#
Basit yaratma mantığı class’lara ihtiyaç duymaz:
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
function createLogger(level: LogLevel): Logger {
switch (level) {
case 'debug':
return new DebugLogger();
case 'info':
return new InfoLogger();
case 'warn':
return new WarnLogger();
case 'error':
return new ErrorLogger();
}
}
// Exhaustive checking ile type-safe
const logger = createLogger('debug');
TypeScript’in exhaustive checking’i tüm case’leri handle ettiğinden emin olur. Yeni bir log level eklersen compiler eksik implementation’ları yakalar.
Lambda Handler’lar Arası Paylaşılan Konfigürasyon#
Factory class’lar, yaratma mantığı paylaşılan konfigürasyon gerektirdiğinde mantıklı:
import { Function, Runtime, Duration, ILayerVersion } from 'aws-cdk-lib/aws-lambda';
import { IVpc, ISecurityGroup, SubnetType } from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';
class LambdaFunctionFactory {
constructor(
private vpc: IVpc,
private layers: ILayerVersion[],
private securityGroup: ISecurityGroup,
private scope: Construct
) {}
createApiHandler(config: ApiHandlerConfig): Function {
return new Function(this.scope, config.id, {
vpc: this.vpc,
vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [this.securityGroup],
layers: this.layers,
runtime: Runtime.NODEJS_20_X,
timeout: Duration.seconds(30),
memorySize: 1024,
...config,
});
}
createWorkerHandler(config: WorkerConfig): Function {
return new Function(this.scope, config.id, {
vpc: this.vpc,
vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [this.securityGroup],
layers: this.layers,
runtime: Runtime.NODEJS_20_X,
timeout: Duration.minutes(15),
memorySize: 2048, // Worker'lar daha fazla memory'ye ihtiyaç duyar
...config,
});
}
createScheduledHandler(config: ScheduledConfig): Function {
return new Function(this.scope, config.id, {
vpc: this.vpc,
vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [this.securityGroup],
layers: this.layers,
runtime: Runtime.NODEJS_20_X,
timeout: Duration.minutes(5),
memorySize: 512, // Scheduled task'lar genelde daha hafif
...config,
});
}
}
// CDK stack'te kullanım
const factory = new LambdaFunctionFactory(
vpc,
[commonLayer, vendorLayer],
lambdaSecurityGroup,
this
);
const getUserHandler = factory.createApiHandler({
id: 'GetUserHandler',
handler: 'dist/handlers/get-user.handler',
environment: { TABLE_NAME: usersTable.tableName },
});
const processJobWorker = factory.createWorkerHandler({
id: 'ProcessJobWorker',
handler: 'dist/workers/process-job.handler',
environment: { QUEUE_URL: jobQueue.queueUrl },
});
Bu factory ortak Lambda konfigürasyonunu (VPC, layer’lar, security group’lar) merkezileştirirken function tipi başına customization’a izin veriyor. Factory olmadan her Lambda tanımı aynı 10-15 satırı tekrarlardı.
Discriminated Union Factory’ler#
TypeScript’in discriminated union’ları type-safe factory’ler sağlar:
type LoggerConfig =
| { type: 'console'; colorize: boolean }
| { type: 'file'; path: string; maxSize: number }
| { type: 'cloudwatch'; logGroup: string; region: string };
function createLogger(config: LoggerConfig): Logger {
switch (config.type) {
case 'console':
return new ConsoleLogger(config.colorize);
case 'file':
return new FileLogger(config.path, config.maxSize);
case 'cloudwatch':
return new CloudWatchLogger(config.logGroup, config.region);
}
}
// TypeScript her tip için doğru property'leri garanti eder
const logger = createLogger({
type: 'file',
path: '/var/log/app.log',
maxSize: 10485760, // 10MB
// Buraya 'colorize' eklersek TypeScript hata verir
});
Compiler, her konfigürasyon branch’inin tam olarak doğru property’lere sahip olduğunu doğrulayarak eksik veya yanlış option’lardan kaynaklanan runtime hatalarını önler.
Type Guard’lar ile Factory Function’lar#
Factory’leri type guard’larla runtime type checking için birleştir:
type DatabaseConfig = PostgresConfig | MySQLConfig | SQLiteConfig;
interface PostgresConfig {
type: 'postgres';
socketPath: string;
database: string;
}
interface MySQLConfig {
type: 'mysql';
host: string;
port: number;
connectionLimit: number;
}
interface SQLiteConfig {
type: 'sqlite';
filename: string;
}
function createDatabase(config: DatabaseConfig): Database {
if (config.type === 'postgres') {
return new PostgresDatabase(config.socketPath, config.database);
}
if (config.type === 'mysql') {
return new MySQLDatabase(config.host, config.port, config.connectionLimit);
}
return new SQLiteDatabase(config.filename);
}
TypeScript her branch’te type’ları daraltır, branch-specific property’ler için autocomplete ve type safety verir.
Builder’la Aşamalı Kurulum#
Builder pattern kompleks nesneleri adım adım oluşturur. TypeScript’te builder ile options object arasında karar vermen gerekir.
Options Object Ne Zaman Daha İyi#
Az optional parametre içeren basit durumlar için options object’ler daha açık:
interface LambdaOptions {
handler: string;
runtime?: Runtime;
timeout?: number;
memorySize?: number;
environment?: Record<string, string>;
}
const fn = new Lambda({
handler: 'index.handler',
runtime: Runtime.NODEJS_20_X,
timeout: 30,
memorySize: 1024,
environment: { TABLE_NAME: 'users' },
});
Bu temiz, type-safe ve self-documenting. Builder’a gerek yok.
Step Function Workflow’unu Adım Adım Kurmak#
Builder pattern, dependency’ler ve progressive konfigürasyon içeren kompleks nesnelerde yerini hak ediyor:
class StepFunctionsWorkflow {
private states: State[] = [];
private errorHandler?: ErrorHandler;
addState(name: string, state: State): this {
this.states.push({ name, ...state });
return this;
}
addParallelStates(name: string, branches: State[][]): this {
this.states.push({
name,
type: 'Parallel',
branches,
});
return this;
}
onError(handler: (builder: ErrorPathBuilder) => ErrorPathBuilder): this {
const errorPath = new ErrorPathBuilder();
this.errorHandler = handler(errorPath).build();
return this;
}
build(): StateMachine {
if (this.states.length === 0) {
throw new Error('Workflow en az bir state içermeli');
}
return {
states: this.states,
errorHandler: this.errorHandler,
startAt: this.states[0].name,
};
}
}
// Kullanım progressive disclosure gösterir
const workflow = new StepFunctionsWorkflow()
.addState('ValidateInput', {
type: 'Task',
resource: validateLambda.functionArn,
})
.addParallelStates('ProcessData', [
[
{
type: 'Task',
resource: scanVirusLambda.functionArn,
retry: [{ errorEquals: ['States.TaskFailed'], maxAttempts: 3 }],
},
],
[
{
type: 'Task',
resource: extractMetadataLambda.functionArn,
timeout: 60,
},
],
[
{
type: 'Task',
resource: generateThumbnailLambda.functionArn,
resultPath: '$.thumbnail',
},
],
])
.addState('StoreResults', {
type: 'Task',
resource: storeLambda.functionArn,
})
.onError((errorPath) =>
errorPath
.addState('LogError', {
type: 'Task',
resource: logErrorLambda.functionArn,
})
.addState('SendAlert', {
type: 'Task',
resource: alertLambda.functionArn,
})
)
.build();
Builder şunları sağlıyor:
- Progressive disclosure: Error handling sadece state’ler eklendikten sonra kullanılabilir
- Fluent API: Autocomplete ile method chaining
- Validation:
build()complete konfigürasyonu validate eder - Kompleks nesting: Parallel state’ler ve error handling temiz bir şekilde compose edilir
Generic’lerle Type-Safe Builder#
Required field’ları compile time’da takip et:
type RequiredFields = 'url' | 'method';
class RequestBuilder<TSet extends string = never> {
private config: Partial<RequestConfig> = {};
url(url: string): RequestBuilder<TSet | 'url'> {
this.config.url = url;
return this as any;
}
method(method: HttpMethod): RequestBuilder<TSet | 'method'> {
this.config.method = method;
return this as any;
}
headers(headers: Record<string, string>): this {
this.config.headers = headers;
return this;
}
timeout(ms: number): this {
this.config.timeout = ms;
return this;
}
// build() sadece tüm required field'lar set edildiğinde kullanılabilir
build(this: RequestBuilder<RequiredFields>): Request {
return new Request(this.config as RequestConfig);
}
}
// Compile error - required field'lar eksik
// const req = new RequestBuilder().build();
// OK - tüm required field'lar sağlanmış
const req = new RequestBuilder()
.url('https://api.example.com/users')
.method('GET')
.headers({ 'Authorization': 'Bearer token' })
.timeout(5000)
.build();
Generic type parametresi hangi field’ların set edildiğini takip eder. build() methodu sadece TSet tüm required field’ları içerdiğinde çağrılabilir.
Factory Method’ları ile Builder#
Pattern’leri ortak konfigürasyonlar için birleştir:
class ApiClientBuilder {
private config: Partial<ApiClientConfig> = {};
// Ortak konfigürasyonlar için factory method'lar
static forProduction(apiKey: string): ApiClientBuilder {
return new ApiClientBuilder()
.withApiKey(apiKey)
.withTimeout(30000)
.withRetries(3)
.enableCaching()
.withBaseUrl('https://api.prod.com');
}
static forDevelopment(apiKey: string): ApiClientBuilder {
return new ApiClientBuilder()
.withApiKey(apiKey)
.withTimeout(60000)
.disableCaching()
.withVerboseLogging()
.withBaseUrl('https://api.dev.com');
}
withApiKey(key: string): this {
this.config.apiKey = key;
return this;
}
withTimeout(ms: number): this {
this.config.timeout = ms;
return this;
}
withRetries(count: number): this {
this.config.retries = count;
return this;
}
enableCaching(): this {
this.config.cacheEnabled = true;
return this;
}
disableCaching(): this {
this.config.cacheEnabled = false;
return this;
}
withVerboseLogging(): this {
this.config.logLevel = 'debug';
return this;
}
withBaseUrl(url: string): this {
this.config.baseUrl = url;
return this;
}
build(): ApiClient {
if (!this.config.apiKey) {
throw new Error('API key gerekli');
}
if (!this.config.baseUrl) {
throw new Error('Base URL gerekli');
}
return new ApiClient(this.config as ApiClientConfig);
}
}
// Mantıklı default'larla hızlı başlangıç
const prodClient = ApiClientBuilder.forProduction(process.env.API_KEY);
// Ya da sıfırdan customize et
const customClient = new ApiClientBuilder()
.withApiKey(process.env.API_KEY)
.withBaseUrl('https://api.custom.com')
.withTimeout(45000)
.withRetries(5)
.build();
Builder’ı 5+ optional parametre içeren, kompleks validation dependency’leri olan, progressive konfigürasyon gerektiren ya da tek bir object literal’dan çok method zinciri olarak daha iyi okunan bir domain-specific language (DSL) kuran nesneler için ayır.
Prototype Yerine Object Spread#
Prototype pattern mevcut instance’ları clone’layarak nesneler yaratır. JavaScript’in prototypal inheritance’ı bu işin çoğunu zaten yapıyor, kalanını da modern dil özellikleri hallediyor.
Object.create() ile Clone’lama#
Klasik prototype cloning:
class Prototype {
clone(): this {
return Object.create(this);
}
}
class ConcretePrototype extends Prototype {
constructor(public data: string) {
super();
}
}
const original = new ConcretePrototype('data');
const clone = original.clone();
Shallow Clone için Object Spread#
Object spread shallow cloning’i temiz bir şekilde hallediyor:
const original = {
name: 'John',
age: 30,
address: { city: 'NYC', zip: '10001' },
};
// Shallow clone
const clone = { ...original };
// Clone'u değiştir - original'in primitive property'lerini etkilemez
clone.name = 'Jane';
console.log(original.name); // Hala 'John'
// Ama nested object'ler paylaşılır
clone.address.city = 'LA';
console.log(original.address.city); // Aynı zamanda 'LA'
Deep cloning için structuredClone() kullan (Node.js 17+, Node 18 LTS’ten beri yaygın; modern tarayıcılarda da var):
const original = {
name: 'John',
age: 30,
address: { city: 'NYC', zip: '10001' },
metadata: {
tags: ['developer', 'typescript'],
preferences: { theme: 'dark' },
},
};
// Deep clone
const deepClone = structuredClone(original);
// Nested property'leri değiştir - original'i etkilemez
deepClone.address.city = 'LA';
deepClone.metadata.tags.push('react');
console.log(original.address.city); // Hala 'NYC'
console.log(original.metadata.tags); // Hala ['developer', 'typescript']
structuredClone() şunları handle eder:
- Nested object’ler ve array’ler
- Date, RegExp, Map, Set
- Typed array’ler
- Cyclic reference’lar
Şunları handle etmez:
- Function’lar
- DOM node’ları
- Symbol’ler
- Prototype’lar (plain object’ler oluşturur)
Immutable React State Update’leri#
React state update’leri pratik cloning’i gösteriyor:
const [state, setState] = useState({
count: 0,
items: ['elma', 'muz'],
user: { name: 'John', role: 'admin' },
});
// Değişiklikle shallow clone
setState(prev => ({ ...prev, count: prev.count + 1 }));
// Nested yapılar için deep clone
setState(prev => ({
...prev,
items: [...prev.items, 'kiraz'],
user: { ...prev.user, role: 'user' },
}));
Ortak Default’lu Test Fixture’ları#
Test data builder’lar prototype benzeri cloning’den faydalanır:
class UserBuilder {
private template: Partial<User> = {
role: 'user',
verified: false,
createdAt: new Date(),
preferences: { theme: 'light', notifications: true },
};
fromTemplate(template: Partial<User>): this {
this.template = { ...this.template, ...template };
return this;
}
asAdmin(): this {
return this.fromTemplate({
role: 'admin',
permissions: ['read', 'write', 'delete'],
});
}
asVerified(): this {
return this.fromTemplate({ verified: true });
}
withEmail(email: string): this {
return this.fromTemplate({ email });
}
build(): User {
return {
id: crypto.randomUUID(),
email: `user-${Date.now()}@example.com`,
...this.template,
} as User;
}
}
// Base admin template oluştur
const adminTemplate = new UserBuilder().asAdmin().asVerified();
// Farklı testler için clone et ve customize et
const admin1 = adminTemplate.fromTemplate({ email: 'admin1@example.com' }).build();
const admin2 = adminTemplate.fromTemplate({ email: 'admin2@example.com' }).build();
// Her biri admin default'larına sahip ama unique email ve ID
Pattern Bazında Trade-off’lar#
| Pattern | Karmaşıklık | Performans | Test | Bundle boyutu |
|---|---|---|---|---|
| Singleton | ES modülleri: sıfır boilerplate. DI container’lar: orta, framework bilgisi gerektirir. Klasik singleton: düşük ama test overhead’i taşır. | İhmal edilebilir; lazy initialization her erişimde küçük bir check ekler. | Klasik singleton testler arası state paylaşır. Modül bazlı singleton’lar modül mock’ları ile mocklanabilir. DI container’lar testi basitleştirir. | ES modül yaklaşımı minimal. DI container’lar ciddi ağırlık ekler; tarayıcıya göndermeden önce container’ın yayımlanmış bundle boyutuna bak. |
| Factory | Function’lar: düşük, basit. Class’lar: konfigürasyon paylaşıldığında orta. Discriminated union’lar: güçlü type safety ile düşük karmaşıklık. | Sadece bir function çağrısı; direct instantiation’a göre anlamlı fark yok. | Pure function’lar veya injectable dependency’ler kolay test edilir. Discriminated union’lar branch branch test edilebilir. | Küçük: sadece function’lar veya hafif class’lar. |
| Builder | 5+ optional parametreden sonra orta karmaşıklığa değer. Type-safe generic’ler karmaşıklığı yükseltir, public library API’leri için gerekçelendirilir. Immutable builder’lar daha fazla memory harcar ama concurrent senaryolarda daha güvenlidir. | Immutable builder’lar intermediate object’ler oluşturur; mutable builder’lar ve düz method chaining ihmal edilebilir overhead ekler. | Builder’lar mükemmel test fixture’ları oluşturur; progressive konfigürasyon her build adımını test etmeyi sağlar. | Her builder methodu bundle’a eklenir. Immutable builder’lar mutable’lardan daha büyük. Type-safe builder’lar compile edilip tamamen kaybolur (sıfır runtime maliyeti). |
| Prototype | Object spread ve structuredClone() ikisi de çok düşük karmaşıklıkta; custom cloning logic sadece özel durumlar için gerekli. | Spread shallow clone için hızlı; structuredClone() daha yavaş ama deep cloning’i doğru handle eder. Performans sadece büyük object’lerde veya yüksek frekanslı cloning’de önemli. | Kolay: pure data transformation, side effect veya gizli state yok. | İhmal edilebilir: sadece built-in dil özellikleri. |
Varsayılan ve Sınırları#
Varsayılan, uygulama kodunun çoğunda geçerli: instance’ı modülden export et, yaratım dallanıyorsa factory function yaz, kopya gerekiyorsa object spread kullan. TypeScript’in discriminated union’ları ve generic kısıtları, 1994 implementasyonlarının runtime’da zorlamak zorunda kaldığı şeyi compile time’da karşılıyor.
Varsayılandan üç durumda çık. Request başına izolasyon gerekiyorsa (SSR, request-scoped context) modül singleton’ı işe yaramaz; instance’ları factory ya da DI container üzerinden yarat. Aynı altyapı konfigürasyonu birçok nesneye uygulanıyorsa, örneğin bir grup Lambda function’ına VPC, layer ve security group veriyorsan, factory class’a geçmek mantıklı. Kurulumun adım adım doğrulanması gerekiyorsa ya da küçük bir DSL olarak daha iyi okunuyorsa builder kullan. Bu üçünün dışında dil özelliği aynı işi daha az kodla görüyor.
Kaynaklar#
- Creational Design Patterns - Refactoring.Guru (yeni sekmede açılır) - Her GoF creational pattern için niyet, yapı ve uygulanabilirliği açıklayan genel bakış
- Design Patterns in TypeScript - Refactoring.Guru (yeni sekmede açılır) - Tüm klasik GoF pattern’ler için TypeScript kod örnekleri
- TypeScript Handbook - Classes (yeni sekmede açılır) - TypeScript sınıf sözdizimi, erişim belirteçleri ve parametre özellikleri için resmi referans
- TypeScript Handbook - Generics (yeni sekmede açılır) - Factory ve builder pattern’lerle ilgili generic tür parametreleri ve kısıtlamalar
- TypeScript Handbook - Utility Types (yeni sekmede açılır) - Belirli creational soyutlamaların yerini alan yerleşik mapped type’lar (Partial, Readonly, Required)
- structuredClone() - MDN (yeni sekmede açılır) - Deep clone semantiği, transfer seçeneği ve hangi değerlerin DataCloneError fırlattığı
- Node.js Globals - structuredClone() (yeni sekmede açılır) - Global’in Node.js v17.0.0’da geldiğini ve WHATWG metodunu izlediğini gösteriyor
Klasik Tasarım Kalıplarına Modern Bakış
Klasik Gang of Four tasarım kalıplarının modern TypeScript, React ve fonksiyonel programlama bağlamında nasıl evrildiğini inceleyen kapsamlı bir seri. Klasik kalıpların hala ne zaman geçerli olduğunu, ne zaman yerini yeni yaklaşımlara bıraktığını ve temel prensiplerin modern kod tabanlarında nasıl ortaya çıktığını öğren.
Bu serideki tüm yazılar
İlgili yazılar
SOLID prensiplerinin modern JavaScript'te uygulanışı: TypeScript, React hooks ve fonksiyonel pattern'lerle pratik örnekler, ayrıca ne zaman gereksiz.
typescript · javascript · react +4
Decorator, Adapter, Facade, Composite ve Proxy patternlerinin React ve TypeScript'te evrimi: HOC'lar ne zaman hook'lara yol verir, adapterler API'ları nasıl izole eder.
typescript · react · design-patterns +2
Domain-Driven Design'a kapsamlı giriş: temel kavramlar, yapı taşları, stratejik desenler ve DDD'yi ne zaman ve nasıl uygulayacağına dair rehber.
domain-driven-design · architecture · design-patterns +2
CDK stack düzeni için yaşam döngüsü testi: bir kaynağın ömrü tek bir dağıtımdan uzunsa kendi uzun ömürlü stack'ine koyun, ona bilinen bir adla erişin.
aws-cdk · infrastructure-as-code · typescript +3
Mimari ağırlığını runtime'ın init-amortismanına göre seç: single-purpose Lambda'da yalın handler, Lambdalith'te orta, tam OOP/DI yalnızca uzun ömürlü runtime'da.
architecture · lambda · serverless +3