தமிழ்

மெட்டாடேட்டா புரோகிராமிங், ஆஸ்பெக்ட்-ஓரியன்டட் புரோகிராமிங் மற்றும் டிக்ளரேட்டிவ் பேட்டர்ன்கள் மூலம் கோடை மேம்படுத்த டைப்ஸ்கிரிப்ட் டெக்கரேட்டர்களின் ஆற்றலை ஆராயுங்கள். உலகளாவிய டெவலப்பர்களுக்கான ஒரு விரிவான வழிகாட்டி.

டைப்ஸ்கிரிப்ட் டெக்கரேட்டர்கள்: வலுவான பயன்பாடுகளுக்கான மெட்டாடேட்டா புரோகிராமிங் பேட்டர்ன்களில் தேர்ச்சி பெறுதல்

நவீன மென்பொருள் மேம்பாட்டின் பரந்த வெளியில், சுத்தமான, அளவிடக்கூடிய மற்றும் நிர்வகிக்கக்கூடிய கோட்பேஸ்களைப் பராமரிப்பது மிக முக்கியமானது. டைப்ஸ்கிரிப்ட், அதன் சக்திவாய்ந்த டைப் சிஸ்டம் மற்றும் மேம்பட்ட அம்சங்களுடன், டெவலப்பர்களுக்கு இதை அடைய கருவிகளை வழங்குகிறது. அதன் மிகவும் கவர்ச்சிகரமான மற்றும் மாற்றத்தக்க அம்சங்களில் ஒன்று டெக்கரேட்டர்கள். எழுதும் நேரத்தில் இது இன்னும் ஒரு சோதனை அம்சமாக (ECMAScript-க்கான நிலை 3 முன்மொழிவு) இருந்தாலும், டெக்கரேட்டர்கள் ஆங்குலர் மற்றும் டைப்ஓஆர்எம் போன்ற கட்டமைப்புகளில் பரவலாகப் பயன்படுத்தப்படுகின்றன, இது வடிவமைப்பு முறைகள், மெட்டாடேட்டா புரோகிராமிங் மற்றும் ஆஸ்பெக்ட்-ஓரியன்டட் புரோகிராமிங் (AOP) ஆகியவற்றை நாம் அணுகும் விதத்தை அடிப்படையில் மாற்றுகிறது.

இந்த விரிவான வழிகாட்டி டைப்ஸ்கிரிப்ட் டெக்கரேட்டர்களைப் பற்றி ஆழமாக ஆராயும், அவற்றின் இயக்கவியல், பல்வேறு வகைகள், நடைமுறைப் பயன்பாடுகள் மற்றும் சிறந்த நடைமுறைகளை ஆராயும். நீங்கள் பெரிய அளவிலான நிறுவன பயன்பாடுகள், மைக்ரோசர்வீஸ்கள் அல்லது கிளையன்ட்-சைட் வலை இடைமுகங்களை உருவாக்கினாலும், டெக்கரேட்டர்களைப் புரிந்துகொள்வது உங்களை மேலும் டிக்ளரேட்டிவ், பராமரிக்கக்கூடிய மற்றும் சக்திவாய்ந்த டைப்ஸ்கிரிப்ட் கோடை எழுத அதிகாரம் அளிக்கும்.

முக்கிய கருத்தைப் புரிந்துகொள்வது: டெக்கரேட்டர் என்றால் என்ன?

அதன் மையத்தில், டெக்கரேட்டர் என்பது ஒரு சிறப்பு வகையான அறிவிப்பு ஆகும், இது ஒரு கிளாஸ் அறிவிப்பு, மெத்தட், அக்சசர், பிராப்பர்ட்டி அல்லது பாராமீட்டருடன் இணைக்கப்படலாம். டெக்கரேட்டர்கள் தாங்கள் அலங்கரிக்கும் இலக்குக்கு ஒரு புதிய மதிப்பைத் திருப்பியளிக்கும் (அல்லது இருக்கும் ஒன்றை மாற்றும்) செயல்பாடுகள் ஆகும். அவற்றின் முதன்மை நோக்கம், அடிப்படை கோட் கட்டமைப்பை நேரடியாக மாற்றாமல், தாங்கள் இணைக்கப்பட்டுள்ள அறிவிப்பின் நடத்தையை மாற்றுவது அல்லது மெட்டாடேட்டாவை சேர்ப்பது ஆகும். கோடை மேம்படுத்தும் இந்த வெளிப்புற, டிக்ளரேட்டிவ் முறை நம்பமுடியாத அளவிற்கு சக்தி வாய்ந்தது.

டெக்கரேட்டர்களை உங்கள் கோடின் பகுதிகளுக்கு நீங்கள் பயன்படுத்தும் குறிப்புகள் அல்லது லேபிள்களாக நினைத்துப் பாருங்கள். இந்த லேபிள்களைப் பின்னர் உங்கள் பயன்பாட்டின் பிற பகுதிகள் அல்லது கட்டமைப்புகள் படிக்கலாம் அல்லது அவற்றின் மீது செயல்படலாம், பெரும்பாலும் இயக்க நேரத்தில் (runtime), கூடுதல் செயல்பாடு அல்லது உள்ளமைவை வழங்க.

