ট্র্যাক
/
JavaScript
JavaScript
/
অনুশীলনী
/
উইন্ডোইং সিস্টেম
উইন্ডোইং সিস্টেম

উইন্ডোইং সিস্টেম

লার্নিং অনুশীলনী

ভূমিকা

JavaScript-এ অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিংয়ের (OOP) সুবিধা আছে। OOP-তে আপনি "টেমপ্লেট" (ক্লাস) থেকে অবজেক্ট (ইনস্ট্যান্স) তৈরি করতে চান, যাতে সেগুলোর মধ্যে নির্দিষ্ট কিছু ডেটা ও কার্যকারিতা থাকে। OOP-র প্রসঙ্গে ডেটার প্রপার্টিগুলোকে ফিল্ড বলা হয়, আর ফাংশনের প্রপার্টিগুলোকে বলা হয় মেথড।

২০১৫ সালে ভাষার স্পেসিফিকেশনে ক্লাস যুক্ত হওয়ার আগে JavaScript-এ ক্লাস বলতে কিছুই ছিল না, তবে প্রোটোটাইপ-ভিত্তিক ইনহেরিটেন্স ব্যবহার করে অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং করা যেত। আর আজকাল class কিওয়ার্ড থাকলেও JavaScript এখনও একটি প্রোটোটাইপ-ভিত্তিক ভাষা।

প্রোটোটাইপ-ভিত্তিক ভাষা হওয়ার অর্থ কী এবং JavaScript আসলে কীভাবে কাজ করে তা বুঝতে আমরা ফিরে যাব সেই সময়ে, যখন ক্লাস বলে কিছুই ছিল না।

প্রোটোটাইপ সিনট্যাক্স

কনস্ট্রাক্টর ফাংশন

JavaScript-এ টেমপ্লেট (ক্লাস) তৈরি করা হয় একটি সাধারণ ফাংশনের মাধ্যমে। কোনো ফাংশনকে যখন এমন টেমপ্লেট হিসেবে ব্যবহার করা হয়, তখন তাকে কনস্ট্রাক্টর ফাংশন বলা হয়, আর প্রচলিত নিয়ম হলো ফাংশনের নাম বড় হাতের অক্ষর দিয়ে শুরু হওয়া উচিত। কনস্ট্রাক্টর ফাংশন কল করার সময় new কিওয়ার্ড ব্যবহার করে টেমপ্লেট থেকে ইনস্ট্যান্স (অবজেক্ট) তৈরি করা হয়।

function Car() {
  // ...
}

const myCar = new Car();
const yourCar = new Car();

লক্ষ্য করা জরুরি যে JavaScript-এ ইনস্ট্যান্স তৈরি হওয়ার পরেও ইনস্ট্যান্সগুলো আর কনস্ট্রাক্টর ফাংশনের মধ্যে সম্পর্ক থেকে যায়। প্রতিটি ইনস্ট্যান্স অবজেক্টে একটি লুকানো, অন্তর্নিহিত প্রপার্টি থাকে, যাকে ভাষার স্পেসিফিকেশনে [[prototype]] বলা হয়। এটি কনস্ট্রাক্টর ফাংশনের prototype কী (key)-এর মানের একটি রেফারেন্স ধারণ করে। হ্যাঁ, আপনি ঠিকই পড়েছেন, একটি JavaScript ফাংশনের কী (key)/ভ্যালু জোড়া থাকতে পারে, কারণ পর্দার আড়ালে এটি নিজেও একটি অবজেক্ট।

Note

সারসংক্ষেপে:

  • JavaScript-এ কনস্ট্রাক্টর হলো সাধারণ ফাংশন।
  • নতুন একটি ইনস্ট্যান্স তৈরি করলে এমন একটি অবজেক্ট তৈরি হয়, যার নিজের কনস্ট্রাক্টরের সঙ্গে প্রোটোটাইপ নামে একটি সম্পর্ক থাকে।
  • ফাংশনও অবজেক্ট (কল করা যায় এমন অবজেক্ট), তাই এদেরও প্রপার্টি থাকতে পারে।
  • কনস্ট্রাক্টর (ফাংশন)-এর prototype প্রপার্টিই ইনস্ট্যান্সের প্রোটোটাইপ হয়ে যায়।

ইনস্ট্যান্স ফিল্ড

