മലയാളം

മെറ്റാഡാറ്റ പ്രോഗ്രാമിംഗ്, ആസ്പെക്റ്റ്-ഓറിയന്റഡ് പ്രോഗ്രാമിംഗ്, ഡിക്ലറേറ്റീവ് പാറ്റേണുകൾ ഉപയോഗിച്ച് കോഡ് മെച്ചപ്പെടുത്തൽ എന്നിവയ്ക്കുള്ള ടൈപ്പ്സ്ക്രിപ്റ്റ് ഡെക്കറേറ്ററുകളുടെ ശക്തി കണ്ടെത്തുക. ആഗോള ഡെവലപ്പർമാർക്കുള്ള ഒരു സമ്പൂർണ്ണ ഗൈഡ്.

ടൈപ്പ്സ്ക്രിപ്റ്റ് ഡെക്കറേറ്ററുകൾ: ശക്തമായ ആപ്ലിക്കേഷനുകൾക്കായി മെറ്റാഡാറ്റ പ്രോഗ്രാമിംഗ് പാറ്റേണുകൾ സ്വായത്തമാക്കാം

ആധുനിക സോഫ്റ്റ്‌വെയർ ഡെവലപ്‌മെന്റിന്റെ വിശാലമായ ലോകത്ത്, വൃത്തിയുള്ളതും, അളക്കാവുന്നതും, കൈകാര്യം ചെയ്യാൻ എളുപ്പമുള്ളതുമായ കോഡ്ബേസുകൾ നിലനിർത്തുന്നത് വളരെ പ്രധാനമാണ്. ടൈപ്പ്സ്ക്രിപ്റ്റ്, അതിന്റെ ശക്തമായ ടൈപ്പ് സിസ്റ്റവും നൂതന ഫീച്ചറുകളും ഉപയോഗിച്ച്, ഡെവലപ്പർമാർക്ക് ഇത് നേടാനുള്ള ഉപകരണങ്ങൾ നൽകുന്നു. അതിന്റെ ഏറ്റവും കൗതുകകരവും പരിവർത്തനാത്മകവുമായ ഫീച്ചറുകളിൽ ഒന്നാണ് ഡെക്കറേറ്ററുകൾ. ഈ ലേഖനം എഴുതുന്ന സമയത്ത് ഇതൊരു പരീക്ഷണാത്മക ഫീച്ചർ ആണെങ്കിലും (ECMAScript-നുള്ള സ്റ്റേജ് 3 പ്രൊപ്പോസൽ), ആംഗുലർ, ടൈപ്പ്ഓആർഎം പോലുള്ള ഫ്രെയിംവർക്കുകളിൽ ഡെക്കറേറ്ററുകൾ വ്യാപകമായി ഉപയോഗിക്കുന്നു. ഡിസൈൻ പാറ്റേണുകൾ, മെറ്റാഡാറ്റ പ്രോഗ്രാമിംഗ്, ആസ്പെക്റ്റ്-ഓറിയന്റഡ് പ്രോഗ്രാമിംഗ് (AOP) എന്നിവയെക്കുറിച്ചുള്ള നമ്മുടെ കാഴ്ചപ്പാടുകളെ ഇത് അടിസ്ഥാനപരമായി മാറ്റുന്നു.

ഈ സമഗ്രമായ ഗൈഡ് ടൈപ്പ്സ്ക്രിപ്റ്റ് ഡെക്കറേറ്ററുകളിലേക്ക് ആഴത്തിൽ ഇറങ്ങിച്ചെല്ലുകയും, അവയുടെ പ്രവർത്തനരീതികൾ, വിവിധ തരങ്ങൾ, പ്രായോഗിക ഉപയോഗങ്ങൾ, മികച്ച രീതികൾ എന്നിവ പര്യവേക്ഷണം ചെയ്യുകയും ചെയ്യും. നിങ്ങൾ വലിയ തോതിലുള്ള എന്റർപ്രൈസ് ആപ്ലിക്കേഷനുകളോ, മൈക്രോസർവീസുകളോ, ക്ലയിന്റ്-സൈഡ് വെബ് ഇന്റർഫേസുകളോ നിർമ്മിക്കുകയാണെങ്കിലും, ഡെക്കറേറ്ററുകളെക്കുറിച്ചുള്ള ധാരണ കൂടുതൽ ഡിക്ലറേറ്റീവും, പരിപാലിക്കാൻ എളുപ്പമുള്ളതും, ശക്തവുമായ ടൈപ്പ്സ്ക്രിപ്റ്റ് കോഡ് എഴുതാൻ നിങ്ങളെ സഹായിക്കും.

