मेटाडेटा प्रोग्रामिंग, एस्पेक्ट-ओरिएंटेड प्रोग्रामिंग, और घोषणात्मक पैटर्न के साथ कोड को बेहतर बनाने के लिए टाइपस्क्रिप्ट डेकोरेटर्स की शक्ति का अन्वेषण करें। वैश्विक डेवलपर्स के लिए एक व्यापक गाइड।
टाइपस्क्रिप्ट डेकोरेटर्स: मजबूत एप्लिकेशन के लिए मेटाडेटा प्रोग्रामिंग पैटर्न में महारत हासिल करना
आधुनिक सॉफ्टवेयर विकास के विशाल परिदृश्य में, स्वच्छ, स्केलेबल और प्रबंधनीय कोडबेस बनाए रखना सर्वोपरि है। टाइपस्क्रिप्ट, अपने शक्तिशाली टाइप सिस्टम और उन्नत सुविधाओं के साथ, डेवलपर्स को इसे प्राप्त करने के लिए उपकरण प्रदान करता है। इसकी सबसे दिलचस्प और परिवर्तनकारी विशेषताओं में से एक डेकोरेटर्स हैं। यद्यपि लिखते समय यह अभी भी एक प्रायोगिक सुविधा है (ECMAScript के लिए स्टेज 3 प्रस्ताव), डेकोरेटर्स का व्यापक रूप से एंगुलर और टाइपओआरएम जैसे फ्रेमवर्क में उपयोग किया जाता है, जो हमारे डिजाइन पैटर्न, मेटाडेटा प्रोग्रामिंग और एस्पेक्ट-ओरिएंटेड प्रोग्रामिंग (AOP) के दृष्टिकोण को मौलिक रूप से बदल देता है।
यह व्यापक गाइड टाइपस्क्रिप्ट डेकोरेटर्स में गहराई से उतरेगा, उनके यांत्रिकी, विभिन्न प्रकारों, व्यावहारिक अनुप्रयोगों और सर्वोत्तम प्रथाओं की खोज करेगा। चाहे आप बड़े पैमाने पर एंटरप्राइज एप्लिकेशन, माइक्रोसेवाइस, या क्लाइंट-साइड वेब इंटरफेस बना रहे हों, डेकोरेटर्स को समझना आपको अधिक घोषणात्मक, रखरखाव योग्य और शक्तिशाली टाइपस्क्रिप्ट कोड लिखने में सशक्त करेगा।
मूल अवधारणा को समझना: डेकोरेटर क्या है?
संक्षेप में, एक डेकोरेटर एक विशेष प्रकार की घोषणा है जिसे क्लास घोषणा, मेथड, एक्सेसर, प्रॉपर्टी, या पैरामीटर से जोड़ा जा सकता है। डेकोरेटर्स फ़ंक्शन होते हैं जो उस लक्ष्य के लिए एक नया मान लौटाते हैं (या मौजूदा मान को संशोधित करते हैं) जिसे वे डेकोरेट कर रहे हैं। उनका प्राथमिक उद्देश्य अंतर्निहित कोड संरचना को सीधे संशोधित किए बिना, जिस घोषणा से वे जुड़े हैं, उसमें मेटाडेटा जोड़ना या उसके व्यवहार को बदलना है। कोड को बढ़ाने का यह बाहरी, घोषणात्मक तरीका अविश्वसनीय रूप से शक्तिशाली है।
डेकोरेटर्स को एनोटेशन या लेबल के रूप में सोचें जिन्हें आप अपने कोड के कुछ हिस्सों पर लागू करते हैं। इन लेबलों को फिर आपके एप्लिकेशन के अन्य हिस्सों या फ्रेमवर्क द्वारा पढ़ा या उन पर कार्य किया जा सकता है, अक्सर रनटाइम पर, अतिरिक्त कार्यक्षमता या कॉन्फ़िगरेशन प्रदान करने के लिए।
डेकोरेटर का सिंटैक्स
डेकोरेटर्स को @
चिह्न के साथ उपसर्ग किया जाता है, जिसके बाद डेकोरेटर फ़ंक्शन का नाम होता है। उन्हें उस घोषणा के ठीक पहले रखा जाता है जिसे वे डेकोरेट कर रहे हैं।
@MyDecorator
class MyClass {
@AnotherDecorator
myMethod() {
// ...
}
}
टाइपस्क्रिप्ट में डेकोरेटर्स को सक्षम करना
डेकोरेटर्स का उपयोग करने से पहले, आपको अपनी tsconfig.json
फ़ाइल में experimentalDecorators
कंपाइलर विकल्प को सक्षम करना होगा। इसके अतिरिक्त, उन्नत मेटाडेटा रिफ्लेक्शन क्षमताओं (जो अक्सर फ्रेमवर्क द्वारा उपयोग की जाती हैं) के लिए, आपको emitDecoratorMetadata
और reflect-metadata
पॉलीफ़िल की भी आवश्यकता होगी।
// tsconfig.json
{
"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
आपको reflect-metadata
भी इंस्टॉल करना होगा:
npm install reflect-metadata --save
# या
yarn add reflect-metadata
और इसे अपने एप्लिकेशन के एंट्री पॉइंट (जैसे, main.ts
या app.ts
) के बिल्कुल शीर्ष पर इम्पोर्ट करें:
import "reflect-metadata";
// आपका एप्लिकेशन कोड इसके बाद आता है
डेकोरेटर फैक्ट्रियाँ: आपकी उंगलियों पर अनुकूलन
जबकि एक मूल डेकोरेटर एक फ़ंक्शन है, अक्सर आपको इसके व्यवहार को कॉन्फ़िगर करने के लिए एक डेकोरेटर को आर्ग्यूमेंट्स पास करने की आवश्यकता होगी। यह एक डेकोरेटर फैक्ट्री का उपयोग करके प्राप्त किया जाता है। एक डेकोरेटर फैक्ट्री एक फ़ंक्शन है जो वास्तविक डेकोरेटर फ़ंक्शन लौटाता है। जब आप एक डेकोरेटर फैक्ट्री लागू करते हैं, तो आप इसे इसके आर्ग्यूमेंट्स के साथ कॉल करते हैं, और फिर यह वह डेकोरेटर फ़ंक्शन लौटाता है जिसे टाइपस्क्रिप्ट आपके कोड पर लागू करता है।
एक सरल डेकोरेटर फैक्ट्री का उदाहरण बनाना
आइए एक Logger
डेकोरेटर के लिए एक फैक्ट्री बनाते हैं जो विभिन्न उपसर्गों के साथ संदेशों को लॉग कर सकती है।
function Logger(prefix: string) {
return function (target: Function) {
console.log(`[${prefix}] Class ${target.name} has been defined.`);
};
}
@Logger("APP_INIT")
class ApplicationBootstrap {
constructor() {
console.log("Application is starting...");
}
}
const app = new ApplicationBootstrap();
// आउटपुट:
// [APP_INIT] Class ApplicationBootstrap has been defined.
// Application is starting...
इस उदाहरण में, Logger("APP_INIT")
डेकोरेटर फैक्ट्री कॉल है। यह वास्तविक डेकोरेटर फ़ंक्शन लौटाता है जो अपने आर्ग्यूमेंट के रूप में target: Function
(क्लास कंस्ट्रक्टर) लेता है। यह डेकोरेटर के व्यवहार के गतिशील कॉन्फ़िगरेशन की अनुमति देता है।
टाइपस्क्रिप्ट में डेकोरेटर्स के प्रकार
टाइपस्क्रिप्ट पांच अलग-अलग प्रकार के डेकोरेटर्स का समर्थन करता है, जिनमें से प्रत्येक एक विशिष्ट प्रकार की घोषणा पर लागू होता है। डेकोरेटर फ़ंक्शन का सिग्नेचर उस संदर्भ के आधार पर भिन्न होता है जिसमें इसे लागू किया जाता है।
1. क्लास डेकोरेटर्स
क्लास डेकोरेटर्स को क्लास घोषणाओं पर लागू किया जाता है। डेकोरेटर फ़ंक्शन क्लास के कंस्ट्रक्टर को अपने एकमात्र आर्ग्यूमेंट के रूप में प्राप्त करता है। एक क्लास डेकोरेटर एक क्लास परिभाषा का निरीक्षण, संशोधन, या उसे प्रतिस्थापित भी कर सकता है।
सिग्नेचर:
function ClassDecorator(target: Function) { ... }
रिटर्न वैल्यू:
यदि क्लास डेकोरेटर एक मान लौटाता है, तो यह क्लास घोषणा को प्रदान किए गए कंस्ट्रक्टर फ़ंक्शन से बदल देगा। यह एक शक्तिशाली सुविधा है, जिसका उपयोग अक्सर मिक्सइन्स या क्लास ऑग्मेंटेशन के लिए किया जाता है। यदि कोई मान नहीं लौटाया जाता है, तो मूल क्लास का उपयोग किया जाता है।
उपयोग के मामले:
- डिपेंडेंसी इंजेक्शन कंटेनर में क्लास को पंजीकृत करना।
- एक क्लास में मिक्सइन्स या अतिरिक्त कार्यक्षमताएँ लागू करना।
- फ्रेमवर्क-विशिष्ट कॉन्फ़िगरेशन (जैसे, एक वेब फ्रेमवर्क में रूटिंग)।
- क्लासेस में लाइफसाइकिल हुक जोड़ना।
क्लास डेकोरेटर का उदाहरण: एक सर्विस को इंजेक्ट करना
एक सरल डिपेंडेंसी इंजेक्शन परिदृश्य की कल्पना करें जहां आप एक क्लास को "इंजेक्टेबल" के रूप में चिह्नित करना चाहते हैं और वैकल्पिक रूप से इसे एक कंटेनर में एक नाम प्रदान करना चाहते हैं।
const InjectableServiceRegistry = new Map<string, Function>();
function Injectable(name?: string) {
return function<T extends { new(...args: any[]): {} }>(constructor: T) {
const serviceName = name || constructor.name;
InjectableServiceRegistry.set(serviceName, constructor);
console.log(`Registered service: ${serviceName}`);
// वैकल्पिक रूप से, आप व्यवहार को बढ़ाने के लिए यहां एक नई क्लास लौटा सकते हैं
return class extends constructor {
createdAt = new Date();
// सभी इंजेक्ट की गई सेवाओं के लिए अतिरिक्त गुण या विधियाँ
};
};
}
@Injectable("UserService")
class UserDataService {
getUsers() {
return [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
}
}
@Injectable()
class ProductDataService {
getProducts() {
return [{ id: 101, name: "Laptop" }, { id: 102, name: "Mouse" }];
}
}
console.log("--- Services Registered ---");
console.log(Array.from(InjectableServiceRegistry.keys()));
const userServiceConstructor = InjectableServiceRegistry.get("UserService");
if (userServiceConstructor) {
const userServiceInstance = new userServiceConstructor();
console.log("Users:", userServiceInstance.getUsers());
// console.log("User Service Created At:", userServiceInstance.createdAt); // यदि लौटाई गई क्लास का उपयोग किया जाता है
}
यह उदाहरण दिखाता है कि कैसे एक क्लास डेकोरेटर एक क्लास को पंजीकृत कर सकता है और उसके कंस्ट्रक्टर को भी संशोधित कर सकता है। Injectable
डेकोरेटर क्लास को एक सैद्धांतिक डिपेंडेंसी इंजेक्शन सिस्टम द्वारा खोजने योग्य बनाता है।
2. मेथड डेकोरेटर्स
मेथड डेकोरेटर्स को मेथड घोषणाओं पर लागू किया जाता है। वे तीन आर्ग्यूमेंट्स प्राप्त करते हैं: लक्ष्य ऑब्जेक्ट (स्टैटिक सदस्यों के लिए, कंस्ट्रक्टर फ़ंक्शन; इंस्टेंस सदस्यों के लिए, क्लास का प्रोटोटाइप), मेथड का नाम, और मेथड का प्रॉपर्टी डिस्क्रिप्टर।
सिग्नेचर:
function MethodDecorator(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) { ... }
रिटर्न वैल्यू:
एक मेथड डेकोरेटर एक नया PropertyDescriptor
लौटा सकता है। यदि ऐसा होता है, तो इस डिस्क्रिप्टर का उपयोग मेथड को परिभाषित करने के लिए किया जाएगा। यह आपको मूल मेथड के कार्यान्वयन को संशोधित करने या बदलने की अनुमति देता है, जिससे यह AOP के लिए अविश्वसनीय रूप से शक्तिशाली बन जाता है।
उपयोग के मामले:
- मेथड कॉल और उनके आर्ग्यूमेंट्स/परिणामों को लॉग करना।
- प्रदर्शन में सुधार के लिए मेथड परिणामों को कैश करना।
- मेथड निष्पादन से पहले प्राधिकरण जांच लागू करना।
- मेथड निष्पादन समय को मापना।
- मेथड कॉल को डिबाउंसिंग या थ्रॉटलिंग करना।
मेथड डेकोरेटर का उदाहरण: प्रदर्शन की निगरानी
आइए एक मेथड के निष्पादन समय को लॉग करने के लिए एक MeasurePerformance
डेकोरेटर बनाते हैं।
function MeasurePerformance(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const start = process.hrtime.bigint();
const result = originalMethod.apply(this, args);
const end = process.hrtime.bigint();
const duration = Number(end - start) / 1_000_000;
console.log(`Method "${propertyKey}" executed in ${duration.toFixed(2)} ms`);
return result;
};
return descriptor;
}
class DataProcessor {
@MeasurePerformance
processData(data: number[]): number[] {
// एक जटिल, समय लेने वाली प्रक्रिया का अनुकरण करें
for (let i = 0; i < 1_000_000; i++) {
Math.sin(i);
}
return data.map(n => n * 2);
}
@MeasurePerformance
fetchRemoteData(id: string): Promise<string> {
return new Promise(resolve => {
setTimeout(() => {
resolve(`Data for ID: ${id}`);
}, 500);
});
}
}
const processor = new DataProcessor();
processor.processData([1, 2, 3]);
processor.fetchRemoteData("abc").then(result => console.log(result));
MeasurePerformance
डेकोरेटर मूल मेथड को टाइमिंग लॉजिक के साथ लपेटता है, मेथड के भीतर व्यावसायिक तर्क को अव्यवस्थित किए बिना निष्पादन अवधि को प्रिंट करता है। यह एस्पेक्ट-ओरिएंटेड प्रोग्रामिंग (AOP) का एक क्लासिक उदाहरण है।
3. एक्सेसर डेकोरेटर्स
एक्सेसर डेकोरेटर्स को एक्सेसर (get
और set
) घोषणाओं पर लागू किया जाता है। मेथड डेकोरेटर्स के समान, वे लक्ष्य ऑब्जेक्ट, एक्सेसर का नाम और उसका प्रॉपर्टी डिस्क्रिप्टर प्राप्त करते हैं।
सिग्नेचर:
function AccessorDecorator(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) { ... }
रिटर्न वैल्यू:
एक एक्सेसर डेकोरेटर एक नया PropertyDescriptor
लौटा सकता है, जिसका उपयोग एक्सेसर को परिभाषित करने के लिए किया जाएगा।
उपयोग के मामले:
- एक प्रॉपर्टी सेट करते समय सत्यापन।
- एक मान को सेट करने से पहले या पुनर्प्राप्त करने के बाद उसे बदलना।
- प्रॉपर्टीज के लिए एक्सेस अनुमतियों को नियंत्रित करना।
एक्सेसर डेकोरेटर का उदाहरण: गेटर्स को कैश करना
आइए एक डेकोरेटर बनाते हैं जो एक महंगे गेटर गणना के परिणाम को कैश करता है।
function CachedGetter(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalGetter = descriptor.get;
const cacheKey = `_cached_${String(propertyKey)}`;
if (originalGetter) {
descriptor.get = function() {
if (this[cacheKey] === undefined) {
console.log(`[Cache Miss] Computing value for ${String(propertyKey)}`);
this[cacheKey] = originalGetter.apply(this);
} else {
console.log(`[Cache Hit] Using cached value for ${String(propertyKey)}`);
}
return this[cacheKey];
};
}
return descriptor;
}
class ReportGenerator {
private data: number[];
constructor(data: number[]) {
this.data = data;
}
// एक महंगी गणना का अनुकरण करता है
@CachedGetter
get expensiveSummary(): number {
console.log("Performing expensive summary calculation...");
return this.data.reduce((sum, current) => sum + current, 0) / this.data.length;
}
}
const generator = new ReportGenerator([10, 20, 30, 40, 50]);
console.log("First access:", generator.expensiveSummary);
console.log("Second access:", generator.expensiveSummary);
console.log("Third access:", generator.expensiveSummary);
यह डेकोरेटर सुनिश्चित करता है कि expensiveSummary
गेटर की गणना केवल एक बार चलती है, बाद की कॉलें कैश किए गए मान को लौटाती हैं। यह पैटर्न प्रदर्शन को अनुकूलित करने के लिए बहुत उपयोगी है जहां प्रॉपर्टी एक्सेस में भारी गणना या बाहरी कॉल शामिल होते हैं।
4. प्रॉपर्टी डेकोरेटर्स
प्रॉपर्टी डेकोरेटर्स को प्रॉपर्टी घोषणाओं पर लागू किया जाता है। वे दो आर्ग्यूमेंट्स प्राप्त करते हैं: लक्ष्य ऑब्जेक्ट (स्टैटिक सदस्यों के लिए, कंस्ट्रक्टर फ़ंक्शन; इंस्टेंस सदस्यों के लिए, क्लास का प्रोटोटाइप), और प्रॉपर्टी का नाम।
सिग्नेचर:
function PropertyDecorator(target: Object, propertyKey: string | symbol) { ... }
रिटर्न वैल्यू:
प्रॉपर्टी डेकोरेटर्स कोई मान नहीं लौटा सकते हैं। उनका प्राथमिक उपयोग प्रॉपर्टी के बारे में मेटाडेटा पंजीकृत करना है। वे सीधे प्रॉपर्टी के मान या उसके डिस्क्रिप्टर को डेकोरेशन के समय नहीं बदल सकते हैं, क्योंकि एक प्रॉपर्टी के लिए डिस्क्रिप्टर अभी तक पूरी तरह से परिभाषित नहीं होता है जब प्रॉपर्टी डेकोरेटर्स चलते हैं।
उपयोग के मामले:
- सीरियलाइज़ेशन/डी-सीरियलाइज़ेशन के लिए प्रॉपर्टीज पंजीकृत करना।
- प्रॉपर्टीज पर सत्यापन नियम लागू करना।
- प्रॉपर्टीज के लिए डिफ़ॉल्ट मान या कॉन्फ़िगरेशन सेट करना।
- ORM (ऑब्जेक्ट-रिलेशनल मैपिंग) कॉलम मैपिंग (जैसे, टाइपओआरएम में
@Column()
)।
प्रॉपर्टी डेकोरेटर का उदाहरण: आवश्यक फ़ील्ड सत्यापन
आइए एक प्रॉपर्टी को "आवश्यक" के रूप में चिह्नित करने और फिर रनटाइम पर इसे मान्य करने के लिए एक डेकोरेटर बनाते हैं।
interface ValidationRule {
property: string | symbol;
validate: (value: any) => boolean;
message: string;
}
const validationRules: Map<Function, ValidationRule[]> = new Map();
function Required(target: Object, propertyKey: string | symbol) {
const rules = validationRules.get(target.constructor) || [];
rules.push({
property: propertyKey,
validate: (value: any) => value !== null && value !== undefined && value !== "",
message: `${String(propertyKey)} is required.`
});
validationRules.set(target.constructor, rules);
}
function validate(instance: any): string[] {
const classRules = validationRules.get(instance.constructor) || [];
const errors: string[] = [];
for (const rule of classRules) {
if (!rule.validate(instance[rule.property])) {
errors.push(rule.message);
}
}
return errors;
}
class UserProfile {
@Required
firstName: string;
@Required
lastName: string;
age?: number;
constructor(firstName: string, lastName: string, age?: number) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
}
const user1 = new UserProfile("John", "Doe", 30);
console.log("User 1 validation errors:", validate(user1)); // []
const user2 = new UserProfile("", "Smith");
console.log("User 2 validation errors:", validate(user2)); // ["firstName is required."]
const user3 = new UserProfile("Alice", "");
console.log("User 3 validation errors:", validate(user3)); // ["lastName is required."]
Required
डेकोरेटर बस सत्यापन नियम को एक केंद्रीय validationRules
मैप के साथ पंजीकृत करता है। एक अलग validate
फ़ंक्शन फिर इस मेटाडेटा का उपयोग रनटाइम पर इंस्टेंस की जांच करने के लिए करता है। यह पैटर्न सत्यापन तर्क को डेटा परिभाषा से अलग करता है, जिससे यह पुन: प्रयोज्य और स्वच्छ हो जाता है।
5. पैरामीटर डेकोरेटर्स
पैरामीटर डेकोरेटर्स को एक क्लास कंस्ट्रक्टर या एक मेथड के भीतर पैरामीटर्स पर लागू किया जाता है। वे तीन आर्ग्यूमेंट्स प्राप्त करते हैं: लक्ष्य ऑब्जेक्ट (स्टैटिक सदस्यों के लिए, कंस्ट्रक्टर फ़ंक्शन; इंस्टेंस सदस्यों के लिए, क्लास का प्रोटोटाइप), मेथड का नाम (या कंस्ट्रक्टर पैरामीटर्स के लिए undefined
), और फ़ंक्शन की पैरामीटर सूची में पैरामीटर का क्रमिक सूचकांक।
सिग्नेचर:
function ParameterDecorator(target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) { ... }
रिटर्न वैल्यू:
पैरामीटर डेकोरेटर्स कोई मान नहीं लौटा सकते हैं। प्रॉपर्टी डेकोरेटर्स की तरह, उनकी प्राथमिक भूमिका पैरामीटर के बारे में मेटाडेटा जोड़ना है।
उपयोग के मामले:
- डिपेंडेंसी इंजेक्शन के लिए पैरामीटर प्रकार पंजीकृत करना (जैसे, एंगुलर में
@Inject()
)। - विशिष्ट पैरामीटर्स पर सत्यापन या परिवर्तन लागू करना।
- वेब फ्रेमवर्क में API अनुरोध पैरामीटर्स के बारे में मेटाडेटा निकालना।
पैरामीटर डेकोरेटर का उदाहरण: अनुरोध डेटा इंजेक्ट करना
आइए अनुकरण करते हैं कि कैसे एक वेब फ्रेमवर्क पैरामीटर डेकोरेटर्स का उपयोग करके एक मेथड पैरामीटर में विशिष्ट डेटा इंजेक्ट कर सकता है, जैसे कि अनुरोध से एक उपयोगकर्ता आईडी।
interface ParameterMetadata {
index: number;
key: string | symbol;
resolver: (request: any) => any;
}
const parameterResolvers: Map<Function, Map<string | symbol, ParameterMetadata[]>> = new Map();
function RequestParam(paramName: string) {
return function (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) {
const targetKey = propertyKey || "constructor";
let methodResolvers = parameterResolvers.get(target.constructor);
if (!methodResolvers) {
methodResolvers = new Map();
parameterResolvers.set(target.constructor, methodResolvers);
}
const paramMetadata = methodResolvers.get(targetKey) || [];
paramMetadata.push({
index: parameterIndex,
key: targetKey,
resolver: (request: any) => request[paramName]
});
methodResolvers.set(targetKey, paramMetadata);
};
}
// सुलझाए गए पैरामीटर के साथ एक मेथड को लागू करने के लिए एक काल्पनिक फ्रेमवर्क फ़ंक्शन
function executeWithParams(instance: any, methodName: string, request: any) {
const classResolvers = parameterResolvers.get(instance.constructor);
if (!classResolvers) {
return (instance[methodName] as Function).apply(instance, []);
}
const methodParamMetadata = classResolvers.get(methodName);
if (!methodParamMetadata) {
return (instance[methodName] as Function).apply(instance, []);
}
const args: any[] = Array(methodParamMetadata.length);
for (const meta of methodParamMetadata) {
args[meta.index] = meta.resolver(request);
}
return (instance[methodName] as Function).apply(instance, args);
}
class UserController {
getUser(@RequestParam("id") userId: string, @RequestParam("token") authToken?: string) {
console.log(`Fetching user with ID: ${userId}, Token: ${authToken || "N/A"}`);
return { id: userId, name: "Jane Doe" };
}
deleteUser(@RequestParam("id") userId: string) {
console.log(`Deleting user with ID: ${userId}`);
return { status: "deleted", id: userId };
}
}
const userController = new UserController();
// एक आने वाले अनुरोध का अनुकरण करें
const mockRequest = {
id: "user123",
token: "abc-123",
someOtherProp: "xyz"
};
console.log("
--- Executing getUser ---");
executeWithParams(userController, "getUser", mockRequest);
console.log("
--- Executing deleteUser ---");
executeWithParams(userController, "deleteUser", { id: "user456" });
यह उदाहरण दिखाता है कि कैसे पैरामीटर डेकोरेटर्स आवश्यक मेथड पैरामीटर्स के बारे में जानकारी एकत्र कर सकते हैं। एक फ्रेमवर्क फिर इस एकत्रित मेटाडेटा का उपयोग करके मेथड को कॉल किए जाने पर स्वचालित रूप से उपयुक्त मानों को हल और इंजेक्ट कर सकता है, जिससे कंट्रोलर या सर्विस लॉजिक काफी सरल हो जाता है।
डेकोरेटर संरचना और निष्पादन क्रम
डेकोरेटर्स को विभिन्न संयोजनों में लागू किया जा सकता है, और उनके निष्पादन क्रम को समझना व्यवहार की भविष्यवाणी करने और अप्रत्याशित मुद्दों से बचने के लिए महत्वपूर्ण है।
एकल लक्ष्य पर एकाधिक डेकोरेटर्स
जब एक ही घोषणा (जैसे, एक क्लास, मेथड, या प्रॉपर्टी) पर कई डेकोरेटर्स लागू किए जाते हैं, तो वे एक विशिष्ट क्रम में निष्पादित होते हैं: उनके मूल्यांकन के लिए नीचे से ऊपर, या दाएं से बाएं। हालांकि, उनके परिणाम विपरीत क्रम में लागू होते हैं।
@DecoratorA
@DecoratorB
class MyClass {
// ...
}
यहां, DecoratorB
का मूल्यांकन पहले किया जाएगा, फिर DecoratorA
का। यदि वे क्लास को संशोधित करते हैं (जैसे, एक नया कंस्ट्रक्टर लौटाकर), तो DecoratorA
से संशोधन DecoratorB
से संशोधन के ऊपर लागू होगा।
उदाहरण: मेथड डेकोरेटर्स को श्रृंखलाबद्ध करना
दो मेथड डेकोरेटर्स पर विचार करें: LogCall
और Authorization
।
function LogCall(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`[LOG] Calling ${String(propertyKey)} with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`[LOG] Method ${String(propertyKey)} returned:`, result);
return result;
};
return descriptor;
}
function Authorization(roles: string[]) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const currentUserRoles = ["admin"]; // वर्तमान उपयोगकर्ता भूमिकाओं को लाने का अनुकरण करें
const authorized = roles.some(role => currentUserRoles.includes(role));
if (!authorized) {
console.warn(`[AUTH] Access denied for ${String(propertyKey)}. Required roles: ${roles.join(", ")}`);
throw new Error("Unauthorized access");
}
console.log(`[AUTH] Access granted for ${String(propertyKey)}`);
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class SecureService {
@LogCall
@Authorization(["admin"])
deleteSensitiveData(id: string) {
console.log(`Deleting sensitive data for ID: ${id}`);
return `Data ID ${id} deleted.`;
}
@Authorization(["user"])
@LogCall // यहां क्रम बदल गया है
fetchPublicData(query: string) {
console.log(`Fetching public data with query: ${query}`);
return `Public data for query: ${query}`;
}
}
const service = new SecureService();
try {
console.log("
--- Calling deleteSensitiveData (Admin User) ---");
service.deleteSensitiveData("record123");
} catch (error: any) {
console.error(error.message);
}
try {
console.log("
--- Calling fetchPublicData (Non-Admin User) ---");
// एक गैर-व्यवस्थापक उपयोगकर्ता का अनुकरण करें जो fetchPublicData तक पहुंचने का प्रयास कर रहा है जिसके लिए 'user' भूमिका की आवश्यकता है
const mockUserRoles = ["guest"]; // यह प्रमाणीकरण विफल हो जाएगा
// इसे गतिशील बनाने के लिए, आपको वर्तमान उपयोगकर्ता भूमिकाओं के लिए DI सिस्टम या स्थिर संदर्भ की आवश्यकता होगी।
// सरलता के लिए, हम मानते हैं कि Authorization डेकोरेटर के पास वर्तमान उपयोगकर्ता संदर्भ तक पहुंच है।
// चलिए प्रदर्शन उद्देश्यों के लिए Authorization डेकोरेटर को हमेशा 'admin' मानने के लिए समायोजित करते हैं,
// ताकि पहली कॉल सफल हो और दूसरी विफल हो ताकि विभिन्न पथ दिखाए जा सकें।
// fetchPublicData के सफल होने के लिए उपयोगकर्ता भूमिका के साथ फिर से चलाएं।
// कल्पना कीजिए कि Authorization में currentUserRoles बन जाता है: ['user']
// इस उदाहरण के लिए, आइए इसे सरल रखें और क्रम प्रभाव दिखाएं।
service.fetchPublicData("search term"); // यह Auth -> Log निष्पादित करेगा
} catch (error: any) {
console.error(error.message);
}
/* deleteSensitiveData के लिए अपेक्षित आउटपुट:
[AUTH] Access granted for deleteSensitiveData
[LOG] Calling deleteSensitiveData with args: [ 'record123' ]
Deleting sensitive data for ID: record123
[LOG] Method deleteSensitiveData returned: Data ID record123 deleted.
*/
/* fetchPublicData के लिए अपेक्षित आउटपुट (यदि उपयोगकर्ता के पास 'user' भूमिका है):
[LOG] Calling fetchPublicData with args: [ 'search term' ]
[AUTH] Access granted for fetchPublicData
Fetching public data with query: search term
[LOG] Method fetchPublicData returned: Public data for query: search term
*/
क्रम पर ध्यान दें: deleteSensitiveData
के लिए, Authorization
(नीचे) पहले चलता है, फिर LogCall
(ऊपर) इसके चारों ओर लपेटता है। Authorization
का आंतरिक तर्क पहले निष्पादित होता है। fetchPublicData
के लिए, LogCall
(नीचे) पहले चलता है, फिर Authorization
(ऊपर) इसके चारों ओर लपेटता है। इसका मतलब है कि LogCall
पहलू Authorization
पहलू के बाहर होगा। यह अंतर लॉगिंग या त्रुटि प्रबंधन जैसी क्रॉस-कटिंग चिंताओं के लिए महत्वपूर्ण है, जहां निष्पादन का क्रम व्यवहार को महत्वपूर्ण रूप से प्रभावित कर सकता है।
विभिन्न लक्ष्यों के लिए निष्पादन क्रम
जब एक क्लास, उसके सदस्यों और पैरामीटर्स सभी में डेकोरेटर्स होते हैं, तो निष्पादन क्रम अच्छी तरह से परिभाषित होता है:
- पैरामीटर डेकोरेटर्स सबसे पहले लागू होते हैं, प्रत्येक पैरामीटर के लिए, अंतिम पैरामीटर से पहले तक।
- फिर, मेथड, एक्सेसर, या प्रॉपर्टी डेकोरेटर्स प्रत्येक सदस्य के लिए लागू होते हैं।
- अंत में, क्लास डेकोरेटर्स क्लास पर ही लागू होते हैं।
प्रत्येक श्रेणी के भीतर, एक ही लक्ष्य पर कई डेकोरेटर्स नीचे से ऊपर (या दाएं से बाएं) लागू होते हैं।
उदाहरण: पूर्ण निष्पादन क्रम
function log(message: string) {
return function (target: any, propertyKey: string | symbol | undefined, descriptorOrIndex?: PropertyDescriptor | number) {
if (typeof descriptorOrIndex === 'number') {
console.log(`Param Decorator: ${message} on parameter #${descriptorOrIndex} of ${String(propertyKey || "constructor")}`);
} else if (typeof propertyKey === 'string' || typeof propertyKey === 'symbol') {
if (descriptorOrIndex && 'value' in descriptorOrIndex && typeof descriptorOrIndex.value === 'function') {
console.log(`Method/Accessor Decorator: ${message} on ${String(propertyKey)}`);
} else {
console.log(`Property Decorator: ${message} on ${String(propertyKey)}`);
}
} else {
console.log(`Class Decorator: ${message} on ${target.name}`);
}
return descriptorOrIndex; // मेथड/एक्सेसर के लिए डिस्क्रिप्टर लौटाएं, दूसरों के लिए undefined
};
}
@log("Class Level D")
@log("Class Level C")
class MyDecoratedClass {
@log("Static Property A")
static staticProp: string = "";
@log("Instance Property B")
instanceProp: number = 0;
@log("Method D")
@log("Method C")
myMethod(
@log("Parameter Z") paramZ: string,
@log("Parameter Y") paramY: number
) {
console.log("Method myMethod executed.");
}
@log("Getter/Setter F")
get myAccessor() {
return "";
}
set myAccessor(value: string) {
//...
}
constructor() {
console.log("Constructor executed.");
}
}
new MyDecoratedClass();
// मेथड डेकोरेटर को ट्रिगर करने के लिए मेथड को कॉल करें
new MyDecoratedClass().myMethod("hello", 123);
/* अनुमानित आउटपुट क्रम (विशिष्ट टाइपस्क्रिप्ट संस्करण और संकलन के आधार पर अनुमानित):
Param Decorator: Parameter Y on parameter #1 of myMethod
Param Decorator: Parameter Z on parameter #0 of myMethod
Property Decorator: Static Property A on staticProp
Property Decorator: Instance Property B on instanceProp
Method/Accessor Decorator: Getter/Setter F on myAccessor
Method/Accessor Decorator: Method C on myMethod
Method/Accessor Decorator: Method D on myMethod
Class Decorator: Class Level C on MyDecoratedClass
Class Decorator: Class Level D on MyDecoratedClass
Constructor executed.
Method myMethod executed.
*/
सटीक कंसोल लॉग टाइमिंग थोड़ी भिन्न हो सकती है जो इस बात पर निर्भर करती है कि कंस्ट्रक्टर या मेथड कब लागू किया जाता है, लेकिन जिस क्रम में डेकोरेटर फ़ंक्शन स्वयं निष्पादित होते हैं (और इस प्रकार उनके साइड इफेक्ट्स या लौटाए गए मान लागू होते हैं) उपरोक्त नियमों का पालन करता है।
डेकोरेटर्स के साथ व्यावहारिक अनुप्रयोग और डिजाइन पैटर्न
डेकोरेटर्स, विशेष रूप से reflect-metadata
पॉलीफ़िल के संयोजन में, मेटाडेटा-संचालित प्रोग्रामिंग का एक नया क्षेत्र खोलते हैं। यह शक्तिशाली डिजाइन पैटर्न की अनुमति देता है जो बॉयलरप्लेट और क्रॉस-कटिंग चिंताओं को दूर करता है।
1. डिपेंडेंसी इंजेक्शन (DI)
डेकोरेटर्स के सबसे प्रमुख उपयोगों में से एक डिपेंडेंसी इंजेक्शन फ्रेमवर्क में है (जैसे एंगुलर का @Injectable()
, @Component()
, आदि, या NestJS का DI का व्यापक उपयोग)। डेकोरेटर्स आपको कंस्ट्रक्टर्स या प्रॉपर्टीज पर सीधे निर्भरता घोषित करने की अनुमति देते हैं, जिससे फ्रेमवर्क को स्वचालित रूप से सही सेवाओं को इंस्टेंटिएट करने और प्रदान करने में सक्षम बनाया जाता है।
उदाहरण: सरलीकृत सर्विस इंजेक्शन
import "reflect-metadata"; // emitDecoratorMetadata के लिए आवश्यक
const INJECTABLE_METADATA_KEY = Symbol("injectable");
const INJECT_METADATA_KEY = Symbol("inject");
function Injectable() {
return function (target: Function) {
Reflect.defineMetadata(INJECTABLE_METADATA_KEY, true, target);
};
}
function Inject(token: any) {
return function (target: Object, propertyKey: string | symbol, parameterIndex: number) {
const existingInjections: any[] = Reflect.getOwnMetadata(INJECT_METADATA_KEY, target, propertyKey) || [];
existingInjections[parameterIndex] = token;
Reflect.defineMetadata(INJECT_METADATA_KEY, existingInjections, target, propertyKey);
};
}
class Container {
private static instances = new Map<any, any>();
static resolve<T>(target: { new (...args: any[]): T }): T {
if (Container.instances.has(target)) {
return Container.instances.get(target);
}
const isInjectable = Reflect.getMetadata(INJECTABLE_METADATA_KEY, target);
if (!isInjectable) {
throw new Error(`Class ${target.name} is not marked as @Injectable.`);
}
// कंस्ट्रक्टर पैरामीटर्स के प्रकार प्राप्त करें (emitDecoratorMetadata की आवश्यकता है)
const paramTypes: any[] = Reflect.getMetadata("design:paramtypes", target) || [];
const explicitInjections: any[] = Reflect.getMetadata(INJECT_METADATA_KEY, target) || [];
const dependencies = paramTypes.map((paramType, index) => {
// यदि प्रदान किया गया हो तो स्पष्ट @Inject टोकन का उपयोग करें, अन्यथा प्रकार का अनुमान लगाएं
const token = explicitInjections[index] || paramType;
if (token === undefined) {
throw new Error(`Cannot resolve parameter at index ${index} for ${target.name}. It might be a circular dependency or primitive type without explicit @Inject.`);
}
return Container.resolve(token);
});
const instance = new target(...dependencies);
Container.instances.set(target, instance);
return instance;
}
}
// सेवाओं को परिभाषित करें
@Injectable()
class DatabaseService {
connect() {
console.log("Connecting to database...");
return "DB Connection";
}
}
@Injectable()
class AuthService {
private db: DatabaseService;
constructor(db: DatabaseService) {
this.db = db;
}
login() {
console.log(`AuthService: Authenticating using ${this.db.connect()}`);
return "User logged in";
}
}
@Injectable()
class UserService {
private authService: AuthService;
private dbService: DatabaseService; // एक कस्टम डेकोरेटर या फ्रेमवर्क सुविधा का उपयोग करके प्रॉपर्टी के माध्यम से इंजेक्ट करने का उदाहरण
constructor(@Inject(AuthService) authService: AuthService,
@Inject(DatabaseService) dbService: DatabaseService) {
this.authService = authService;
this.dbService = dbService;
}
getUserProfile() {
this.authService.login();
this.dbService.connect();
console.log("UserService: Fetching user profile...");
return { id: 1, name: "Global User" };
}
}
// मुख्य सेवा को हल करें
console.log("--- Resolving UserService ---");
const userService = Container.resolve(UserService);
console.log(userService.getUserProfile());
console.log("
--- Resolving AuthService (should be cached) ---");
const authService = Container.resolve(AuthService);
authService.login();
यह विस्तृत उदाहरण दिखाता है कि कैसे @Injectable
और @Inject
डेकोरेटर्स, reflect-metadata
के साथ मिलकर, एक कस्टम Container
को स्वचालित रूप से निर्भरता को हल करने और प्रदान करने की अनुमति देते हैं। टाइपस्क्रिप्ट द्वारा स्वचालित रूप से उत्सर्जित design:paramtypes
मेटाडेटा (जब emitDecoratorMetadata
सत्य हो) यहां महत्वपूर्ण है।
2. एस्पेक्ट-ओरिएंटेड प्रोग्रामिंग (AOP)
AOP क्रॉस-कटिंग चिंताओं (जैसे, लॉगिंग, सुरक्षा, लेनदेन) को मॉड्यूलर बनाने पर केंद्रित है जो कई क्लास और मॉड्यूल में व्याप्त हैं। डेकोरेटर्स टाइपस्क्रिप्ट में AOP अवधारणाओं को लागू करने के लिए एक उत्कृष्ट विकल्प हैं।
उदाहरण: मेथड डेकोरेटर के साथ लॉगिंग
LogCall
डेकोरेटर पर वापस जाते हुए, यह AOP का एक आदर्श उदाहरण है। यह किसी भी मेथड में उसके मूल कोड को संशोधित किए बिना लॉगिंग व्यवहार जोड़ता है। यह "क्या करना है" (व्यावसायिक तर्क) को "कैसे करना है" (लॉगिंग, प्रदर्शन निगरानी, आदि) से अलग करता है।
function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`[LOG AOP] Entering method: ${String(propertyKey)} with args:`, args);
try {
const result = originalMethod.apply(this, args);
console.log(`[LOG AOP] Exiting method: ${String(propertyKey)} with result:`, result);
return result;
} catch (error: any) {
console.error(`[LOG AOP] Error in method ${String(propertyKey)}:`, error.message);
throw error;
}
};
return descriptor;
}
class PaymentProcessor {
@LogMethod
processPayment(amount: number, currency: string) {
if (amount <= 0) {
throw new Error("Payment amount must be positive.");
}
console.log(`Processing payment of ${amount} ${currency}...`);
return `Payment of ${amount} ${currency} processed successfully.`;
}
@LogMethod
refundPayment(transactionId: string) {
console.log(`Refunding payment for transaction ID: ${transactionId}...`);
return `Refund initiated for ${transactionId}.`;
}
}
const processor = new PaymentProcessor();
processor.processPayment(100, "USD");
try {
processor.processPayment(-50, "EUR");
} catch (error: any) {
console.error("Caught error:", error.message);
}
यह दृष्टिकोण PaymentProcessor
क्लास को पूरी तरह से भुगतान तर्क पर केंद्रित रखता है, जबकि LogMethod
डेकोरेटर लॉगिंग की क्रॉस-कटिंग चिंता को संभालता है।
3. सत्यापन और परिवर्तन
डेकोरेटर्स सत्यापन नियमों को सीधे प्रॉपर्टीज पर परिभाषित करने या सीरियलाइज़ेशन/डी-सीरियलाइज़ेशन के दौरान डेटा को बदलने के लिए अविश्वसनीय रूप से उपयोगी हैं।
उदाहरण: प्रॉपर्टी डेकोरेटर्स के साथ डेटा सत्यापन
पहले का @Required
उदाहरण पहले ही इसे प्रदर्शित कर चुका है। यहां एक संख्यात्मक सीमा सत्यापन के साथ एक और उदाहरण है।
interface FieldValidationRule {
property: string | symbol;
validator: (value: any) => boolean;
message: string;
}
const fieldValidationRules = new Map<Function, FieldValidationRule[]>();
function addValidationRule(target: Object, propertyKey: string | symbol, validator: (value: any) => boolean, message: string) {
const rules = fieldValidationRules.get(target.constructor) || [];
rules.push({ property: propertyKey, validator, message });
fieldValidationRules.set(target.constructor, rules);
}
function IsPositive(target: Object, propertyKey: string | symbol) {
addValidationRule(target, propertyKey, (value: number) => value > 0, `${String(propertyKey)} must be a positive number.`);
}
function MaxLength(maxLength: number) {
return function (target: Object, propertyKey: string | symbol) {
addValidationRule(target, propertyKey, (value: string) => value.length <= maxLength, `${String(propertyKey)} must be at most ${maxLength} characters long.`);
};
}
class Product {
@MaxLength(50)
name: string;
@IsPositive
price: number;
constructor(name: string, price: number) {
this.name = name;
this.price = price;
}
static validate(instance: any): string[] {
const errors: string[] = [];
const rules = fieldValidationRules.get(instance.constructor) || [];
for (const rule of rules) {
if (!rule.validator(instance[rule.property])) {
errors.push(rule.message);
}
}
return errors;
}
}
const product1 = new Product("Laptop", 1200);
console.log("Product 1 errors:", Product.validate(product1)); // []
const product2 = new Product("Very long product name that exceeds fifty characters limit for testing purpose", 50);
console.log("Product 2 errors:", Product.validate(product2)); // ["name must be at most 50 characters long."]
const product3 = new Product("Book", -10);
console.log("Product 3 errors:", Product.validate(product3)); // ["price must be a positive number."]
यह सेटअप आपको अपने मॉडल प्रॉपर्टीज पर घोषणात्मक रूप से सत्यापन नियम परिभाषित करने की अनुमति देता है, जिससे आपके डेटा मॉडल उनकी बाधाओं के संदर्भ में आत्म-वर्णन करने वाले बन जाते हैं।
सर्वोत्तम प्रथाएं और विचार
जबकि डेकोरेटर्स शक्तिशाली हैं, उनका उपयोग विवेकपूर्ण तरीके से किया जाना चाहिए। उनका दुरुपयोग करने से ऐसा कोड बन सकता है जिसे डीबग करना या समझना कठिन हो।
डेकोरेटर्स का उपयोग कब करें (और कब नहीं)
- इनके लिए उपयोग करें:
- क्रॉस-कटिंग कंसर्न्स: लॉगिंग, कैशिंग, प्राधिकरण, लेनदेन प्रबंधन।
- मेटाडेटा घोषणा: ORMs के लिए स्कीमा को परिभाषित करना, सत्यापन नियम, DI कॉन्फ़िगरेशन।
- फ्रेमवर्क एकीकरण: जब मेटाडेटा का लाभ उठाने वाले फ्रेमवर्क का निर्माण या उपयोग कर रहे हों।
- बॉयलरप्लेट कम करना: दोहराए जाने वाले कोड पैटर्न को दूर करना।
- इनके लिए बचें:
- सरल फ़ंक्शन कॉल: यदि एक सादा फ़ंक्शन कॉल स्पष्ट रूप से वही परिणाम प्राप्त कर सकता है, तो उसे प्राथमिकता दें।
- व्यावसायिक तर्क: डेकोरेटर्स को मुख्य व्यावसायिक तर्क को परिभाषित नहीं, बल्कि बढ़ाना चाहिए।
- अत्यधिक जटिलता: यदि डेकोरेटर का उपयोग करने से कोड कम पठनीय या परीक्षण करना कठिन हो जाता है, तो पुनर्विचार करें।
प्रदर्शन निहितार्थ
डेकोरेटर्स संकलन-समय (या जावास्क्रिप्ट रनटाइम में परिभाषा-समय यदि ट्रांसपाइल किया गया हो) पर निष्पादित होते हैं। परिवर्तन या मेटाडेटा संग्रह तब होता है जब क्लास/मेथड को परिभाषित किया जाता है, न कि प्रत्येक कॉल पर। इसलिए, डेकोरेटर्स *लागू करने* का रनटाइम प्रदर्शन प्रभाव न्यूनतम होता है। हालांकि, आपके डेकोरेटर्स के *अंदर का तर्क* प्रदर्शन पर प्रभाव डाल सकता है, खासकर यदि वे प्रत्येक मेथड कॉल पर महंगी कार्रवाइयां करते हैं (उदाहरण के लिए, एक मेथड डेकोरेटर के भीतर जटिल गणना)।
रखरखाव और पठनीयता
डेकोरेटर्स, जब सही तरीके से उपयोग किए जाते हैं, तो बॉयलरप्लेट कोड को मुख्य तर्क से बाहर ले जाकर पठनीयता में काफी सुधार कर सकते हैं। हालांकि, यदि वे जटिल, छिपे हुए परिवर्तन करते हैं, तो डीबगिंग चुनौतीपूर्ण हो सकती है। सुनिश्चित करें कि आपके डेकोरेटर्स अच्छी तरह से प्रलेखित हैं और उनका व्यवहार पूर्वानुमेय है।
प्रायोगिक स्थिति और डेकोरेटर्स का भविष्य
यह दोहराना महत्वपूर्ण है कि टाइपस्क्रिप्ट डेकोरेटर्स एक स्टेज 3 TC39 प्रस्ताव पर आधारित हैं। इसका मतलब है कि विनिर्देश काफी हद तक स्थिर है लेकिन आधिकारिक ECMAScript मानक का हिस्सा बनने से पहले अभी भी छोटे बदलावों से गुजर सकता है। एंगुलर जैसे फ्रेमवर्क ने उन्हें अपनाया है, उनके अंतिम मानकीकरण पर दांव लगाया है। इसका मतलब एक निश्चित स्तर का जोखिम है, हालांकि उनके व्यापक रूप से अपनाने को देखते हुए, महत्वपूर्ण ब्रेकिंग परिवर्तन की संभावना नहीं है।
TC39 प्रस्ताव विकसित हुआ है। टाइपस्क्रिप्ट का वर्तमान कार्यान्वयन प्रस्ताव के एक पुराने संस्करण पर आधारित है। एक "लिगेसी डेकोरेटर्स" बनाम "स्टैंडर्ड डेकोरेटर्स" का अंतर है। जब आधिकारिक मानक आएगा, तो टाइपस्क्रिप्ट संभवतः अपने कार्यान्वयन को अपडेट करेगा। अधिकांश डेवलपर्स के लिए जो फ्रेमवर्क का उपयोग कर रहे हैं, यह संक्रमण फ्रेमवर्क द्वारा ही प्रबंधित किया जाएगा। पुस्तकालय लेखकों के लिए, लिगेसी और भविष्य के मानक डेकोरेटर्स के बीच सूक्ष्म अंतर को समझना आवश्यक हो सकता है।
emitDecoratorMetadata
कंपाइलर विकल्प
यह विकल्प, जब tsconfig.json
में true
पर सेट होता है, तो टाइपस्क्रिप्ट कंपाइलर को संकलित जावास्क्रिप्ट में कुछ डिज़ाइन-टाइम प्रकार मेटाडेटा उत्सर्जित करने का निर्देश देता है। इस मेटाडेटा में कंस्ट्रक्टर पैरामीटर्स का प्रकार (design:paramtypes
), मेथड्स का रिटर्न प्रकार (design:returntype
), और प्रॉपर्टीज का प्रकार (design:type
) शामिल है।
यह उत्सर्जित मेटाडेटा मानक जावास्क्रिप्ट रनटाइम का हिस्सा नहीं है। यह आमतौर पर reflect-metadata
पॉलीफ़िल द्वारा उपभोग किया जाता है, जो फिर इसे Reflect.getMetadata()
फ़ंक्शंस के माध्यम से सुलभ बनाता है। यह डिपेंडेंसी इंजेक्शन जैसे उन्नत पैटर्न के लिए बिल्कुल महत्वपूर्ण है, जहां एक कंटेनर को यह जानने की आवश्यकता होती है कि एक क्लास को बिना स्पष्ट कॉन्फ़िगरेशन के किन निर्भरताओं की आवश्यकता है।
डेकोरेटर्स के साथ उन्नत पैटर्न
डेकोरेटर्स को और भी अधिक परिष्कृत पैटर्न बनाने के लिए संयोजित और विस्तारित किया जा सकता है।
1. डेकोरेटर्स को डेकोरेट करना (हायर-ऑर्डर डेकोरेटर्स)
आप ऐसे डेकोरेटर्स बना सकते हैं जो अन्य डेकोरेटर्स को संशोधित या संयोजित करते हैं। यह कम आम है लेकिन डेकोरेटर्स की कार्यात्मक प्रकृति को प्रदर्शित करता है।
// एक डेकोरेटर जो सुनिश्चित करता है कि एक मेथड लॉग किया गया है और उसे व्यवस्थापक भूमिकाओं की भी आवश्यकता है
function AdminAndLoggedMethod() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// पहले Authorization लागू करें (आंतरिक)
Authorization(["admin"])(target, propertyKey, descriptor);
// फिर LogCall लागू करें (बाहरी)
LogCall(target, propertyKey, descriptor);
return descriptor; // संशोधित डिस्क्रिप्टर लौटाएं
};
}
class AdminPanel {
@AdminAndLoggedMethod()
deleteUserAccount(userId: string) {
console.log(`Deleting user account: ${userId}`);
return `User ${userId} deleted.`;
}
}
const adminPanel = new AdminPanel();
adminPanel.deleteUserAccount("user007");
/* अपेक्षित आउटपुट (व्यवस्थापक भूमिका मानते हुए):
[AUTH] Access granted for deleteUserAccount
[LOG] Calling deleteUserAccount with args: [ 'user007' ]
Deleting user account: user007
[LOG] Method deleteUserAccount returned: User user007 deleted.
*/
यहां, AdminAndLoggedMethod
एक फैक्ट्री है जो एक डेकोरेटर लौटाती है, और उस डेकोरेटर के अंदर, यह दो अन्य डेकोरेटर्स लागू करती है। यह पैटर्न जटिल डेकोरेटर रचनाओं को समाहित कर सकता है।
2. मिक्सइन्स के लिए डेकोरेटर्स का उपयोग करना
जबकि टाइपस्क्रिप्ट मिक्सइन्स को लागू करने के अन्य तरीके प्रदान करता है, डेकोरेटर्स का उपयोग क्लास में क्षमताओं को एक घोषणात्मक तरीके से इंजेक्ट करने के लिए किया जा सकता है।
function ApplyMixins(constructors: Function[]) {
return function (derivedConstructor: Function) {
constructors.forEach(baseConstructor => {
Object.getOwnPropertyNames(baseConstructor.prototype).forEach(name => {
Object.defineProperty(
derivedConstructor.prototype,
name,
Object.getOwnPropertyDescriptor(baseConstructor.prototype, name) || Object.create(null)
);
});
});
};
}
class Disposable {
isDisposed: boolean = false;
dispose() {
this.isDisposed = true;
console.log("Object disposed.");
}
}
class Loggable {
log(message: string) {
console.log(`[Loggable] ${message}`);
}
}
@ApplyMixins([Disposable, Loggable])
class MyResource implements Disposable, Loggable {
// ये गुण/विधियाँ डेकोरेटर द्वारा इंजेक्ट की जाती हैं
isDisposed!: boolean;
dispose!: () => void;
log!: (message: string) => void;
constructor(public name: string) {
this.log(`Resource ${this.name} created.`);
}
cleanUp() {
this.dispose();
this.log(`Resource ${this.name} cleaned up.`);
}
}
const resource = new MyResource("NetworkConnection");
console.log(`Is disposed: ${resource.isDisposed}`);
resource.cleanUp();
console.log(`Is disposed: ${resource.isDisposed}`);
यह @ApplyMixins
डेकोरेटर गतिशील रूप से बेस कंस्ट्रक्टर्स से व्युत्पन्न क्लास के प्रोटोटाइप में मेथड्स और प्रॉपर्टीज को कॉपी करता है, प्रभावी रूप से कार्यात्मकताओं को "मिक्स इन" करता है।
निष्कर्ष: आधुनिक टाइपस्क्रिप्ट विकास को सशक्त बनाना
टाइपस्क्रिप्ट डेकोरेटर्स एक शक्तिशाली और अभिव्यंजक सुविधा है जो मेटाडेटा-संचालित और एस्पेक्ट-ओरिएंटेड प्रोग्रामिंग के एक नए प्रतिमान को सक्षम करती है। वे डेवलपर्स को उनके मूल तर्क को बदले बिना क्लास, मेथड्स, प्रॉपर्टीज, एक्सेसर्स और पैरामीटर्स में घोषणात्मक व्यवहारों को बढ़ाने, संशोधित करने और जोड़ने की अनुमति देते हैं। चिंताओं का यह पृथक्करण स्वच्छ, अधिक रखरखाव योग्य और अत्यधिक पुन: प्रयोज्य कोड की ओर ले जाता है।
डिपेंडेंसी इंजेक्शन को सरल बनाने और मजबूत सत्यापन प्रणालियों को लागू करने से लेकर लॉगिंग और प्रदर्शन निगरानी जैसी क्रॉस-कटिंग चिंताओं को जोड़ने तक, डेकोरेटर्स कई सामान्य विकास चुनौतियों का एक सुंदर समाधान प्रदान करते हैं। जबकि उनकी प्रायोगिक स्थिति जागरूकता की मांग करती है, प्रमुख फ्रेमवर्क में उनका व्यापक रूप से अपनाना उनके व्यावहारिक मूल्य और भविष्य की प्रासंगिकता को दर्शाता है।
टाइपस्क्रिप्ट डेकोरेटर्स में महारत हासिल करके, आप अपने शस्त्रागार में एक महत्वपूर्ण उपकरण प्राप्त करते हैं, जो आपको अधिक मजबूत, स्केलेबल और बुद्धिमान एप्लिकेशन बनाने में सक्षम बनाता है। उन्हें जिम्मेदारी से अपनाएं, उनके यांत्रिकी को समझें, और अपने टाइपस्क्रिप्ट प्रोजेक्ट्स में घोषणात्मक शक्ति के एक नए स्तर को अनलॉक करें।