অনেক সময় আপনি চান, টেমপ্লেট থেকে তৈরি সব অবজেক্ট (ইনস্ট্যান্স)-এ কিছু ফিল্ড থাকুক এবং অবজেক্টটি তৈরি হওয়ার সময় সেগুলোর জন্য কিছু প্রাথমিক মান পাঠানো হোক। এটি করা যায় this কিওয়ার্ডের মাধ্যমে। কনস্ট্রাক্টর ফাংশনের ভেতরে this বলতে বোঝায় সেই নতুন অবজেক্ট, যা new দিয়ে তৈরি হবে। new দিয়ে কল করা হলে কনস্ট্রাক্টর ফাংশন থেকে this স্বয়ংক্রিয়ভাবে রিটার্ন হয়।

এর মানে হলো, কনস্ট্রাক্টর ফাংশনে this-এর সঙ্গে যোগ করে আমরা নতুন ইনস্ট্যান্সে ফিল্ড যোগ করতে পারি।

function Car(color, weight) {
  this.color = color;
  this.weight = weight;
  this.engineRunning = false;
}

const myCar = new Car('red', '2mt');
myCar.color;
// => 'red'
myCar.engineRunning;
// => false

ইনস্ট্যান্স মেথড

মেথড যোগ করা হয় কনস্ট্রাক্টর ফাংশনের prototype প্রপার্টির মাধ্যমে। একটি মেথডের ভেতরে this-এর মাধ্যমে ইনস্ট্যান্সের ফিল্ডগুলো ব্যবহার করা যায়। এটি কাজ করে নিচের সাধারণ নিয়মটির কারণে।

কোনো ফাংশন যখন কোনো অবজেক্টের মেথড হিসেবে কল করা হয়, তখন তার this সেট হয় সেই অবজেক্টে, যার উপর মেথডটি কল করা হয়েছে। 1

function Car() {
  this.engineRunning = false;
  // ...
}

Car.prototype.startEngine = function () {
  this.engineRunning = true;
};

Car.prototype.addGas = function (litre) {
  // ...
};

const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true

প্রোটোটাইপ চেইন

উপরের উদাহরণের myCar একটি সাধারণ JavaScript অবজেক্ট, আর আমরা যদি এটি পরীক্ষা করে দেখি (যেমন ব্রাউজার কনসোল-এ), তাহলে myCar অবজেক্টের সরাসরি ভেতরে মান হিসেবে ফাংশন রয়েছে এমন কোনো startEngine প্রপার্টি পাব না। তাহলে উপরের কোডটি কীভাবে কাজ করছে?

এখানে রহস্যটির নামই প্রোটোটাইপ চেইন। কোনো অবজেক্টের যেকোনো প্রপার্টি (ফিল্ড বা মেথড) ব্যবহার করতে চাইলে JavaScript প্রথমে দেখে, সংশ্লিষ্ট কী (key) সরাসরি ওই অবজেক্টেই পাওয়া যায় কি না। না পাওয়া গেলে এটি খুঁজতে থাকে মূল অবজেক্টের [[prototype]] প্রপার্টি যে অবজেক্টকে নির্দেশ করে, তার ভেতরে। আগেই বলা হয়েছে, আমাদের উদাহরণে [[prototype]] কনস্ট্রাক্টর ফাংশনের prototype প্রপার্টিকে নির্দেশ করে। সেখানেই JavaScript startEngine ফাংশনটি খুঁজে পায়, কারণ আমরা সেটি ওখানেই যোগ করেছি।

function Car() {
  // ...
}

Car.prototype.startEngine = function () {
  // ...
};

আর চেইন এখানেই শেষ হয় না। Car.prototype-এর [[prototype]] প্রপার্টি (myCar.[[prototype]].[[prototype]]) নির্দেশ করে Object.prototype-কে (Object কনস্ট্রাক্টর ফাংশনের prototype প্রপার্টি)। এতে এমন সাধারণ মেথড থাকে, যা সব JavaScript অবজেক্টের জন্যই পাওয়া যায়, যেমন toString()। সব মিলিয়ে, আপনি myCar.toString() কল করতে পারেন, আর মেথডটি থাকবে, কারণ JavaScript পুরো প্রোটোটাইপ চেইন জুড়ে ওই মেথডটি খোঁজে।

Caution

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

ক্লাস সিনট্যাক্স

