JavaScript Decorator'larına derinlemesine bir bakış; söz dizimi, üst veri programlama için kullanım durumları, en iyi uygulamalar ve kod sürdürülebilirliğine etkileri inceleniyor. Pratik örnekler ve geleceğe yönelik değerlendirmeler içerir.
JavaScript Decorator'ları: Üst Veri Programlamanın Uygulanması
JavaScript Decorator'ları, sınıfların, metotların, özelliklerin ve parametrelerin davranışlarını bildirimsel ve yeniden kullanılabilir bir şekilde değiştirmenize ve bunlara üst veri (metadata) eklemenize olanak tanıyan güçlü bir özelliktir. ECMAScript standart sürecinde 3. aşama (stage 3) bir tekliftir ve kendi (biraz farklı) uygulamasına sahip olan TypeScript ile yaygın olarak kullanılmaktadır. Bu makale, JavaScript Decorator'larına kapsamlı bir genel bakış sunacak, üst veri programlamadaki rollerine odaklanacak ve kullanımlarını pratik örneklerle gösterecektir.
JavaScript Decorator'ları Nedir?
Decorator'lar, bir nesnenin yapısını değiştirmeden işlevselliğini artıran veya değiştiren bir tasarım desenidir. JavaScript'te decorator'lar, sınıflara, metotlara, erişimcilere (accessors), özelliklere veya parametrelere eklenebilen özel türde bildirimlerdir. @ sembolü ve ardından dekore edilen eleman tanımlandığında çalıştırılacak bir fonksiyon kullanırlar.
Decorator'ları, dekore edilen elemanı girdi olarak alan ve o elemanın değiştirilmiş bir sürümünü döndüren veya ona dayanarak bazı yan etkiler gerçekleştiren fonksiyonlar olarak düşünün. Bu, orijinal sınıfı veya fonksiyonu doğrudan değiştirmeden işlevsellik eklemek için temiz ve şık bir yol sağlar.
Temel Kavramlar:
- Decorator Fonksiyonu:
@sembolünden önce gelen fonksiyondur. Dekore edilen eleman hakkında bilgi alır ve onu değiştirebilir. - Dekore Edilen Eleman: Dekore edilen sınıf, metot, erişimci, özellik veya parametre.
- Üst Veri (Metadata): Veriyi tanımlayan veridir. Decorator'lar genellikle üst veriyi kod elemanlarıyla ilişkilendirmek için kullanılır.
Söz Dizimi ve Yapı
Bir decorator'ın temel söz dizimi aşağıdaki gibidir:
@decorator
class MyClass {
// Class members
}
Burada, @decorator decorator fonksiyonu ve MyClass dekore edilen sınıftır. Decorator fonksiyonu, sınıf tanımlandığında çağrılır ve sınıf tanımına erişebilir ve onu değiştirebilir.
Decorator'lar ayrıca, decorator fonksiyonunun kendisine iletilen argümanları da kabul edebilir:
@loggable(true, "Custom Message")
class MyClass {
// Class members
}
Bu durumda, loggable bir decorator fabrika fonksiyonudur; argümanları alır ve asıl decorator fonksiyonunu döndürür. Bu, daha esnek ve yapılandırılabilir decorator'lara olanak tanır.
Decorator Türleri
Neyi dekore ettiklerine bağlı olarak farklı decorator türleri vardır:
- Sınıf Decorator'ları: Sınıflara uygulanır.
- Metot Decorator'ları: Bir sınıf içindeki metotlara uygulanır.
- Erişimci (Accessor) Decorator'ları: Getter ve setter erişimcilerine uygulanır.
- Özellik (Property) Decorator'ları: Sınıf özelliklerine uygulanır.
- Parametre Decorator'ları: Bir metodun parametrelerine uygulanır.
Sınıf Decorator'ları
Sınıf decorator'ları, bir sınıfın davranışını değiştirmek veya geliştirmek için kullanılır. Sınıfın kurucu fonksiyonunu (constructor) bir argüman olarak alırlar ve orijinalini değiştirmek için yeni bir kurucu fonksiyon döndürebilirler. Bu, loglama, bağımlılık enjeksiyonu veya durum yönetimi gibi işlevler eklemenizi sağlar.
Örnek:
function loggable(constructor: Function) {
console.log("Class " + constructor.name + " was created.");
}
@loggable
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
const user = new User("Alice"); // Çıktı: User sınıfı oluşturuldu.
Bu örnekte, loggable decorator'ı, User sınıfının yeni bir örneği oluşturulduğunda konsola bir mesaj yazar. Bu, hata ayıklama veya izleme için yararlı olabilir.
Metot Decorator'ları
Metot decorator'ları, bir sınıf içindeki bir metodun davranışını değiştirmek için kullanılır. Aşağıdaki argümanları alırlar:
target: Sınıfın prototipi.propertyKey: Metodun adı.descriptor: Metot için özellik tanımlayıcısı (property descriptor).
Tanımlayıcı, metodun davranışına erişmenize ve onu değiştirmenize olanak tanır; örneğin, onu ek mantıkla sarmalayabilir veya tamamen yeniden tanımlayabilirsiniz.
Örnek:
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling method ${propertyKey} with arguments: ${args}`);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@logMethod
add(a: number, b: number): number {
return a + b;
}
}
const calculator = new Calculator();
const sum = calculator.add(5, 3); // Metot çağrısı ve dönüş değeri için logları yazdırır
Bu örnekte, logMethod decorator'ı metodun argümanlarını ve dönüş değerini loglar. Bu, hata ayıklama ve performans izleme için yararlı olabilir.
Erişimci Decorator'ları
Erişimci decorator'ları, metot decorator'larına benzer ancak getter ve setter erişimcilerine uygulanır. Metot decorator'ları ile aynı argümanları alırlar ve erişimcinin davranışını değiştirmenize olanak tanırlar.
Örnek:
function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalSet = descriptor.set;
descriptor.set = function (value: any) {
if (value < 0) {
throw new Error("Value must be non-negative.");
}
originalSet.call(this, value);
};
}
class Temperature {
private _celsius: number;
constructor(celsius: number) {
this._celsius = celsius;
}
@validate
set celsius(value: number) {
this._celsius = value;
}
get celsius(): number {
return this._celsius;
}
}
const temperature = new Temperature(25);
temperature.celsius = 30; // Geçerli
// temperature.celsius = -10; // Hata fırlatır
Bu örnekte, validate decorator'ı sıcaklık değerinin negatif olmamasını sağlar. Bu, veri bütünlüğünü zorunlu kılmak için yararlı olabilir.
Özellik Decorator'ları
Özellik decorator'ları, bir sınıf özelliğinin davranışını değiştirmek için kullanılır. Aşağıdaki argümanları alırlar:
target: Sınıfın prototipi (örnek özellikleri için) veya sınıfın kurucu fonksiyonu (statik özellikler için).propertyKey: Özelliğin adı.
Özellik decorator'ları, üst veri tanımlamak veya özelliğin tanımlayıcısını değiştirmek için kullanılabilir.
Örnek:
function readonly(target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
writable: false,
});
}
class Configuration {
@readonly
apiUrl: string = "https://api.example.com";
}
const config = new Configuration();
// config.apiUrl = "https://newapi.example.com"; // Strict modda bir hata fırlatır
Bu örnekte, readonly decorator'ı apiUrl özelliğini salt okunur (read-only) yapar ve başlangıçtan sonra değiştirilmesini engeller. Bu, değişmez yapılandırma değerlerini tanımlamak için yararlı olabilir.
Parametre Decorator'ları
Parametre decorator'ları, bir metot parametresinin davranışını değiştirmek için kullanılır. Aşağıdaki argümanları alırlar:
target: Sınıfın prototipi (örnek metotları için) veya sınıfın kurucu fonksiyonu (statik metotlar için).propertyKey: Metodun adı.parameterIndex: Metodun parametre listesindeki parametrenin indeksi.
Parametre decorator'ları diğer decorator türlerine göre daha az yaygın olarak kullanılır, ancak girdi parametrelerini doğrulamak veya bağımlılıkları enjekte etmek için yararlı olabilirler.
Örnek:
function required(target: any, propertyKey: string, parameterIndex: number) {
const existingRequiredParameters: number[] = Reflect.getOwnMetadata(propertyKey, target, "required") || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(propertyKey, existingRequiredParameters, target, "required");
}
function validateMethod(target: any, propertyName: string, descriptor: PropertyDescriptor) {
let method = descriptor.value!;
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(propertyName, target, "required");
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (arguments[parameterIndex] === null || arguments[parameterIndex] === undefined) {
throw new Error(`Missing required argument at index ${parameterIndex}`);
}
}
}
return method.apply(this, arguments);
};
}
class ArticleService {
create(
@required title: string,
@required content: string
): void {
console.log(`Creating article with title: ${title} and content: ${content}`);
}
}
const service = new ArticleService();
// service.create("My Article", null); // Hata fırlatır
service.create("My Article", "Article Content"); // Geçerli
Bu örnekte, required decorator'ı parametreleri zorunlu olarak işaretler ve validateMethod decorator'ı bu parametrelerin null veya undefined olmadığından emin olur. Bu, metot girdi doğrulamasını zorunlu kılmak için yararlı olabilir.
Decorator'lar ile Üst Veri Programlama
Decorator'ların en güçlü kullanım alanlarından biri üst veri programlamadır. Üst veri, veri hakkındaki veridir. Programlama bağlamında, kodunuzun yapısını, davranışını ve amacını tanımlayan veridir. Decorator'lar, üst veriyi sınıflar, metotlar, özellikler ve parametrelerle ilişkilendirmek için temiz ve bildirimsel bir yol sağlar.
Reflect Metadata API'si
Reflect Metadata API, nesnelerle ilişkili üst verileri saklamanıza ve almanıza olanak tanıyan standart bir API'dir. Aşağıdaki fonksiyonları sağlar:
Reflect.defineMetadata(key, value, target, propertyKey): Bir nesnenin belirli bir özelliği için üst veri tanımlar.Reflect.getMetadata(key, target, propertyKey): Bir nesnenin belirli bir özelliği için üst veriyi alır.Reflect.hasMetadata(key, target, propertyKey): Bir nesnenin belirli bir özelliği için üst verinin mevcut olup olmadığını kontrol eder.Reflect.deleteMetadata(key, target, propertyKey): Bir nesnenin belirli bir özelliği için üst veriyi siler.
Bu fonksiyonları decorator'larla birlikte kullanarak kod elemanlarınızla üst veri ilişkilendirebilirsiniz.
Örnek: Üst Veri Tanımlama ve Alma
import 'reflect-metadata';
const logKey = "log";
function log(message: string) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata(logKey, message, target, key);
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(Reflect.getMetadata(logKey, target, key));
const result = originalMethod.apply(this, args);
return result;
}
return descriptor;
}
}
class Example {
@log("Metot çalıştırılıyor")
myMethod(arg: string): string {
return `Method called with ${arg}`;
}
}
const example = new Example();
example.myMethod("Hello"); // Çıktı: Metot çalıştırılıyor, Method called with Hello
Bu örnekte, log decorator'ı, bir log mesajını myMethod metoduyla ilişkilendirmek için Reflect Metadata API'sini kullanır. Metot çağrıldığında, decorator mesajı alır ve konsola yazar.
Üst Veri Programlama İçin Kullanım Alanları
Decorator'larla üst veri programlamanın birçok pratik uygulaması vardır, bunlar arasında:
- Serileştirme ve Deserializasyon: Özelliklerin JSON veya diğer formatlara nasıl serileştirileceğini veya bu formatlardan nasıl geri alınacağını kontrol etmek için özellikleri üst veri ile işaretleyin. Bu, harici API'lerden veya veritabanlarından gelen verilerle uğraşırken, özellikle farklı platformlar arasında veri dönüşümü gerektiren dağıtık sistemlerde (örneğin, farklı bölgesel standartlar arasında tarih formatlarını dönüştürme) yararlı olabilir. Uluslararası gönderim adresleriyle ilgilenen bir e-ticaret platformu düşünün; burada her ülke için doğru adres formatını ve doğrulama kurallarını belirtmek için üst veriyi kullanabilirsiniz.
- Bağımlılık Enjeksiyonu (Dependency Injection): Bir sınıfa enjekte edilmesi gereken bağımlılıkları tanımlamak için üst veriyi kullanın. Bu, bağımlılıkların yönetimini basitleştirir ve gevşek bağlılığı (loose coupling) teşvik eder. Hizmetlerin birbirine bağlı olduğu bir mikroservis mimarisini düşünün. Decorator'lar ve üst veri, yapılandırmaya dayalı olarak hizmet istemcilerinin dinamik olarak enjekte edilmesini kolaylaştırarak daha kolay ölçeklendirme ve hata toleransı sağlar.
- Doğrulama (Validation): Doğrulama kurallarını üst veri olarak tanımlayın ve verileri otomatik olarak doğrulamak için decorator'ları kullanın. Bu, veri bütünlüğünü sağlar ve tekrar eden kodları (boilerplate code) azaltır. Örneğin, küresel bir finans uygulamasının çeşitli bölgesel finansal düzenlemelere uyması gerekir. Üst veri, kullanıcının konumuna göre para birimi formatları, vergi hesaplamaları ve işlem limitleri için doğrulama kurallarını tanımlayarak yerel yasalara uyumu sağlayabilir.
- Yönlendirme (Routing) ve Ara Yazılım (Middleware): Web uygulamaları için rotaları ve ara yazılımları tanımlamak için üst veriyi kullanın. Bu, uygulamanızın yapılandırmasını basitleştirir ve daha sürdürülebilir hale getirir. Küresel olarak dağıtılmış bir içerik dağıtım ağı (CDN), içeriğin türüne ve kullanıcının konumuna göre önbellekleme politikalarını ve yönlendirme kurallarını tanımlamak için üst veriyi kullanarak dünya çapındaki kullanıcılar için performansı optimize edebilir ve gecikmeyi azaltabilir.
- Yetkilendirme ve Kimlik Doğrulama (Authorization and Authentication): Rolleri, izinleri ve kimlik doğrulama gereksinimlerini metotlar ve sınıflarla ilişkilendirerek bildirimsel güvenlik politikalarını kolaylaştırın. Farklı departmanlarda ve konumlarda çalışanları olan çok uluslu bir şirket düşünün. Decorator'lar, kullanıcının rolüne, departmanına ve konumuna göre erişim kontrol kurallarını tanımlayarak yalnızca yetkili personelin hassas verilere ve işlevlere erişebilmesini sağlar.
En İyi Uygulamalar
JavaScript Decorator'larını kullanırken aşağıdaki en iyi uygulamaları göz önünde bulundurun:
- Decorator'ları Basit Tutun: Decorator'lar odaklanmış olmalı ve tek, iyi tanımlanmış bir görevi yerine getirmelidir. Okunabilirliği ve sürdürülebilirliği korumak için decorator'lar içinde karmaşık mantıktan kaçının.
- Decorator Fabrikaları Kullanın: Yapılandırılabilir decorator'lara izin vermek için decorator fabrikaları kullanın. Bu, decorator'larınızı daha esnek ve yeniden kullanılabilir hale getirir.
- Yan Etkilerden Kaçının: Decorator'lar öncelikle dekore edilen elemanı değiştirmeye veya onunla üst veri ilişkilendirmeye odaklanmalıdır. Kodunuzu anlamayı ve hata ayıklamayı zorlaştırabilecek karmaşık yan etkileri decorator'lar içinde gerçekleştirmekten kaçının.
- TypeScript Kullanın: TypeScript, tür denetimi ve IntelliSense dahil olmak üzere decorator'lar için mükemmel destek sağlar. TypeScript kullanmak, hataları erken yakalamanıza ve geliştirme deneyiminizi iyileştirmenize yardımcı olabilir.
- Decorator'larınızı Belgeleyin: Decorator'larınızın amacını ve nasıl kullanılması gerektiğini açıklamak için onları net bir şekilde belgeleyin. Bu, diğer geliştiricilerin decorator'larınızı doğru bir şekilde anlamasını ve kullanmasını kolaylaştırır.
- Performansı Göz Önünde Bulundurun: Decorator'lar güçlü olsalar da performansı da etkileyebilirler. Özellikle performansa duyarlı uygulamalarda decorator'larınızın performans üzerindeki etkilerine dikkat edin.
Decorator'lar ile Uluslararasılaştırma Örnekleri
Decorator'lar, yerel ayara özgü verileri ve davranışları kod bileşenleriyle ilişkilendirerek uluslararasılaştırma (i18n) ve yerelleştirme (l10n) konularına yardımcı olabilir:
Örnek: Yerelleştirilmiş Tarih Biçimlendirme
import 'reflect-metadata';
interface DateFormatOptions {
locale: string;
options?: Intl.DateTimeFormatOptions;
}
const dateFormatKey = 'dateFormat';
function formatDate(options: DateFormatOptions) {
return function(target: any, propertyKey: string) {
Reflect.defineMetadata(dateFormatKey, options, target, propertyKey);
};
}
class Event {
@formatDate({ locale: 'fr-FR', options: { year: 'numeric', month: 'long', day: 'numeric' } })
startDate: Date;
constructor(startDate: Date) {
this.startDate = startDate;
}
getFormattedStartDate(): string {
const options: DateFormatOptions = Reflect.getMetadata(dateFormatKey, Object.getPrototypeOf(this), 'startDate');
return this.startDate.toLocaleDateString(options.locale, options.options);
}
}
const event = new Event(new Date());
console.log(event.getFormattedStartDate()); // Tarihi Fransızca formatında yazdırır
Örnek: Kullanıcı Konumuna Göre Para Birimi Biçimlendirme
import 'reflect-metadata';
interface CurrencyFormatOptions {
locale: string;
currency: string;
}
const currencyFormatKey = 'currencyFormat';
function formatCurrency(options: CurrencyFormatOptions) {
return function(target: any, propertyKey: string) {
Reflect.defineMetadata(currencyFormatKey, options, target, propertyKey);
};
}
class Product {
@formatCurrency({ locale: 'de-DE', currency: 'EUR' })
price: number;
constructor(price: number) {
this.price = price;
}
getFormattedPrice(): string {
const options: CurrencyFormatOptions = Reflect.getMetadata(currencyFormatKey, Object.getPrototypeOf(this), 'price');
return this.price.toLocaleString(options.locale, { style: 'currency', currency: options.currency });
}
}
const product = new Product(99.99);
console.log(product.getFormattedPrice()); // Fiyatı Alman Euro formatında yazdırır
Geleceğe Yönelik Değerlendirmeler
JavaScript decorator'ları gelişmekte olan bir özelliktir ve standart hala geliştirme aşamasındadır. Geleceğe yönelik bazı değerlendirmeler şunları içerir:
- Standardizasyon: Decorator'lar için ECMAScript standardı hala devam etmektedir. Standart geliştikçe, decorator'ların söz diziminde ve davranışında değişiklikler olabilir.
- Performans Optimizasyonu: Decorator'lar daha yaygın kullanıldıkça, uygulama performansını olumsuz etkilemediklerinden emin olmak için performans optimizasyonlarına ihtiyaç duyulacaktır.
- Araç Desteği: IDE entegrasyonu ve hata ayıklama araçları gibi decorator'lar için geliştirilmiş araç desteği, geliştiricilerin decorator'ları etkili bir şekilde kullanmasını kolaylaştıracaktır.
Sonuç
JavaScript Decorator'ları, üst veri programlamayı uygulamak ve kodunuzun davranışını geliştirmek için güçlü bir araçtır. Decorator'ları kullanarak, temiz, bildirimsel ve yeniden kullanılabilir bir şekilde işlevsellik ekleyebilirsiniz. Bu, daha sürdürülebilir, test edilebilir ve ölçeklenebilir bir koda yol açar. Farklı decorator türlerini ve bunları etkili bir şekilde nasıl kullanacağınızı anlamak, modern JavaScript geliştirmesi için esastır. Decorator'lar, özellikle Reflect Metadata API ile birleştirildiğinde, bağımlılık enjeksiyonu ve doğrulamadan serileştirme ve yönlendirmeye kadar bir dizi olasılığın kilidini açarak kodunuzu daha anlamlı ve yönetimi daha kolay hale getirir.