അടിസ്ഥാന ആശയം മനസ്സിലാക്കാം: എന്താണ് ഒരു ഡെക്കറേറ്റർ?

യഥാർത്ഥത്തിൽ, ഒരു ക്ലാസ് ഡിക്ലറേഷൻ, മെത്തേഡ്, അക്സസ്സർ, പ്രോപ്പർട്ടി, അല്ലെങ്കിൽ പാരാമീറ്റർ എന്നിവയിൽ ഘടിപ്പിക്കാൻ കഴിയുന്ന ഒരു പ്രത്യേക തരം ഡിക്ലറേഷനാണ് ഡെക്കറേറ്റർ. ഡെക്കറേറ്ററുകൾ എന്നത് ഫംഗ്ഷനുകളാണ്, അവ അലങ്കരിക്കുന്ന ടാർഗെറ്റിനായി ഒരു പുതിയ മൂല്യം നൽകുകയോ (അല്ലെങ്കിൽ നിലവിലുള്ളത് പരിഷ്കരിക്കുകയോ) ചെയ്യുന്നു. അവയുടെ പ്രാഥമിക ലക്ഷ്യം, കോഡിന്റെ അടിസ്ഥാന ഘടനയെ നേരിട്ട് മാറ്റാതെ, അവ ഘടിപ്പിച്ചിരിക്കുന്ന ഡിക്ലറേഷനിലേക്ക് മെറ്റാഡാറ്റ ചേർക്കുകയോ അതിന്റെ സ്വഭാവത്തിൽ മാറ്റം വരുത്തുകയോ ചെയ്യുക എന്നതാണ്. കോഡിനെ ബാഹ്യവും ഡിക്ലറേറ്റീവുമായ രീതിയിൽ മെച്ചപ്പെടുത്താനുള്ള ഈ മാർഗ്ഗം വളരെ ശക്തമാണ്.

നിങ്ങളുടെ കോഡിന്റെ ഭാഗങ്ങളിൽ പ്രയോഗിക്കുന്ന കുറിപ്പുകളോ ലേബലുകളോ ആയി ഡെക്കറേറ്ററുകളെ കരുതുക. ഈ ലേബലുകൾ പിന്നീട് നിങ്ങളുടെ ആപ്ലിക്കേഷന്റെ മറ്റ് ഭാഗങ്ങൾക്കോ ഫ്രെയിംവർക്കുകൾക്കോ വായിക്കാനും, അതിനനുസരിച്ച് പ്രവർത്തിക്കാനും, അധിക പ്രവർത്തനക്ഷമതയോ കോൺഫിഗറേഷനോ നൽകാനും കഴിയും, പലപ്പോഴും ഇത് റൺടൈമിലാണ് സംഭവിക്കുന്നത്.

ഒരു ഡെക്കറേറ്ററിന്റെ വാക്യഘടന (Syntax)

ഡെക്കറേറ്ററുകൾ ഒരു @ ചിഹ്നത്തിൽ ആരംഭിക്കുന്നു, അതിനുശേഷം ഡെക്കറേറ്റർ ഫംഗ്ഷന്റെ പേര് വരുന്നു. അവ അലങ്കരിക്കുന്ന ഡിക്ലറേഷന് തൊട്ടുമുമ്പായി സ്ഥാപിക്കുന്നു.

@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
# or
yarn add reflect-metadata

അതുപോലെ, നിങ്ങളുടെ ആപ്ലിക്കേഷന്റെ എൻട്രി പോയിന്റിന്റെ (ഉദാഹരണത്തിന്, main.ts അല്ലെങ്കിൽ app.ts) ഏറ്റവും മുകളിൽ ഇത് ഇമ്പോർട്ട് ചെയ്യുക:

import "reflect-metadata";
// Your application code follows

ഡെക്കറേറ്റർ ഫാക്ടറികൾ: ഇഷ്ടാനുസൃതമാക്കൽ നിങ്ങളുടെ വിരൽത്തുമ്പിൽ