আজকাল JavaScript class কিওয়ার্ড দিয়ে ক্লাস ডিফাইন করা সমর্থন করে। এটি ২০১৫ সালে ভাষার স্পেসিফিকেশনে যুক্ত হয়েছিল। একদিকে, এটি সিনট্যাক্টিক সুগার এনে দেয়, যা ক্লাস পড়া ও লেখা সহজ করে। নতুন সিনট্যাক্সটি C++ বা Java-র মতো ভাষায় ক্লাস লেখার ধরনের সঙ্গে বেশি মেলে। ওই ভাষাগুলো থেকে আসা ডেভেলপারদের খাপ খাওয়ানো সহজ হয়। অন্যদিকে, ক্লাস সিনট্যাক্স এমন নতুন ভাষা-ফিচারের পথ খুলে দেয়, যা প্রোটোটাইপ সিনট্যাক্সে পাওয়া যায় না।

ক্লাস ডিক্লারেশন

নতুন সিনট্যাক্সে ক্লাস ডিফাইন করা হয় class কিওয়ার্ড দিয়ে, তারপর থাকে ক্লাসের নাম, আর দ্বিতীয় বন্ধনীর ভেতরে ক্লাস বডি। বডিতে থাকে কনস্ট্রাক্টর ফাংশনের ডেফিনিশন, অর্থাৎ constructor নামের একটি বিশেষ মেথড। এই ফাংশনটি প্রোটোটাইপ সিনট্যাক্সের কনস্ট্রাক্টর ফাংশনের মতোই কাজ করে। ক্লাস বডিতে থাকে সব মেথডের ডেফিনিশনও। মেথডগুলোর সিনট্যাক্স অবজেক্টের ভেতরে মান হিসেবে ফাংশন যোগ করার যে শর্টহ্যান্ড নোটেশন আমরা দেখেছি, তার মতোই, দেখুন অবজেক্ট কনসেপ্ট।

class Car {
  constructor(color, weight) {
    this.color = color;
    this.weight = weight;
    this.engineRunning = false;
  }

  startEngine() {
    this.engineRunning = true;
  }

  addGas(litre) {
    // ...
  }
}

const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true

মনে রাখবেন, পর্দার আড়ালে JavaScript এখনও প্রোটোটাইপ-ভিত্তিক ভাষাই। উপরের "প্রোটোটাইপ সিনট্যাক্স" অংশে আমরা যে সব প্রক্রিয়া শিখেছি, সেগুলো এখনও প্রযোজ্য।

প্রাইভেট ফিল্ড, গেটার ও সেটার

ডিফল্টভাবে JavaScript-এ সব ইনস্ট্যান্স ফিল্ড পাবলিক। এগুলো সরাসরি ব্যবহার করা যায় এবং মান অ্যাসাইন করা যায়।

তবে একটি প্রতিষ্ঠিত নিয়ম আছে, আন্ডারস্কোর দিয়ে শুরু হওয়া ফিল্ড ও মেথডকে প্রাইভেট হিসেবে ধরা উচিত। ক্লাসের বাইরে থেকে এগুলো সরাসরি ব্যবহার করা উচিত নয়।

প্রাইভেট ফিল্ডের সঙ্গে অনেক সময় গেটার ও সেটার থাকে। get ও set কিওয়ার্ড দিয়ে আপনি এমন ফাংশন ডিফাইন করতে পারেন, যা ফাংশনের নামের মতোই নামের কোনো প্রপার্টি ব্যবহার বা অ্যাসাইন করার সময় চালানো হয়।

class Car {
  constructor() {
    this._mileage = 0;
  }

  get mileage() {
    return this._mileage;
  }

  set mileage(value) {
    throw new Error(`Mileage cannot be manipulated, ${value} is ignored.`);
    // Just an example, usually you would not provide a setter in this case.
  }
}

const myCar = new Car();
myCar.mileage;
// => 0
myCar.mileage = 100;
// => Error: Mileage cannot be manipulated, 100 is ignored.

  1. this-এর উদাহরণ, অবজেক্ট মেথড হিসেবে, MDN। https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#as_an_object_method (তথ্যসূত্র: ৩ ডিসেম্বর, ২০২১) ↩

নির্দেশনা

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

                  <--------------------- screenSize.width --------------------->

       ^          ┌────────────────────────────────────────────────────────────┐
       |          │                                                            │
       |          │         position.x, _                                      │
       |          │         position.y   \                                     │
       |          │                       \<----- size.width ----->            │
       |          │                 ^      *──────────────────────┐            │
       |          │                 |      │        title         │            │
       |          │                 |      ├──────────────────────┤            │
