JavaScript Prototype Inheritance & ES6 Class

প্রত্যেকটি ফাংশন এর প্রটোটাইপ থাকে।

জাভা ও অন্যান্য class বেস প্রোগ্রামিং লাঙ্গুইয়েজে আমরা অব্জেক্ট তৈরি করে থাকি class দিয়ে। কিন্তু জাভাস্ক্রিপ্ট হচ্ছে ফাংশনাল প্রোগ্রামিং ল্যাঙ্গুইয়েজ। এখানে অব্জেক্ট তৈরি হয় কন্সট্রাক্টর দিয়ে। আর জাভাস্ক্রিপ্ট প্রত্যেকটি ফাংশনই একেকটি কন্সট্রাক্টর। চলুন একটা কন্সট্রাক্টর ফাংশন নি এবং তাকে একটা ভেরিয়েবল এ রেখে console.dir() করি ।

var f = function Person() {}; console.dir(f);

দেখুন আমাদের person ফাংশন এর প্রোটোটাইপ এর মধ্যে আরেকটা প্রোটোটাইপ আছে তার মধ্যে আছে মাস্টার অব্জেক্ট অর্থাৎ Object। জাভাস্ক্রিপ্ট এ এর মাস্টার অব্জেক্ট থেকে সকল অব্জেক্ট তৈরি হয়ে থাকে। আমাদের উপরের ফাংশন ক্রিয়েশন টা যদি একটু ভিজুয়ালাইজ করি তাহলে দেখতে অনেকটা এইরকম হবে।

এখানে মাস্টার অব্জেক্ট থেকে আমাদের ফাংশন এবং আমাদের ফাংশন থেকে f ক্রিয়েট হয়েছে। এখানে প্রত্যেকটি জিনিসের prototype আছে এবং তাদের আলাদা আলাদা prototype এর নিজস্ব properties and method আছে। এই যে চেইন এর মতো বেপার টা একে বলা হয় প্রোটোটাইপ চেইন
এখন সর্বশেষ f ফাংশন থেকে যদি আমার আরেকটা অব্জেক্ট তৈরি করি তাহলে আমার অই অব্জেক্ট থেকে তার পেরেন্ট তার পেরেন্ট অর্থাৎ মাস্টার অব্জেক্ট পর্যন্ত সকল প্রোপার্টিস এবং মেথড গুলো ব্যবহার করতে পারব।
চলুন আমাদের নিজের করা একটা ফাংশন বানানো যাক যাকে আমরা মাস্টের অব্জেক্ট এ সেট করে দিবে যার ফলে আমরা তাকে যেকোনো স্থান থেকে তাকে ব্যবহার করতে পারব।

Object.prototype.myLog = function () { console.log('This is my home made log'); }; let p = {}; p.myLog(); // output - This is my home made log

তাহলে বেপার বুঝা যাচ্ছে। এটা হচ্ছে প্রোটোটাইপ চেইন এর কারনে, p ফাংশন তার স্পেস না পেয়ে তার পেরেন্ট থেকে ইনহেরিট করে আমাদের দিয়ে দিচ্ছে।

Inheritance

চলুন আজকের মেইন টপিক ইনহেরিটেন্স এ যাওয়া যাক। আসলে ইনহেরিটেন্স এ যাবার আগে জাভাস্ক্রিপ্ট এর এই প্রোটোটাইপ টা জানা খুবই গুরুত্বপূর্ণ কারন ব্যাকেন্ড এ প্রোটোটাইপ দিয়ে সব হচ্ছে।

Usecase of inheritance

ধরুন আমাদের একটা person অব্জেক্ট লাগবে তো আমরা তৈরি করলাম।

function Person(name, age) { this.name = name; this.age = age; } Person.prototype = { eat() { console.log(`${this.name} am eating`); }, }; const sakib = new Person('Sakib', 35);

এখন আমাদের আরেকটি অব্জেক্ট লাগবে ক্রিকেটার।

function Cricketer(type, country) { this.type = type; this.country = country; }

