মেটাডেটা প্রোগ্রামিং, অ্যাস্পেক্ট-অরিয়েন্টেড প্রোগ্রামিং, এবং ডিক্লেয়ারেটিভ প্যাটার্নের মাধ্যমে কোড উন্নত করতে টাইপস্ক্রিপ্ট ডেকোরেটরের শক্তি অন্বেষণ করুন। বিশ্বব্যাপী ডেভেলপারদের জন্য একটি সম্পূর্ণ নির্দেশিকা।
টাইপস্ক্রিপ্ট ডেকোরেটরস: শক্তিশালী অ্যাপ্লিকেশনের জন্য মেটাডেটা প্রোগ্রামিং প্যাটার্নে দক্ষতা অর্জন
আধুনিক সফটওয়্যার ডেভেলপমেন্টের বিশাল জগতে, পরিষ্কার, স্কেলেবল এবং পরিচালনাযোগ্য কোডবেস বজায় রাখা অত্যন্ত গুরুত্বপূর্ণ। টাইপস্ক্রিপ্ট, তার শক্তিশালী টাইপ সিস্টেম এবং উন্নত বৈশিষ্ট্যগুলির মাধ্যমে ডেভেলপারদের এটি অর্জনের জন্য টুলস সরবরাহ করে। এর সবচেয়ে আকর্ষণীয় এবং রূপান্তরকারী বৈশিষ্ট্যগুলির মধ্যে একটি হলো ডেকোরেটরস। লেখার সময় এটি এখনও একটি পরীক্ষামূলক বৈশিষ্ট্য (ECMAScript-এর জন্য স্টেজ 3 প্রস্তাবনা) হলেও, Angular এবং TypeORM-এর মতো ফ্রেমওয়ার্কগুলিতে ডেকোরেটর ব্যাপকভাবে ব্যবহৃত হয়, যা ডিজাইন প্যাটার্ন, মেটাডেটা প্রোগ্রামিং এবং অ্যাস্পেক্ট-অরিয়েন্টেড প্রোগ্রামিং (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
(ক্লাস কনস্ট্রাক্টর) তার আর্গুমেন্ট হিসাবে গ্রহণ করে। এটি ডেকোরেটরের আচরণের ডাইনামিক কনফিগারেশনের অনুমতি দেয়।
টাইপস্ক্রিপ্টে ডেকোরেটরের প্রকারভেদ
টাইপস্ক্রিপ্ট পাঁচটি স্বতন্ত্র ধরনের ডেকোরেটর সমর্থন করে, যার প্রতিটি একটি নির্দিষ্ট ধরণের ডিক্লেয়ারেশনের জন্য প্রযোজ্য। ডেকোরেটর ফাংশনের সিগনেচার এটি কোন প্রসঙ্গে প্রয়োগ করা হয়েছে তার উপর ভিত্তি করে পরিবর্তিত হয়।
১. ক্লাস ডেকোরেটর
ক্লাস ডেকোরেটর ক্লাস ডিক্লেয়ারেশনে প্রয়োগ করা হয়। ডেকোরেটর ফাংশনটি তার একমাত্র আর্গুমেন্ট হিসেবে ক্লাসের কনস্ট্রাক্টর গ্রহণ করে। একটি ক্লাস ডেকোরেটর একটি ক্লাস সংজ্ঞা পর্যবেক্ষণ, পরিবর্তন বা এমনকি প্রতিস্থাপন করতে পারে।
সিগনেচার:
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}`);
// ঐচ্ছিকভাবে, আপনি এখানে আচরণ বাড়ানোর জন্য একটি নতুন ক্লাস রিটার্ন করতে পারেন
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
ডেকোরেটর ক্লাসটিকে একটি তাত্ত্বিক ডিপেন্ডেন্সি ইনজেকশন সিস্টেম দ্বারা আবিষ্কারযোগ্য করে তোলে।
২. মেথড ডেকোরেটর
মেথড ডেকোরেটর মেথড ডিক্লেয়ারেশনে প্রয়োগ করা হয়। এগুলি তিনটি আর্গুমেন্ট গ্রহণ করে: টার্গেট অবজেক্ট (স্ট্যাটিক মেম্বারদের জন্য, কনস্ট্রাক্টর ফাংশন; ইনস্ট্যান্স মেম্বারদের জন্য, ক্লাসের প্রোটোটাইপ), মেথডের নাম এবং মেথডের প্রপার্টি ডেসক্রিপ্টর।
সিগনেচার:
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)-এর একটি ক্লাসিক উদাহরণ।
৩. অ্যাক্সেসর ডেকোরেটর
অ্যাক্সেসর ডেকোরেটর অ্যাক্সেসর (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
গেটারের গণনা কেবল একবারই চলে, পরবর্তী কলগুলি ক্যাশ করা মান রিটার্ন করে। এই প্যাটার্নটি পারফরম্যান্স অপ্টিমাইজ করার জন্য খুব দরকারী যেখানে প্রপার্টি অ্যাক্সেসে ভারী গণনা বা বাহ্যিক কল জড়িত থাকে।
৪. প্রপার্টি ডেকোরেটর
প্রপার্টি ডেকোরেটর প্রপার্টি ডিক্লেয়ারেশনে প্রয়োগ করা হয়। এগুলি দুটি আর্গুমেন্ট গ্রহণ করে: টার্গেট অবজেক্ট (স্ট্যাটিক মেম্বারদের জন্য, কনস্ট্রাক্টর ফাংশন; ইনস্ট্যান্স মেম্বারদের জন্য, ক্লাসের প্রোটোটাইপ), এবং প্রপার্টির নাম।
সিগনেচার:
function PropertyDecorator(target: Object, propertyKey: string | symbol) { ... }
রিটার্ন ভ্যালু:
প্রপার্টি ডেকোরেটর কোনো মান রিটার্ন করতে পারে না। তাদের মূল ব্যবহার হলো প্রপার্টি সম্পর্কে মেটাডেটা নিবন্ধন করা। তারা ডেকোরেশনের সময় সরাসরি প্রপার্টির মান বা তার ডেসক্রিপ্টর পরিবর্তন করতে পারে না, কারণ প্রপার্টি ডেকোরেটরগুলি যখন চলে তখন একটি প্রপার্টির জন্য ডেসক্রিপ্টর এখনও সম্পূর্ণরূপে সংজ্ঞায়িত হয় না।
ব্যবহারের ক্ষেত্র:
- সিরিয়ালাইজেশন/ডিসিরিয়ালাইজেশনের জন্য প্রপার্টি নিবন্ধন করা।
- প্রপার্টিতে বৈধতা নিয়ম প্রয়োগ করা।
- প্রপার্টির জন্য ডিফল্ট মান বা কনফিগারেশন সেট করা।
- ORM (অবজেক্ট-রিলেশনাল ম্যাপিং) কলাম ম্যাপিং (যেমন, TypeORM-এ
@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
ফাংশন তারপর এই মেটাডেটা ব্যবহার করে রানটাইমে ইনস্ট্যান্সটি পরীক্ষা করে। এই প্যাটার্নটি ডেটা সংজ্ঞা থেকে বৈধতা লজিককে আলাদা করে, এটিকে পুনঃব্যবহারযোগ্য এবং পরিষ্কার করে তোলে।
৫. প্যারামিটার ডেকোরেটর
প্যারামিটার ডেকোরেটর ক্লাস কনস্ট্রাক্টর বা একটি মেথডের মধ্যে প্যারামিটারে প্রয়োগ করা হয়। এগুলি তিনটি আর্গুমেন্ট গ্রহণ করে: টার্গেট অবজেক্ট (স্ট্যাটিক মেম্বারদের জন্য, কনস্ট্রাক্টর ফাংশন; ইনস্ট্যান্স মেম্বারদের জন্য, ক্লাসের প্রোটোটাইপ), মেথডের নাম (অথবা কনস্ট্রাক্টর প্যারামিটারদের জন্য undefined
), এবং ফাংশনের প্যারামিটার তালিকায় প্যারামিটারের ক্রমিক সূচক।
সিগনেচার:
function ParameterDecorator(target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) { ... }
রিটার্ন ভ্যালু:
প্যারামিটার ডেকোরেটর কোনো মান রিটার্ন করতে পারে না। প্রপার্টি ডেকোরেটরের মতো, তাদের মূল ভূমিকা হলো প্যারামিটার সম্পর্কে মেটাডেটা যুক্ত করা।
ব্যবহারের ক্ষেত্র:
- ডিপেন্ডেন্সি ইনজেকশনের জন্য প্যারামিটার টাইপ নিবন্ধন করা (যেমন, Angular-এ
@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("\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"]; // বর্তমান ব্যবহারকারীর ভূমিকা আনার সিমুলেশন
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("\n--- Calling deleteSensitiveData (Admin User) ---");
service.deleteSensitiveData("record123");
} catch (error: any) {
console.error(error.message);
}
try {
console.log("\n--- 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);
/* পূর্বাভাসিত আউটপুট অর্ডার (আনুমানিক, নির্দিষ্ট TypeScript সংস্করণ এবং কম্পাইলেশনের উপর নির্ভর করে):
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
পলিফিলের সাথে মিলিত হয়ে, মেটাডেটা-চালিত প্রোগ্রামিংয়ের একটি নতুন জগৎ খুলে দেয়। এটি শক্তিশালী ডিজাইন প্যাটার্নের অনুমতি দেয় যা বয়লারপ্লেট এবং ক্রস-কাটিং উদ্বেগগুলিকে বিমূর্ত করে।
১. ডিপেন্ডেন্সি ইনজেকশন (DI)
ডেকোরেটরের সবচেয়ে বিশিষ্ট ব্যবহারগুলির মধ্যে একটি হলো ডিপেন্ডেন্সি ইনজেকশন ফ্রেমওয়ার্কে (যেমন Angular-এর @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("\n--- Resolving AuthService (should be cached) ---");
const authService = Container.resolve(AuthService);
authService.login();
এই বিস্তারিত উদাহরণটি দেখায় যে কীভাবে @Injectable
এবং @Inject
ডেকোরেটরগুলি, reflect-metadata
-এর সাথে মিলিত হয়ে, একটি কাস্টম Container
-কে স্বয়ংক্রিয়ভাবে নির্ভরতা সমাধান এবং সরবরাহ করতে দেয়। design:paramtypes
মেটাডেটা যা TypeScript দ্বারা স্বয়ংক্রিয়ভাবে নির্গত হয় (যখন emitDecoratorMetadata
সত্য থাকে) এখানে অত্যন্ত গুরুত্বপূর্ণ।
২. অ্যাস্পেক্ট-অরিয়েন্টেড প্রোগ্রামিং (AOP)
AOP ক্রস-কাটিং উদ্বেগগুলিকে (যেমন, লগিং, নিরাপত্তা, লেনদেন) মডিউলারাইজ করার উপর দৃষ্টি নিবদ্ধ করে যা একাধিক ক্লাস এবং মডিউল জুড়ে বিস্তৃত। ডেকোরেটরগুলি TypeScript-এ 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
ডেকোরেটর লগিংয়ের ক্রস-কাটিং উদ্বেগটি পরিচালনা করে।
৩. ভ্যালিডেশন এবং ট্রান্সফরমেশন
ডেকোরেটরগুলি সরাসরি প্রপার্টিতে ভ্যালিডেশন নিয়ম সংজ্ঞায়িত করার জন্য বা সিরিয়ালাইজেশন/ডিসিরিয়ালাইজেশনের সময় ডেটা রূপান্তর করার জন্য অবিশ্বাস্যভাবে দরকারী।
উদাহরণ: প্রপার্টি ডেকোরেটর দিয়ে ডেটা ভ্যালিডেশন
আগের @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."]
এই সেটআপটি আপনাকে আপনার মডেল প্রপার্টিতে ঘোষণামূলকভাবে ভ্যালিডেশন নিয়ম সংজ্ঞায়িত করতে দেয়, যা আপনার ডেটা মডেলগুলিকে তাদের সীমাবদ্ধতার পরিপ্রেক্ষিতে স্ব-বর্ণনামূলক করে তোলে।
সেরা অনুশীলন এবং বিবেচ্য বিষয়
যদিও ডেকোরেটরগুলি শক্তিশালী, তাদের বিচক্ষণতার সাথে ব্যবহার করা উচিত। তাদের অপব্যবহার করলে এমন কোড তৈরি হতে পারে যা ডিবাগ করা বা বোঝা কঠিন।
কখন ডেকোরেটর ব্যবহার করবেন (এবং কখন নয়)
- এদের জন্য ব্যবহার করুন:
- ক্রস-কাটিং উদ্বেগ: লগিং, ক্যাশিং, অনুমোদন, লেনদেন ব্যবস্থাপনা।
- মেটাডেটা ঘোষণা: ORM-এর জন্য স্কিমা সংজ্ঞায়িত করা, ভ্যালিডেশন নিয়ম, DI কনফিগারেশন।
- ফ্রেমওয়ার্ক ইন্টিগ্রেশন: যখন মেটাডেটা ব্যবহার করে এমন ফ্রেমওয়ার্ক তৈরি বা ব্যবহার করা হয়।
- বয়লারপ্লেট কমানো: পুনরাবৃত্তিমূলক কোড প্যাটার্নগুলিকে বিমূর্ত করা।
- এদের জন্য এড়িয়ে চলুন:
- সাধারণ ফাংশন কল: যদি একটি সাধারণ ফাংশন কল একই ফলাফল স্পষ্টভাবে অর্জন করতে পারে, তবে সেটি পছন্দ করুন।
- ব্যবসায়িক যুক্তি: ডেকোরেটরগুলির উচিত মূল ব্যবসায়িক যুক্তিকে বাড়ানো, সংজ্ঞায়িত করা নয়।
- অতিরিক্ত জটিলতা: যদি একটি ডেকোরেটর ব্যবহার করলে কোড কম পঠনযোগ্য বা পরীক্ষা করা কঠিন হয়ে যায়, তবে পুনর্বিবেচনা করুন।
পারফরম্যান্সের প্রভাব
ডেকোরেটরগুলি কম্পাইল-টাইমে (অথবা ট্রান্সপাইল করা হলে জাভাস্ক্রিপ্ট রানটাইমে সংজ্ঞা-সময়ে) কার্যকর হয়। রূপান্তর বা মেটাডেটা সংগ্রহ ঘটে যখন ক্লাস/মেথড সংজ্ঞায়িত করা হয়, প্রতিটি কলে নয়। অতএব, ডেকোরেটর *প্রয়োগ করার* রানটাইম পারফরম্যান্স প্রভাব ন্যূনতম। তবে, আপনার ডেকোরেটরের *ভেতরের যুক্তি* পারফরম্যান্সের উপর প্রভাব ফেলতে পারে, বিশেষ করে যদি তারা প্রতিটি মেথড কলে ব্যয়বহুল অপারেশন সম্পাদন করে (যেমন, একটি মেথড ডেকোরেটরের মধ্যে জটিল গণনা)।
রক্ষণাবেক্ষণযোগ্যতা এবং পঠনযোগ্যতা
ডেকোরেটরগুলি, যখন সঠিকভাবে ব্যবহার করা হয়, তখন মূল যুক্তি থেকে বয়লারপ্লেট কোড সরিয়ে পঠনযোগ্যতা উল্লেখযোগ্যভাবে উন্নত করতে পারে। তবে, যদি তারা জটিল, লুকানো রূপান্তর সম্পাদন করে, তবে ডিবাগিং চ্যালেঞ্জিং হতে পারে। নিশ্চিত করুন যে আপনার ডেকোরেটরগুলি ভালভাবে নথিভুক্ত এবং তাদের আচরণ অনুমানযোগ্য।
পরীক্ষামূলক অবস্থা এবং ডেকোরেটরের ভবিষ্যৎ
এটি পুনরাবৃত্তি করা গুরুত্বপূর্ণ যে TypeScript ডেকোরেটরগুলি একটি স্টেজ 3 TC39 প্রস্তাবনার উপর ভিত্তি করে। এর মানে হল স্পেসিফিকেশনটি মূলত স্থিতিশীল তবে অফিসিয়াল ECMAScript স্ট্যান্ডার্ডের অংশ হওয়ার আগে এখনও ছোটখাটো পরিবর্তন হতে পারে। Angular-এর মতো ফ্রেমওয়ার্কগুলি এগুলিকে গ্রহণ করেছে, তাদের চূড়ান্ত মানককরণের উপর বাজি ধরে। এটি একটি নির্দিষ্ট স্তরের ঝুঁকি বোঝায়, যদিও তাদের ব্যাপক গ্রহণের কারণে, উল্লেখযোগ্য ব্রেকিং পরিবর্তনগুলি অসম্ভাব্য।
TC39 প্রস্তাবনাটি বিকশিত হয়েছে। TypeScript-এর বর্তমান বাস্তবায়ন প্রস্তাবনার একটি পুরানো সংস্করণের উপর ভিত্তি করে। একটি "লেগাসি ডেকোরেটর" বনাম "স্ট্যান্ডার্ড ডেকোরেটর" পার্থক্য রয়েছে। যখন অফিসিয়াল স্ট্যান্ডার্ড আসবে, তখন TypeScript সম্ভবত তার বাস্তবায়ন আপডেট করবে। বেশিরভাগ ডেভেলপারদের জন্য যারা ফ্রেমওয়ার্ক ব্যবহার করেন, এই রূপান্তরটি ফ্রেমওয়ার্ক নিজেই পরিচালনা করবে। লাইব্রেরি লেখকদের জন্য, লেগাসি এবং ভবিষ্যতের স্ট্যান্ডার্ড ডেকোরেটরগুলির মধ্যে সূক্ষ্ম পার্থক্য বোঝা প্রয়োজন হতে পারে।
emitDecoratorMetadata
কম্পাইলার অপশন
এই অপশনটি, যখন tsconfig.json
-এ true
সেট করা হয়, তখন TypeScript কম্পাইলারকে নির্দিষ্ট ডিজাইন-টাইম টাইপ মেটাডেটা কম্পাইল করা জাভাস্ক্রিপ্টে নির্গত করার নির্দেশ দেয়। এই মেটাডেটাতে কনস্ট্রাক্টর প্যারামিটারের টাইপ (design:paramtypes
), মেথডের রিটার্ন টাইপ (design:returntype
), এবং প্রপার্টির টাইপ (design:type
) অন্তর্ভুক্ত থাকে।
এই নির্গত মেটাডেটা স্ট্যান্ডার্ড জাভাস্ক্রিপ্ট রানটাইমের অংশ নয়। এটি সাধারণত reflect-metadata
পলিফিল দ্বারা ব্যবহৃত হয়, যা তারপর এটিকে Reflect.getMetadata()
ফাংশনের মাধ্যমে অ্যাক্সেসযোগ্য করে তোলে। এটি ডিপেন্ডেন্সি ইনজেকশনের মতো উন্নত প্যাটার্নগুলির জন্য একেবারে গুরুত্বপূর্ণ, যেখানে একটি কন্টেইনারকে স্পষ্ট কনফিগারেশন ছাড়াই একটি ক্লাসের প্রয়োজনীয় নির্ভরতার টাইপগুলি জানতে হবে।
ডেকোরেটর সহ উন্নত প্যাটার্ন
ডেকোরেটরগুলিকে আরও পরিশীলিত প্যাটার্ন তৈরি করতে একত্রিত এবং প্রসারিত করা যেতে পারে।
১. ডেকোরেটরকে ডেকোরেট করা (হায়ার-অর্ডার ডেকোরেটর)
আপনি এমন ডেকোরেটর তৈরি করতে পারেন যা অন্যান্য ডেকোরেটরগুলিকে পরিবর্তন বা রচনা করে। এটি কম সাধারণ তবে ডেকোরেটরের কার্যকরী প্রকৃতি প্রদর্শন করে।
// একটি ডেকোরেটর যা নিশ্চিত করে যে একটি মেথড লগ করা হয়েছে এবং অ্যাডমিন ভূমিকারও প্রয়োজন
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
একটি ফ্যাক্টরি যা একটি ডেকোরেটর রিটার্ন করে, এবং সেই ডেকোরেটরের ভিতরে, এটি অন্য দুটি ডেকোরেটর প্রয়োগ করে। এই প্যাটার্নটি জটিল ডেকোরেটর কম্পোজিশনগুলিকে এনক্যাপসুলেট করতে পারে।
২. মিক্সিনের জন্য ডেকোরেটর ব্যবহার করা
যদিও TypeScript মিক্সিন বাস্তবায়নের অন্যান্য উপায় সরবরাহ করে, ডেকোরেটরগুলি ঘোষণামূলকভাবে ক্লাসগুলিতে ক্ষমতা ইনজেক্ট করতে ব্যবহার করা যেতে পারে।
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
ডেকোরেটরটি বেস কনস্ট্রাক্টর থেকে প্রাপ্ত ক্লাসের প্রোটোটাইপে মেথড এবং প্রপার্টিগুলি গতিশীলভাবে কপি করে, কার্যকরভাবে কার্যকারিতাগুলিকে "মিক্স ইন" করে।
উপসংহার: আধুনিক টাইপস্ক্রিপ্ট ডেভেলপমেন্টকে ক্ষমতায়ন করা
টাইপস্ক্রিপ্ট ডেকোরেটরগুলি একটি শক্তিশালী এবং অভিব্যক্তিপূর্ণ বৈশিষ্ট্য যা মেটাডেটা-চালিত এবং অ্যাস্পেক্ট-অরিয়েন্টেড প্রোগ্রামিংয়ের একটি নতুন দৃষ্টান্ত সক্ষম করে। তারা ডেভেলপারদের ক্লাস, মেথড, প্রপার্টি, অ্যাক্সেসর এবং প্যারামিটারগুলিতে তাদের মূল যুক্তি পরিবর্তন না করে ঘোষণামূলক আচরণ যোগ, পরিবর্তন এবং উন্নত করতে দেয়। উদ্বেগের এই পৃথকীকরণ পরিষ্কার, আরও রক্ষণাবেক্ষণযোগ্য এবং অত্যন্ত পুনঃব্যবহারযোগ্য কোডের দিকে পরিচালিত করে।
ডিপেন্ডেন্সি ইনজেকশন সহজ করা এবং শক্তিশালী ভ্যালিডেশন সিস্টেম বাস্তবায়ন করা থেকে শুরু করে লগিং এবং পারফরম্যান্স মনিটরিংয়ের মতো ক্রস-কাটিং উদ্বেগ যোগ করা পর্যন্ত, ডেকোরেটরগুলি অনেক সাধারণ ডেভেলপমেন্ট চ্যালেঞ্জের একটি মার্জিত সমাধান সরবরাহ করে। যদিও তাদের পরীক্ষামূলক অবস্থা সচেতনতার দাবি রাখে, প্রধান ফ্রেমওয়ার্কগুলিতে তাদের ব্যাপক গ্রহণ তাদের ব্যবহারিক মূল্য এবং ভবিষ্যতের প্রাসঙ্গিকতা নির্দেশ করে।
টাইপস্ক্রিপ্ট ডেকোরেটরে দক্ষতা অর্জন করে, আপনি আপনার অস্ত্রাগারে একটি গুরুত্বপূর্ণ টুল লাভ করেন, যা আপনাকে আরও শক্তিশালী, স্কেলেবল এবং বুদ্ধিমান অ্যাপ্লিকেশন তৈরি করতে সক্ষম করে। তাদের দায়িত্বের সাথে গ্রহণ করুন, তাদের কার্যকারিতা বুঝুন এবং আপনার টাইপস্ক্রিপ্ট প্রকল্পগুলিতে ঘোষণামূলক শক্তির একটি নতুন স্তর আনলক করুন।