screenSize.height │                 |      │                      │            │
       |          │            size.height │                      │            │
       |          │                 |      │       contents       │            │
       |          │                 |      │                      │            │
       |          │                 |      │                      │            │
       |          │                 v      └──────────────────────┘            │
       |          │                                                            │
       |          │                                                            │
       v          └────────────────────────────────────────────────────────────┘

📣 আপনার JavaScript দক্ষতার বিস্তৃত পরিসর অনুশীলন করতে, কাজ 1 ও 2 প্রোটোটাইপ সিনট্যাক্স দিয়ে এবং বাকি কাজগুলো ক্লাস সিনট্যাক্স দিয়ে সমাধান করার চেষ্টা করুন।

1. উইন্ডোর মাপ সংরক্ষণের জন্য Size ডিফাইন করুন

Size নামের একটি ক্লাস (কনস্ট্রাক্টর ফাংশন) ডিফাইন করুন। এতে width ও height নামের দুটি ফিল্ড থাকবে, যেগুলো উইন্ডোর বর্তমান মাপ সংরক্ষণ করে। কনস্ট্রাক্টর ফাংশনটি এই ফিল্ডগুলোর প্রাথমিক মান গ্রহণ করবে। width প্রথম প্যারামিটার হিসেবে এবং height দ্বিতীয় প্যারামিটার হিসেবে দেওয়া হয়। ডিফল্ট width ও height যথাক্রমে 80 এবং 60 হবে।

এর পাশাপাশি, একটি resize(newWidth, newHeight) মেথড ডিফাইন করুন, যা প্যারামিটার হিসেবে নতুন width ও height নেয় এবং নতুন আকারটি প্রতিফলিত করতে ফিল্ডগুলো পরিবর্তন করে।

const size = new Size(1080, 764);
size.width;
// => 1080
size.height;
// => 764

size.resize(1920, 1080);
size.width;
// => 1920
size.height;
// => 1080

2. উইন্ডোর অবস্থান সংরক্ষণের জন্য Position ডিফাইন করুন

Position নামের একটি ক্লাস (কনস্ট্রাক্টর ফাংশন) ডিফাইন করুন, যাতে x ও y নামের দুটি ফিল্ড থাকবে, যেগুলো যথাক্রমে উইন্ডোর উপরের বাম কোণের বর্তমান অনুভূমিক ও উল্লম্ব অবস্থান সংরক্ষণ করে। কনস্ট্রাক্টর ফাংশনটি এই ফিল্ডগুলোর প্রাথমিক মান গ্রহণ করবে। x-এর মান প্রথম প্যারামিটার হিসেবে এবং y-এর মান দ্বিতীয় প্যারামিটার হিসেবে দেওয়া হয়। দুটি ফিল্ডের জন্যই ডিফল্ট মান 0 হবে।

(0, 0) অবস্থানটি হলো স্ক্রিনের উপরের বাম কোণ; আপনি ডানে যাওয়ার সাথে সাথে x-এর মান বাড়ে এবং নিচে যাওয়ার সাথে সাথে y-এর মান বাড়ে।

এছাড়া একটি move(newX, newY) মেথডও ডিফাইন করুন, যা নতুন x ও y প্যারামিটার নেয় এবং নতুন অবস্থান প্রতিফলিত করতে প্রপার্টিগুলো পরিবর্তন করে।

const point = new Position();
point.x;
// => 0
point.y;
// => 0

point.move(100, 200);
point.x;
// => 100
point.y;
// => 200

3. একটি ProgramWindow ক্লাস ডিফাইন করুন

নিচের ফিল্ডগুলোসহ একটি ProgramWindow ক্লাস ডিফাইন করুন:

  • screenSize: Size টাইপের একটি নির্দিষ্ট মান ধারণ করে, যার width 800 এবং height 600
  • size : Size টাইপের একটি মান ধারণ করে, প্রাথমিক মানটি হলো Size ইনস্ট্যান্সের ডিফল্ট মান
  • position : Position টাইপের একটি মান ধারণ করে, প্রাথমিক মানটি হলো Position ইনস্ট্যান্সের ডিফল্ট মান

উইন্ডো খোলা হলে (তৈরি হলে), শুরুতে এটির সর্বদাই ডিফল্ট আকার ও অবস্থান থাকে।

const programWindow = new ProgramWindow();
programWindow.screenSize.width;
// => 800

// Similar for the other fields.

পার্শ্ব নোট: ব্রাউজার এনভায়রনমেন্টে থাকা বিল্ট-ইন Window ক্লাস থেকে এই ক্লাসটিকে আলাদা করতে Window-এর বদলে ProgramWindow নামটি ব্যবহার করা হয়েছে।