এখন দিনশেষে ক্রিকেটারও তো একজন Person এখন আমরা কি করব Person থেকে ইনহেরিট করে ক্রিকেটার অব্জেক্ট এ Person সকল বৈশিষ্ট্য গুলো নিয়ে এনে ব্যবহার করব। এটাই হচ্ছে ইনহেরিটেন্স এখন এটা জাভাস্ক্রিপ্ট এ একরকম এবং জাভা অন্যান্য লাঙ্গুইয়েজে অন্য রকম তাই জাভাস্ক্রিপ্ট এ আমাদের কে তার ওয়েতে চিন্তা করতে হবে।

এখন চলুন Person থেকে ইনহেরিট করে ক্রিকেটার অব্জেক্ট এ Person সকল বৈশিষ্ট্য গুলো ব্যবহার করার কানেকশন টা করে নিই।

function Person(name, age) { this.name = name; this.age = age; } function Cricketer(name, age, type, country) { Person.call(this); this.name = name; this.age = age; this.type = type; this.country = country; } Person.prototype = { eat() { console.log(`${this.name} am eating`); }, }; const sakib = new Person('Sakib', 35);

যেহেতু আমরা Cricketer এর মধ্যে Person কে পেতে চাচ্ছি। তাই আমরা CricketerPerson কে Person.call() করে দিবো এবং সাথে this কে পাঠায় দিবো। এই this হচ্ছে Cricketer এর সকল ডাটা।

এখন এই কাজ টা করে আমরা Person এর সকল ইনহেরিট করে নিয়ে আসলাম কিন্তু Person.prototype এর কোনো কিছু নিয়ে আশা হলো না চলুন এই কাজটা Object.create() দিয়ে করে ফেলে। কারন আমরা জানি Object.create() কি করে তাকে দেওয়া কোনো অব্জেক্টকে বেস করে সে আরেকটা চাইল্ড অব্জেক্ট বানায় যা আমাদের দিয়ে অব্জেক্ট থেকে অ্যাক্সেস পায়। চলুন এখানে Person.prototype কে Cricketer.prototype এর প্রোটোটাইপ এ রেখে দিই।

Cricketer.prototype = Object.create(Person.prototype);

এখানে আমাদের আরেকটি কাজ করতে হবে সেটা হলো আমরা জানি জাভাস্ক্রিপ্ট এ অব্জেক্ট ক্রিয়েট হয় কন্সট্রাক্টর এর মাধ্যমে এখানে Cricketer এর কন্সট্রাক্টর এ যে আমরা Person.call(this) করে Person এর ডাটা গুলো আনলাম এটা তো Cricketer জানে না তাই আমাদেরকে Cricketer এর কন্সট্রাক্টর কে অভাররাইট করতে হবে। এভাবে,

Cricketer.prototype.constructor = Cricketer;

এবার যদি সাকিব একটা অব্জেক্ট বানাই।

const sakib = new Cricketer('Sakib', 34, 'Cricketer', 'Bangladesh'); console.log(sakib.name); // output - Sakib consol.log(sakib.eat()); //output - sakib is eating

Summary

যদি আমরা প্রোটোটাইপ এর মাধ্যমে কোনো পেরেন্ট অব্জেক্ট থেকে অন্য অন্য অব্জেক্ট এই ক্ষেত্রে এটা চাইল্ড বলা যায়, সকল কিছু ইনহেরিট করে নিয়ে আসতে চাই তাহলে আমাদের যা যা করতে হবে।

যেখানে ইনহেরিট করতে চাই সেখানে

  • childObject.call(this) করতে হবে।
  • তার কন্সট্রাক্টর কে আপডেট করতে হবে।
  • প্রোটোটাইপ এর মধ্যে পেরেন্ট এর প্রোটোটাইপ কানেকশন করে দিতে হবে।