ഒരു അടിസ്ഥാന ഡെക്കറേറ്റർ ഒരു ഫംഗ്ഷനാണെങ്കിലും, അതിന്റെ സ്വഭാവം ക്രമീകരിക്കുന്നതിന് പലപ്പോഴും നിങ്ങൾക്ക് ഡെക്കറേറ്ററിലേക്ക് ആർഗ്യുമെന്റുകൾ നൽകേണ്ടി വരും. ഇത് ഒരു ഡെക്കറേറ്റർ ഫാക്ടറി ഉപയോഗിച്ചാണ് നേടുന്നത്. ഒരു ഡെക്കറേറ്റർ ഫാക്ടറി എന്നത് യഥാർത്ഥ ഡെക്കറേറ്റർ ഫംഗ്ഷനെ തിരികെ നൽകുന്ന ഒരു ഫംഗ്ഷനാണ്. നിങ്ങൾ ഒരു ഡെക്കറേറ്റർ ഫാക്ടറി പ്രയോഗിക്കുമ്പോൾ, നിങ്ങൾ അതിനെ അതിന്റെ ആർഗ്യുമെന്റുകൾ ഉപയോഗിച്ച് വിളിക്കുന്നു, തുടർന്ന് അത് ടൈപ്പ്സ്ക്രിപ്റ്റ് നിങ്ങളുടെ കോഡിൽ പ്രയോഗിക്കുന്ന ഡെക്കറേറ്റർ ഫംഗ്ഷനെ തിരികെ നൽകുന്നു.

ഒരു ലളിതമായ ഡെക്കറേറ്റർ ഫാക്ടറി ഉദാഹരണം

വ്യത്യസ്ത പ്രിഫിക്സുകൾ ഉപയോഗിച്ച് സന്ദേശങ്ങൾ ലോഗ് ചെയ്യാൻ കഴിയുന്ന ഒരു 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();
// Output:
// [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}`);

    // Optionally, you could return a new class here to augment behavior
    return class extends constructor {
      createdAt = new Date();
      // Additional properties or methods for all injected services
    };
  };
}

@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); // If the returned class is used
}

ഈ ഉദാഹരണം ഒരു ക്ലാസ് ഡെക്കറേറ്ററിന് എങ്ങനെ ഒരു ക്ലാസ് രജിസ്റ്റർ ചെയ്യാനും അതിന്റെ കൺസ്ട്രക്ടർ പരിഷ്കരിക്കാനും കഴിയുമെന്ന് കാണിക്കുന്നു. 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[] {
    // Simulate a complex, time-consuming operation
    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;
  }

  // Simulates an expensive computation
  @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) { ... }

തിരികെ നൽകുന്ന മൂല്യം:

പ്രോപ്പർട്ടി ഡെക്കറേറ്ററുകൾക്ക് ഒരു മൂല്യവും തിരികെ നൽകാൻ കഴിയില്ല. അവയുടെ പ്രാഥമിക ഉപയോഗം പ്രോപ്പർട്ടിയെക്കുറിച്ചുള്ള മെറ്റാഡാറ്റ രജിസ്റ്റർ ചെയ്യുക എന്നതാണ്. ഡെക്കറേറ്റ് ചെയ്യുന്ന സമയത്ത് അവയ്ക്ക് പ്രോപ്പർട്ടിയുടെ മൂല്യമോ അതിന്റെ ഡിസ്ക്രിപ്റ്ററോ നേരിട്ട് മാറ്റാൻ കഴിയില്ല, കാരണം പ്രോപ്പർട്ടി ഡെക്കറേറ്ററുകൾ പ്രവർത്തിക്കുമ്പോൾ ഒരു പ്രോപ്പർട്ടിയുടെ ഡിസ്ക്രിപ്റ്റർ പൂർണ്ണമായി നിർവചിച്ചിട്ടുണ്ടാവില്ല.

ഉപയോഗങ്ങൾ:

പ്രോപ്പർട്ടി ഡെക്കറേറ്റർ ഉദാഹരണം: ആവശ്യമായ ഫീൽഡ് സാധുത

ഒരു പ്രോപ്പർട്ടിയെ "ആവശ്യമാണ്" എന്ന് അടയാളപ്പെടുത്താനും റൺടൈമിൽ അത് സാധൂകരിക്കാനും ഒരു ഡെക്കറേറ്റർ ഉണ്ടാക്കാം.

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) { ... }

തിരികെ നൽകുന്ന മൂല്യം:

പാരാമീറ്റർ ഡെക്കറേറ്ററുകൾക്ക് ഒരു മൂല്യവും തിരികെ നൽകാൻ കഴിയില്ല. പ്രോപ്പർട്ടി ഡെക്കറേറ്ററുകളെപ്പോലെ, അവയുടെ പ്രാഥമിക പങ്ക് പാരാമീറ്ററിനെക്കുറിച്ചുള്ള മെറ്റാഡാറ്റ ചേർക്കുക എന്നതാണ്.

ഉപയോഗങ്ങൾ:

പാരാമീറ്റർ ഡെക്കറേറ്റർ ഉദാഹരണം: അഭ്യർത്ഥന ഡാറ്റ ഇഞ്ചക്റ്റ് ചെയ്യൽ