டெக்கரேட்டரின் தொடரியல் (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) { ... }

திரும்பும் மதிப்பு:

கிளாஸ் டெக்கரேட்டர் ஒரு மதிப்பைத் திருப்பியளித்தால், அது கிளாஸ் அறிவிப்பை வழங்கப்பட்ட கன்ஸ்ட்ரக்டர் செயல்பாட்டுடன் மாற்றீடு செய்யும். இது ஒரு சக்திவாய்ந்த அம்சம், இது மிக்ஸின்கள் அல்லது கிளாஸ் மேம்பாட்டிற்கு அடிக்கடி பயன்படுத்தப்படுகிறது. எந்த மதிப்பும் திருப்பியளிக்கப்படாவிட்டால், அசல் கிளாஸ் பயன்படுத்தப்படும்.

பயன்பாட்டு வழக்குகள்:

கிளாஸ் டெக்கரேட்டர் உதாரணம்: ஒரு சேவையை இன்ஜெக்ட் செய்தல்

ஒரு எளிய டிபென்டென்சி இன்ஜெக்ஷன் சூழ்நிலையை கற்பனை செய்து பாருங்கள், அங்கு நீங்கள் ஒரு கிளாஸை "injectable" என்று குறிக்க விரும்புகிறீர்கள் மற்றும் விருப்பப்படி அதற்கு ஒரு கண்டெய்னரில் ஒரு பெயரை வழங்க விரும்புகிறீர்கள்.

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 டெக்கரேட்டர் அடிப்படை கன்ஸ்ட்ரக்டர்களிலிருந்து மெத்தட்கள் மற்றும் பிராப்பர்ட்டிகளை டெரைவ்டு கிளாஸின் புரோட்டோடைப்பிற்கு டைனமிக்காக நகலெடுக்கிறது, திறம்பட செயல்பாடுகளை "கலக்கிறது".

முடிவுரை: நவீன டைப்ஸ்கிரிப்ட் மேம்பாட்டை மேம்படுத்துதல்

டைப்ஸ்கிரிப்ட் டெக்கரேட்டர்கள் ஒரு சக்திவாய்ந்த மற்றும் வெளிப்படையான அம்சம், இது மெட்டாடேட்டா-உந்துதல் மற்றும் ஆஸ்பெக்ட்-ஓரியன்டட் புரோகிராமிங்கின் ஒரு புதிய முன்னுதாரணத்தை செயல்படுத்துகிறது. அவை டெவலப்பர்களுக்கு கிளாஸ்கள், மெத்தட்கள், பிராப்பர்ட்டிகள், அக்சசர்கள் மற்றும் பாராமீட்டர்களின் முக்கிய லாஜிக்கை மாற்றாமல் அவற்றை மேம்படுத்த, மாற்றியமைக்க மற்றும் டிக்ளரேட்டிவ் நடத்தைகளைச் சேர்க்க அனுமதிக்கின்றன. இந்த கவலைகளின் பிரிப்பு சுத்தமான, மேலும் பராமரிக்கக்கூடிய மற்றும் மிகவும் மறுபயன்பாட்டிற்குரிய கோடிற்கு வழிவகுக்கிறது.

டிபென்டென்சி இன்ஜெக்ஷனை எளிதாக்குவது மற்றும் வலுவான சரிபார்ப்பு அமைப்புகளைச் செயல்படுத்துவது முதல் லாக்கிங் மற்றும் செயல்திறன் கண்காணிப்பு போன்ற குறுக்குவெட்டு கவலைகளைச் சேர்ப்பது வரை, டெக்கரேட்டர்கள் பல பொதுவான மேம்பாட்டு சவால்களுக்கு ஒரு நேர்த்தியான தீர்வை வழங்குகின்றன. அவற்றின் சோதனை நிலை விழிப்புணர்வைக் கோரினாலும், முக்கிய கட்டமைப்புகளில் அவற்றின் பரவலான தத்தெடுப்பு அவற்றின் நடைமுறை மதிப்பையும் எதிர்கால பொருத்தத்தையும் குறிக்கிறது.

டைப்ஸ்கிரிப்ட் டெக்கரேட்டர்களில் தேர்ச்சி பெறுவதன் மூலம், உங்கள் ஆயுதக் களஞ்சியத்தில் ஒரு குறிப்பிடத்தக்க கருவியைப் பெறுகிறீர்கள், இது உங்களை மேலும் வலுவான, அளவிடக்கூடிய மற்றும் புத்திசாலித்தனமான பயன்பாடுகளை உருவாக்க உதவுகிறது. அவற்றை பொறுப்புடன் தழுவுங்கள், அவற்றின் இயக்கவியலைப் புரிந்து கொள்ளுங்கள், மற்றும் உங்கள் டைப்ஸ்கிரிப்ட் திட்டங்களில் ஒரு புதிய அளவிலான டிக்ளரேட்டிவ் சக்தியைத் திறக்கவும்.