ইন দ্যা ব্যাকেন্ড জাভাস্ক্রিপ্ট এভাবে ইনহেরিট করে থাকে। আসলে আমার কোডিং এর সময় এভাবে করবো না। জাভাস্ক্রিপ্ট Es6 থেকে Class সাপোর্ট করে যা আর কিছুই না জাস্ট সিনট্যাক্সটেকটিক্স সুগার মাত্র। আমার Class ব্যবহার করলেও জাভাস্ক্রিপ্ট এভাবেই কাজ করে। চলুন দেখা যাক, জাভাস্ক্রিপ্ট এ কিভাবে সহজ ভাবে Class ব্যবহার করে উপরের কাজটি করা যায়।

class Person { constructor(name, age) { this.name = name; this.age = age; } eat() { console.log(`${this.name} is eating`); } } class Cricketer extends Person { constructor(name, age, type, country) { super(name, age); this.name = name; this.age = age; this.type = type; this.country = country; } play() { console.log(`${this.name} is playing`); } } const sakib = new Cricketer('Sakib', 34, 'Cricketer', 'Bangladesh'); console.log(sakib.name); // output - Sakib consol.log(sakib.eat()); //output - sakib is eating

এখানে সিম্পল ক্লাস এর সিনট্যাক্স্টেক্স দিয়ে কনর্ভাট করা হয়েছে। এখানে ক্লাস সিনট্যাক্স্টেক্স বুঝানো আমার উদ্দেশ্য নয় আপনারা class syntax এখানে থেকে শিখে নিতে পারেন।

এখানে বুঝার বেপার হলো class Cricketer extends Person এভাবে Person থেকে ইনহেরিট করা হলো। যেটা কে ক্লাস এর ভাষায় এক্সটেন্ড করা ও বলে। এবং Person কে Cricketer এ কল করার জন্য Cricketer এর মধ্যে super কে কল করে দেওয়া হয়। এভাবে জাভাস্ক্রিপ্ট এ ক্লাস ব্যবহার করে ইনহেরিটেন্স করা হয়।

getter & setter

class Person { constructor(name, age) { this.name = name; this.age = age; } eat() { console.log(`${this.name} is eating`); } get getName() { //getter return this.name; } set getName(name) { //setter return (this.name = name); } } const sakib = new Person('Sakib', 34); console.log(sakib.getName); //output - Sakib sakib.getName = 'Sakib Al Hasan'; console.log(sakib.getName); //output - Sakib Al Hasan

getter তেমন কিছুই না কন্সট্রাক্টর অব্জেক্ট থেকে কোনো প্রোপার্ট অ্যাক্সেস করার জন্য ব্যবহার করা হয়। setter এর মাধ্যমে আমরা কন্সট্রাক্টর অব্জেক্ট এর কোনো ভ্যালু চেঞ্জড করে দিতে পারি। কিন্তু এগুলো ফাংশন এর মতো দেখতে হলেও এগুলো ইনবোক করার সিস্টেম টা কিন্তু আলাদা। উপরের console.log() গুলো দেখুন।

তাহলে প্রশ্ন আসতে পারে যে তাহলে কেন আমরা এগুলো ব্যবহার করব।
ফাংশন আর getter & setter এর মধ্যে পার্থক্য কি?