ഒരു വെബ് ഫ്രെയിംവർക്ക് ഒരു അഭ്യർത്ഥനയിൽ നിന്ന് ഒരു യൂസർ ഐഡി പോലുള്ള നിർദ്ദിഷ്ട ഡാറ്റ ഒരു മെത്തേഡ് പാരാമീറ്ററിലേക്ക് ഇഞ്ചക്റ്റ് ചെയ്യാൻ പാരാമീറ്റർ ഡെക്കറേറ്ററുകൾ എങ്ങനെ ഉപയോഗിക്കാമെന്ന് നമുക്ക് അനുകരിക്കാം.

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);
  };
}

// A hypothetical framework function to invoke a method with resolved parameters
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();

// Simulate an incoming request
const mockRequest = {
  id: "user123",
  token: "abc-123",
  someOtherProp: "xyz"
};

console.log("\n--- Executing getUser ---");
executeWithParams(userController, "getUser", mockRequest);

console.log("\n--- 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"]; // Simulate fetching current user roles
      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 // Order changed here
  fetchPublicData(query: string) {
    console.log(`Fetching public data with query: ${query}`);
    return `Public data for query: ${query}`; 
  }
}

const service = new SecureService();

try {
  console.log("\n--- Calling deleteSensitiveData (Admin User) ---");
  service.deleteSensitiveData("record123");
} catch (error: any) {
  console.error(error.message);
}

try {
  console.log("\n--- Calling fetchPublicData (Non-Admin User) ---");
  // Simulate a non-admin user trying to access fetchPublicData which requires 'user' role
  const mockUserRoles = ["guest"]; // This will fail auth
  // To make this dynamic, you'd need a DI system or static context for current user roles.
  // For simplicity, we assume the Authorization decorator has access to current user context.
  // Let's adjust Authorization decorator to always assume 'admin' for demo purposes, 
  // so the first call succeeds and second fails to show different paths.
  
  // Re-run with user role for fetchPublicData to succeed.
  // Imagine currentUserRoles in Authorization becomes: ['user']
  // For this example, let's keep it simple and show the order effect.
  service.fetchPublicData("search term"); // This will execute Auth -> Log
} catch (error: any) {
  console.error(error.message);
}

/* Expected output for 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.
*/