4. উইন্ডো রিসাইজ করার একটি মেথড যোগ করুন

ProgramWindow ক্লাসে একটি resize মেথড থাকা উচিত। এটি ইনপুট হিসেবে Size টাইপের একটি প্যারামিটার নেবে এবং উইন্ডোটিকে নির্দিষ্ট আকারে রিসাইজ করার চেষ্টা করবে।

তবে নতুন আকার নির্দিষ্ট কিছু সীমা ছাড়িয়ে যেতে পারবে না।

  • অনুমোদিত সর্বনিম্ন height বা width হলো 1। 1-এর কম height বা width চাওয়া হলে সেটি 1-এ ক্লিপ করা হবে।
  • সর্বোচ্চ height ও width নির্ভর করে উইন্ডোর বর্তমান অবস্থানের উপর; উইন্ডোর প্রান্ত স্ক্রিনের প্রান্ত ছাড়িয়ে যেতে পারবে না। এই সীমার চেয়ে বড় মানগুলো তাদের সর্বোচ্চ সম্ভাব্য আকারে ক্লিপ করা হবে। যেমন, উইন্ডোর অবস্থান x = 400, y = 300 হলে এবং height = 400, width = 300-এ রিসাইজ চাওয়া হলে, উইন্ডোটিকে height = 300, width = 300-এ রিসাইজ করা হবে, কারণ অনুরোধটি সম্পূর্ণভাবে মেটানোর জন্য y দিকে স্ক্রিনটি যথেষ্ট বড় নয়।
const programWindow = new ProgramWindow();

const newSize = new Size(600, 400);
programWindow.resize(newSize);
programWindow.size.width;
// => 600
programWindow.size.height;
// => 400

5. উইন্ডো সরানোর একটি মেথড যোগ করুন

রিসাইজ ফাংশনালিটির পাশাপাশি ProgramWindow ক্লাসে একটি move মেথডও থাকা উচিত। এটি ইনপুট হিসেবে Position টাইপের একটি প্যারামিটার নেবে। move মেথডটি resize-এর মতোই, তবে এটি আকারের বদলে উইন্ডোর অবস্থান চাওয়া মানে সমন্বয় করে।

resize-এর মতোই, নতুন অবস্থানও নির্দিষ্ট কিছু সীমা ছাড়িয়ে যেতে পারবে না।

  • x ও y উভয়ের জন্যই সবচেয়ে ছোট অবস্থান হলো 0।
  • যেকোনো দিকে সর্বোচ্চ অবস্থান নির্ভর করে উইন্ডোর বর্তমান আকারের উপর। প্রান্ত স্ক্রিনের প্রান্ত ছাড়িয়ে যেতে পারবে না। এই সীমার চেয়ে বড় মানগুলো তাদের সর্বোচ্চ সম্ভাব্য আকারে ক্লিপ করা হবে। যেমন, উইন্ডোর আকার x = 250, y = 100 থাকলে এবং x = 600, y = 200-এ সরানোর অনুরোধ করা হলে, উইন্ডোটিকে x = 550, y = 200-এ সরানো হবে, কারণ অনুরোধটি সম্পূর্ণভাবে মেটানোর জন্য x দিকে স্ক্রিনটি যথেষ্ট বড় নয়।
const programWindow = new ProgramWindow();

const newPosition = new Position(50, 100);
programWindow.move(newPosition);
programWindow.position.x;
// => 50
programWindow.position.y;
// => 100

6. একটি প্রোগ্রাম উইন্ডো পরিবর্তন করুন

এমন একটি changeWindow ফাংশন বাস্তবায়ন করুন, যা ইনপুট হিসেবে একটি ProgramWindow ইনস্ট্যান্স নেয় এবং উইন্ডোটিকে নির্দিষ্ট আকার ও অবস্থানে পরিবর্তন করে। পরিবর্তনগুলো প্রয়োগ করার পর ফাংশনটি যে ProgramWindow ইনস্ট্যান্সটি পাস করা হয়েছিল সেটিই রিটার্ন করবে।

উইন্ডোটির width হবে 400, height হবে 300, এবং এটি x = 100, y = 150 অবস্থানে থাকবে।

const programWindow = new ProgramWindow();
changeWindow(programWindow);
programWindow.size.width;
// => 400

// Similar for the other fields.
GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
JavaScript Exercism

উইন্ডোইং সিস্টেম শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, JavaScript ট্র্যাকের 37টি কনসেপ্ট159টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।