  • এটি সহজ সিনট্যাক্স 😁
  • এটি প্রোপার্ট এবং মেথড এর জন্য একই সিনট্যাক্স লিখতে দেয়।
  • এটি আরও ভাল ডেটা গুণমান সুরক্ষিত করতে পারে।
  • এটি প্রাইভেট প্রোপার্টি নিয়ে কাজ করার জন্য দরকারী

Static Method

এটি ও একটি ফাংশন কিন্তু এটা ইনিশিয়েট হয় Class এর মধ্যে উপরের কোড অনুসারে Person ক্লাস এ সেট হবে। চলুন static দিয়ে একটা ফাংশন তৈরি করি।

class Person { constructor(name, age) { this.name = name; this.age = age; } eat() { console.log(`${this.name} is eating`); } static isEqualAge() { console.log('I am static equation'); } } const sakib = new Person('Sakib', 34); console.log(Person.isEqualAge()); //output - I am static equation

অর্থাৎ static মেথড গুলো আমাদের অব্জেক্ট এর সাথে কোনো সংযোগ নাই এটা কমপ্লিটলি আলাদা। কিন্তু প্যারামিটার আকারে আমরা আবার আমাদের তৈরি করা অব্জেক্ট কে পাঠাইতে পারি।

class Person { constructor(name, age) { this.name = name; this.age = age; } eat() { console.log(`${this.name} is eating`); } static isEqualAge(cricketer1) { return cricketer1.age; } } const sakib = new Person('Sakib', 34); console.log(Person.isEqualAge(sakib)); //output - 34

আমরা যদি চেষ্ট্রা করি যে isEqualAge এর মধ্যে যদি this.name দেই তাহলে কি আমার name পাবো? নাহ তখন আমরা পাবো Person কে। তাই বলা যায় static একটি সম্পূর্ন আলাদা একটি ফাংশন। যাকে ইনবোক করতে হলে Person.something() করতে হবে।

Polymorphism

এখানে জাভাস্ক্রিপ্ট Class এ Polymorphism বলতে আর তেমন কিছুই না। একটি ফিচার বলা যায়। চলুন একটা Class Constructor দিয়ে একটা অব্জেক্ট ব্লু প্রিন্ট তৈরি করা যাক।

class Person { constructor(name, age) { this.name = name; this.age = age; } play() { console.log(`${this.name} is playing`); } } class Cricketer extends Person { constructor(name, age, type, country) { super(name, age); this.name = name; this.age = age; this.type = type; this.country = country; } } const sakib = new Cricketer('Sakib', 34, 'Cricketer', 'Bangladesh'); sakib.play(); //output - sakib is playing

ফিচারটা হলো Person ক্লাস এ যে play() মেথড আছে এটাকে আমরা চাচ্ছি Cricketer এ একটি মডিফাই ভাবে ব্যবহার করতে। এখানে পেরেন্ট এর মেথড গুলোকে চাইল্ড এ মডিফাই ভাবে ব্যবহার করা কে Polymorphism বলা হয়।

class Person { constructor(name, age) { this.name = name; this.age = age; } play() { console.log(`${this.name} is playing`); } } class Cricketer extends Person { constructor(name, age, type, country) { super(name, age); this.name = name; this.age = age; this.type = type; this.country = country; } play() { console.log(`${this.name} is playing cricket`); } } const sakib = new Cricketer('Sakib', 34, 'Cricketer', 'Bangladesh'); sakib.play(); //output - sakib is playing cricket

কিন্তু এখানে একটি প্রব্লেম হয়ে যায় আমাদের Person এর play() মেথডটাও অভাররাইড হয়ে যায়। এখন আমরা দেখবে কিভাবে Person এবং Cricketer দুইটা দুই ভাবে হ্যান্ডেল করা যায় মানে কিভাবে দুইটা ফাংশন কেই রাখা যায়। তাহলে এমন কিছুই না জাস্ট প্রথম এ পেরেন্ট থেকে চাইল্ড ফাংশন এ super.play() কল করে দিলেই হবে।

class Person { constructor(name, age) { this.name = name; this.age = age; } play() { console.log(`${this.name} is playing`); } } class Cricketer extends Person { constructor(name, age, type, country) { super(name, age); this.name = name; this.age = age; this.type = type; this.country = country; } play() { super.play(); console.log(`${this.name} is playing cricket`); } } const sakib = new Cricketer('Sakib', 34, 'Cricketer', 'Bangladesh'); sakib.play(); //output - sakib is playing //output - sakib is plaing cricket

আউটপুট দুইটাই আসবে।

Polymorphism হলো একটা কন্সেপ্ট বা ফিচার বলা যায় যার মাধ্যমে যদি আমার কোনো থার্ড পার্টি প্যাকেজ ব্যবহার করি এবং তার থেকে এক্সটেন্ড করে আমরা কোনো ফাংশন কে মডিফাই করে ব্যবহার করতে চাই তখন এই Polymorphism কন্সেপ্ট এর মাধ্যমে জাভাস্ক্রিপ্ট কাজটি করে থাকে।