/* Expected output for fetchPublicData (if user has 'user' role):
[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 എന്ന ആസ്പെക്റ്റിന് പുറത്തായിരിക്കും. ലോഗിംഗ് അല്ലെങ്കിൽ എറർ ഹാൻഡ്‌ലിംഗ് പോലുള്ള ക്രോസ്-കട്ടിംഗ് കൺസേണുകൾക്ക് ഈ വ്യത്യാസം നിർണായകമാണ്, ഇവിടെ എക്സിക്യൂഷൻ ക്രമം പെരുമാറ്റത്തെ കാര്യമായി സ്വാധീനിക്കും.

വ്യത്യസ്ത ടാർഗെറ്റുകൾക്കുള്ള എക്സിക്യൂഷൻ ഓർഡർ

ഒരു ക്ലാസ്, അതിന്റെ അംഗങ്ങൾ, പാരാമീറ്ററുകൾ എന്നിവയ്‌ക്കെല്ലാം ഡെക്കറേറ്ററുകൾ ഉള്ളപ്പോൾ, എക്സിക്യൂഷൻ ഓർഡർ കൃത്യമായി നിർവചിക്കപ്പെട്ടിട്ടുണ്ട്:

  1. പാരാമീറ്റർ ഡെക്കറേറ്ററുകൾ ആദ്യം പ്രയോഗിക്കുന്നു, ഓരോ പാരാമീറ്ററിനും, അവസാന പാരാമീറ്റർ മുതൽ ആദ്യത്തേത് വരെ.
  2. തുടർന്ന്, ഓരോ അംഗത്തിനും മെത്തേഡ്, അക്സസ്സർ, അല്ലെങ്കിൽ പ്രോപ്പർട്ടി ഡെക്കറേറ്ററുകൾ പ്രയോഗിക്കുന്നു.
  3. അവസാനമായി, ക്ലാസ് ഡെക്കറേറ്ററുകൾ ക്ലാസിൽ തന്നെ പ്രയോഗിക്കുന്നു.

ഓരോ വിഭാഗത്തിലും, ഒരേ ടാർഗെറ്റിലുള്ള ഒന്നിലധികം ഡെക്കറേറ്ററുകൾ താഴെ നിന്ന് മുകളിലേക്ക് (അല്ലെങ്കിൽ വലത്തുനിന്ന് ഇടത്തേക്ക്) പ്രയോഗിക്കുന്നു.

ഉദാഹരണം: പൂർണ്ണമായ എക്സിക്യൂഷൻ ഓർഡർ

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; // Return descriptor for method/accessor, undefined for others
  };
}

@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();
// Call method to trigger method decorator
new MyDecoratedClass().myMethod("hello", 123);

/* Predicted Output Order (approximate, depending on specific TypeScript version and compilation):
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"; // Essential for 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.`);
    }    // Get constructor parameters' types (requires emitDecoratorMetadata)
    const paramTypes: any[] = Reflect.getMetadata("design:paramtypes", target) || [];
    const explicitInjections: any[] = Reflect.getMetadata(INJECT_METADATA_KEY, target) || [];

    const dependencies = paramTypes.map((paramType, index) => {
      // Use explicit @Inject token if provided, otherwise infer type
      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;
  }
}

// Define services
@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; // Example of injecting via property using a custom decorator or framework feature

  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" };
  }
}

// Resolve the main service
console.log("--- Resolving UserService ---");
const userService = Container.resolve(UserService);
console.log(userService.getUserProfile());

console.log("\n--- Resolving AuthService (should be cached) ---");
const authService = Container.resolve(AuthService);
authService.login();

ഈ വിശദമായ ഉദാഹരണം, @Injectable, @Inject ഡെക്കറേറ്ററുകൾ, reflect-metadata-യുമായി ചേർന്ന്, ഒരു കസ്റ്റം Container-ന് ഡിപെൻഡൻസികൾ സ്വയമേവ പരിഹരിക്കാനും നൽകാനും എങ്ങനെ അനുവദിക്കുന്നു എന്ന് കാണിക്കുന്നു. ടൈപ്പ്സ്ക്രിപ്റ്റ് സ്വയമേവ പുറത്തുവിടുന്ന design:paramtypes മെറ്റാഡാറ്റ (emitDecoratorMetadata true ആയിരിക്കുമ്പോൾ) ഇവിടെ നിർണായകമാണ്.

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."]

ഈ സജ്ജീകരണം നിങ്ങളുടെ മോഡൽ പ്രോപ്പർട്ടികളിൽ സാധുതാ നിയമങ്ങൾ ഡിക്ലറേറ്റീവ് ആയി നിർവചിക്കാൻ നിങ്ങളെ അനുവദിക്കുന്നു, ഇത് നിങ്ങളുടെ ഡാറ്റാ മോഡലുകളെ അവയുടെ പരിമിതികളുടെ അടിസ്ഥാനത്തിൽ സ്വയം വിവരിക്കുന്നതാക്കി മാറ്റുന്നു.

മികച്ച രീതികളും പരിഗണനകളും

ഡെക്കറേറ്ററുകൾ ശക്തമാണെങ്കിലും, അവ വിവേകത്തോടെ ഉപയോഗിക്കണം. അവയെ ദുരുപയോഗം ചെയ്യുന്നത് ഡീബഗ് ചെയ്യാനോ മനസ്സിലാക്കാനോ ബുദ്ധിമുട്ടുള്ള കോഡിലേക്ക് നയിച്ചേക്കാം.

എപ്പോൾ ഡെക്കറേറ്ററുകൾ ഉപയോഗിക്കണം (എപ്പോൾ ഉപയോഗിക്കരുത്)

പ്രകടന പ്രത്യാഘാതങ്ങൾ

ഡെക്കറേറ്ററുകൾ കംപൈൽ-ടൈമിൽ (അല്ലെങ്കിൽ ട്രാൻസ്പൈൽ ചെയ്യുമ്പോൾ ജാവാസ്ക്രിപ്റ്റ് റൺടൈമിലെ ഡെഫനിഷൻ-ടൈമിൽ) എക്സിക്യൂട്ട് ചെയ്യുന്നു. ക്ലാസ്/മെത്തേഡ് നിർവചിക്കുമ്പോഴാണ് രൂപാന്തരീകരണമോ മെറ്റാഡാറ്റ ശേഖരണമോ സംഭവിക്കുന്നത്, ഓരോ കോളിലും അല്ല. അതിനാൽ, ഡെക്കറേറ്ററുകൾ *പ്രയോഗിക്കുന്നതിന്റെ* റൺടൈം പ്രകടന സ്വാധീനം വളരെ കുറവാണ്. എന്നിരുന്നാലും, നിങ്ങളുടെ ഡെക്കറേറ്ററുകൾക്കുള്ളിലെ *ലോജിക്കിന്* പ്രകടന സ്വാധീനം ഉണ്ടാകാം, പ്രത്യേകിച്ചും അവ ഓരോ മെത്തേഡ് കോളിലും ചെലവേറിയ പ്രവർത്തനങ്ങൾ നടത്തുന്നുവെങ്കിൽ (ഉദാ. ഒരു മെത്തേഡ് ഡെക്കറേറ്ററിനുള്ളിലെ സങ്കീർണ്ണമായ കണക്കുകൂട്ടലുകൾ).

പരിപാലനക്ഷമതയും വായനാക്ഷമതയും

ഡെക്കറേറ്ററുകൾ, ശരിയായി ഉപയോഗിക്കുമ്പോൾ, പ്രധാന ലോജിക്കിൽ നിന്ന് ബോയിലർപ്ലേറ്റ് കോഡ് നീക്കം ചെയ്ത് വായനാക്ഷമത ഗണ്യമായി മെച്ചപ്പെടുത്താൻ കഴിയും. എന്നിരുന്നാലും, അവ സങ്കീർണ്ണവും മറഞ്ഞിരിക്കുന്നതുമായ രൂപാന്തരീകരണങ്ങൾ നടത്തുന്നുവെങ്കിൽ, ഡീബഗ്ഗിംഗ് വെല്ലുവിളിയാകും. നിങ്ങളുടെ ഡെക്കറേറ്ററുകൾ നന്നായി ഡോക്യുമെന്റ് ചെയ്തിട്ടുണ്ടെന്നും അവയുടെ സ്വഭാവം പ്രവചിക്കാവുന്നതാണെന്നും ഉറപ്പാക്കുക.

പരീക്ഷണാത്മക നിലയും ഡെക്കറേറ്ററുകളുടെ ഭാവിയും

ടൈപ്പ്സ്ക്രിപ്റ്റ് ഡെക്കറേറ്ററുകൾ ഒരു സ്റ്റേജ് 3 TC39 പ്രൊപ്പോസലിനെ അടിസ്ഥാനമാക്കിയുള്ളതാണെന്ന് ആവർത്തിക്കേണ്ടത് പ്രധാനമാണ്. ഇതിനർത്ഥം സ്പെസിഫിക്കേഷൻ വലിയൊരളവിൽ സ്ഥിരതയുള്ളതാണെങ്കിലും ഔദ്യോഗിക ECMAScript സ്റ്റാൻഡേർഡിന്റെ ഭാഗമാകുന്നതിന് മുമ്പ് ഇപ്പോഴും ചെറിയ മാറ്റങ്ങൾക്ക് വിധേയമാകാം. ആംഗുലർ പോലുള്ള ഫ്രെയിംവർക്കുകൾ അവയുടെ അന്തിമ സ്റ്റാൻഡേർഡൈസേഷനിൽ വിശ്വസിച്ച് അവയെ സ്വീകരിച്ചു. ഇത് ഒരു നിശ്ചിത തലത്തിലുള്ള അപകടസാധ്യത സൂചിപ്പിക്കുന്നു, എങ്കിലും അവയുടെ വ്യാപകമായ സ്വീകാര്യത കണക്കിലെടുക്കുമ്പോൾ, കാര്യമായ ബ്രേക്കിംഗ് മാറ്റങ്ങൾ ഉണ്ടാകാൻ സാധ്യതയില്ല.

TC39 പ്രൊപ്പോസൽ വികസിച്ചിട്ടുണ്ട്. ടൈപ്പ്സ്ക്രിപ്റ്റിന്റെ നിലവിലെ നടപ്പാക്കൽ പ്രൊപ്പോസലിന്റെ ഒരു പഴയ പതിപ്പിനെ അടിസ്ഥാനമാക്കിയുള്ളതാണ്. "ലെഗസി ഡെക്കറേറ്ററുകൾ" വേഴ്സസ് "സ്റ്റാൻഡേർഡ് ഡെക്കറേറ്ററുകൾ" എന്ന വ്യത്യാസമുണ്ട്. ഔദ്യോഗിക സ്റ്റാൻഡേർഡ് വരുമ്പോൾ, ടൈപ്പ്സ്ക്രിപ്റ്റ് അതിന്റെ നടപ്പാക്കൽ അപ്ഡേറ്റ് ചെയ്യാൻ സാധ്യതയുണ്ട്. ഫ്രെയിംവർക്കുകൾ ഉപയോഗിക്കുന്ന മിക്ക ഡെവലപ്പർമാർക്കും, ഈ മാറ്റം ഫ്രെയിംവർക്ക് തന്നെ കൈകാര്യം ചെയ്യും. ലൈബ്രറി രചയിതാക്കൾക്ക്, ലെഗസിയും ഭാവിയിലെ സ്റ്റാൻഡേർഡ് ഡെക്കറേറ്ററുകളും തമ്മിലുള്ള സൂക്ഷ്മമായ വ്യത്യാസങ്ങൾ മനസ്സിലാക്കേണ്ടത് ആവശ്യമായി വന്നേക്കാം.

emitDecoratorMetadata കംപൈലർ ഓപ്ഷൻ

ഈ ഓപ്ഷൻ, tsconfig.json-ൽ true ആയി സജ്ജമാക്കുമ്പോൾ, ടൈപ്പ്സ്ക്രിപ്റ്റ് കംപൈലറിനോട് ചില ഡിസൈൻ-ടൈം ടൈപ്പ് മെറ്റാഡാറ്റ കംപൈൽ ചെയ്ത ജാവാസ്ക്രിപ്റ്റിലേക്ക് പുറത്തുവിടാൻ നിർദ്ദേശിക്കുന്നു. ഈ മെറ്റാഡാറ്റയിൽ കൺസ്ട്രക്ടർ പാരാമീറ്ററുകളുടെ തരം (design:paramtypes), മെത്തേഡുകളുടെ റിട്ടേൺ ടൈപ്പ് (design:returntype), പ്രോപ്പർട്ടികളുടെ തരം (design:type) എന്നിവ ഉൾപ്പെടുന്നു.

ഈ പുറത്തുവിട്ട മെറ്റാഡാറ്റ സ്റ്റാൻഡേർഡ് ജാവാസ്ക്രിപ്റ്റ് റൺടൈമിന്റെ ഭാഗമല്ല. ഇത് സാധാരണയായി reflect-metadata പോളിഫിൽ ഉപയോഗിക്കുന്നു, അത് പിന്നീട് Reflect.getMetadata() ഫംഗ്ഷനുകൾ വഴി ഇത് ആക്സസ് ചെയ്യാൻ കഴിയുന്നതാക്കി മാറ്റുന്നു. ഡിപെൻഡൻസി ഇഞ്ചക്ഷൻ പോലുള്ള വിപുലമായ പാറ്റേണുകൾക്ക് ഇത് തികച്ചും നിർണായകമാണ്, ഇവിടെ ഒരു കണ്ടെയ്നറിന് ഒരു ക്ലാസിന് ആവശ്യമായ ഡിപെൻഡൻസികളുടെ തരങ്ങൾ വ്യക്തമായ കോൺഫിഗറേഷൻ ഇല്ലാതെ അറിയേണ്ടതുണ്ട്.

ഡെക്കറേറ്ററുകളുമായുള്ള വിപുലമായ പാറ്റേണുകൾ

ഡെക്കറേറ്ററുകൾ സംയോജിപ്പിച്ച് വിപുലീകരിച്ച് കൂടുതൽ സങ്കീർണ്ണമായ പാറ്റേണുകൾ നിർമ്മിക്കാൻ കഴിയും.

1. ഡെക്കറേറ്ററുകളെ ഡെക്കറേറ്റ് ചെയ്യൽ (ഹയർ-ഓർഡർ ഡെക്കറേറ്ററുകൾ)

മറ്റ് ഡെക്കറേറ്ററുകളെ പരിഷ്കരിക്കുകയോ കമ്പോസ് ചെയ്യുകയോ ചെയ്യുന്ന ഡെക്കറേറ്ററുകൾ നിങ്ങൾക്ക് സൃഷ്ടിക്കാൻ കഴിയും. ഇത് സാധാരണ കുറവാണെങ്കിലും ഡെക്കറേറ്ററുകളുടെ ഫംഗ്ഷണൽ സ്വഭാവം പ്രകടമാക്കുന്നു.

// A decorator that ensures a method is logged and also requires admin roles
function AdminAndLoggedMethod() {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    // Apply Authorization first (inner)
    Authorization(["admin"])(target, propertyKey, descriptor);
    // Then apply LogCall (outer)
    LogCall(target, propertyKey, descriptor);

    return descriptor; // Return the modified descriptor
  };
}

class AdminPanel {
  @AdminAndLoggedMethod()
  deleteUserAccount(userId: string) {
    console.log(`Deleting user account: ${userId}`);
    return `User ${userId} deleted.`;
  }
}

const adminPanel = new AdminPanel();
adminPanel.deleteUserAccount("user007");
/* Expected Output (assuming admin role):
[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 {
  // These properties/methods are injected by the decorator
  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 ഡെക്കറേറ്റർ അടിസ്ഥാന കൺസ്ട്രക്ടറുകളിൽ നിന്ന് മെത്തേഡുകളും പ്രോപ്പർട്ടികളും ഡെറിവേഡ് ക്ലാസിന്റെ പ്രോട്ടോടൈപ്പിലേക്ക് ഡൈനാമിക് ആയി പകർത്തുന്നു, ഫലപ്രദമായി പ്രവർത്തനങ്ങളെ "മിക്സ് ഇൻ" ചെയ്യുന്നു.

ഉപസംഹാരം: ആധുനിക ടൈപ്പ്സ്ക്രിപ്റ്റ് ഡെവലപ്മെന്റിനെ ശാക്തീകരിക്കുന്നു

ടൈപ്പ്സ്ക്രിപ്റ്റ് ഡെക്കറേറ്ററുകൾ മെറ്റാഡാറ്റ-ഡ്രിവൺ, ആസ്പെക്റ്റ്-ഓറിയന്റഡ് പ്രോഗ്രാമിംഗിന്റെ ഒരു പുതിയ മാതൃക സാധ്യമാക്കുന്ന ശക്തവും പ്രകടനാത്മകവുമായ ഒരു ഫീച്ചറാണ്. അവ ഡെവലപ്പർമാർക്ക് ക്ലാസുകൾ, മെത്തേഡുകൾ, പ്രോപ്പർട്ടികൾ, അക്സസ്സറുകൾ, പാരാമീറ്ററുകൾ എന്നിവയുടെ പ്രധാന ലോജിക് മാറ്റാതെ തന്നെ അവയെ മെച്ചപ്പെടുത്താനും, പരിഷ്കരിക്കാനും, ഡിക്ലറേറ്റീവ് സ്വഭാവങ്ങൾ ചേർക്കാനും അനുവദിക്കുന്നു. ഈ ആശങ്കകളുടെ വേർതിരിവ് വൃത്തിയുള്ളതും, കൂടുതൽ പരിപാലിക്കാൻ കഴിയുന്നതും, ഉയർന്ന പുനരുപയോഗിക്കാവുന്നതുമായ കോഡിലേക്ക് നയിക്കുന്നു.

ഡിപെൻഡൻസി ഇഞ്ചക്ഷൻ ലളിതമാക്കുന്നതിലും ശക്തമായ സാധുതാ സംവിധാനങ്ങൾ നടപ്പിലാക്കുന്നതിലും തുടങ്ങി ലോഗിംഗ്, പ്രകടന നിരീക്ഷണം പോലുള്ള ക്രോസ്-കട്ടിംഗ് കൺസേണുകൾ ചേർക്കുന്നതുവരെ, ഡെക്കറേറ്ററുകൾ പല സാധാരണ വികസന വെല്ലുവിളികൾക്കും ഒരു ഗംഭീരമായ പരിഹാരം നൽകുന്നു. അവയുടെ പരീക്ഷണാത്മക നില അവബോധം ആവശ്യപ്പെടുന്നുണ്ടെങ്കിലും, പ്രധാന ഫ്രെയിംവർക്കുകളിലെ അവയുടെ വ്യാപകമായ സ്വീകാര്യത അവയുടെ പ്രായോഗിക മൂല്യവും ഭാവിയിലെ പ്രസക്തിയും സൂചിപ്പിക്കുന്നു.

ടൈപ്പ്സ്ക്രിപ്റ്റ് ഡെക്കറേറ്ററുകൾ സ്വായത്തമാക്കുന്നതിലൂടെ, നിങ്ങളുടെ ആയുധപ്പുരയിൽ ഒരു സുപ്രധാന ഉപകരണം ലഭിക്കുന്നു, ഇത് കൂടുതൽ കരുത്തുറ്റതും, അളക്കാവുന്നതും, ബുദ്ധിപരവുമായ ആപ്ലിക്കേഷനുകൾ നിർമ്മിക്കാൻ നിങ്ങളെ പ്രാപ്തരാക്കുന്നു. അവയെ ഉത്തരവാദിത്തത്തോടെ സ്വീകരിക്കുക, അവയുടെ പ്രവർത്തനരീതികൾ മനസ്സിലാക്കുക, നിങ്ങളുടെ ടൈപ്പ്സ്ക്രിപ്റ്റ് പ്രോജക്റ്റുകളിൽ ഡിക്ലറേറ്റീവ് ശക്തിയുടെ ഒരു പുതിയ തലം അൺലോക്ക് ചെയ്യുക.