Lesson ১০৭ মিনিট পড়া

ইন্টারফেস এবং অ্যাবস্ট্রাক্ট ক্লাস (Interfaces & Abstract Classes)

আপনার ক্লাসগুলো যেসব নিয়ম মানার প্রতিশ্রুতি দেয়

ইন্টারফেস — পৃথিবীর কাছে একটি প্রতিশ্রুতি (Interfaces — A Promise to the World)

একটি ইন্টারফেসকে (Interface) অনেকটা চাকরির বিবরণের বা জব ডেসক্রিপশনের (job description) মতো ভাবতে পারেন। এটি বলে দেয় যে "যে-ই এই কাজটি নেবে, তাকে অবশ্যই ক, খ এবং গ কাজগুলো করতে পারতে হবে" — কিন্তু কাজগুলো কীভাবে করতে হবে, সেটি বলা থাকে না। যে ক্লাসই এই ইন্টারফেসটিকে ইমপ্লিমেন্ট (implements) বা বাস্তবায়ন করে, সে মূলত একটি প্রতিশ্রুতি দেয়: "আমি এই সবগুলো মেথডের কাজ করে দেব।"

এটি এতটা শক্তিশালী কেন? কারণ এর ফলে অন্য কোডগুলো ওই নির্দিষ্ট ক্লাসের ভেতরের খুঁটিনাটি নিয়ে মাথা না ঘামিয়েই ইন্টারফেসটির সাথে কাজ করতে পারে। একটি Printable (প্রিন্ট করার যোগ্য) ইন্টারফেসের এটা জানার দরকার নেই যে সে কোনো Document বা নথিপত্র প্রিন্ট করছে, নাকি কোনো Photo (ছবি) অথবা কোনো Receipt (রসিদ) প্রিন্ট করছে — যতক্ষণ পর্যন্ত তাদের সবার ভেতরে একটি print() মেথড থাকছে, ততক্ষণ এটি কাজ করবে।

ক্লাসগুলোর সাথে এর মূল পার্থক্যগুলো হলো:

  • ইন্টারফেসের নিজস্ব কোনো কনস্ট্রাক্টর বা ইনস্ট্যান্স ফিল্ড থাকতে পারে না (শুধুমাত্র কনস্ট্যান্ট বা ধ্রুবক থাকতে পারে)।
  • একটি ক্লাস একাধিক ইন্টারফেসকে ইমপ্লিমেন্ট (implement) করতে পারে (extends এর মতো নয়, যা কেবল একটির মাঝেই সীমাবদ্ধ)।
  • জাভা ৮ (Java 8) থেকে, ইন্টারফেসগুলোর ভেতরে বডি বা কোড যুক্ত default মেথডও থাকতে পারে।

ইন্টারফেস তৈরি ও ইমপ্লিমেন্ট করা (Defining & Implementing Interfaces)

public class Main {
// Interface — the contract
interface Drawable {
void draw(); // any Drawable must have this
}
interface Resizable {
void resize(double factor);
}
// A class can implement MULTIPLE interfaces
static class Circle implements Drawable, Resizable {
double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.printf("Drawing circle with radius %.1f%n", radius);
}
@Override
public void resize(double factor) {
radius *= factor;
System.out.printf("Resized to radius %.1f%n", radius);
}
}
static class Square implements Drawable {
double side;
Square(double side) {
this.side = side;
}
@Override
public void draw() {
System.out.printf("Drawing square with side %.1f%n", side);
}
}
public static void main(String[] args) {
// Polymorphism through interfaces
Drawable[] art = { new Circle(5), new Square(3), new Circle(2) };
for (Drawable d : art) {
d.draw(); // Java picks the right version
}
System.out.println("---");
Circle c = new Circle(10);
c.resize(0.5);
c.draw();
}
}
Output
Drawing circle with radius 5.0
Drawing square with side 3.0
Drawing circle with radius 2.0
---
Resized to radius 5.0
Drawing circle with radius 5.0

ডিফল্ট মেথড এবং অ্যাবস্ট্রাক্ট বনাম ইন্টারফেস (Default Methods & Abstract vs Interface)

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

কখন কোনটি ব্যবহার করবেন?

  • যখন আপনি এমন কোনো দক্ষতা বা সক্ষমতা (capability) তৈরি করতে চান যা একে অপরের সাথে সম্পর্কিত নয় এমন অনেকগুলো ক্লাস শেয়ার করতে পারে, তখন একটি ইন্টারফেস (interface) ব্যবহার করুন। যেমন "উড়তে পারে (can fly)" (Flyable), "সিরিয়ালাইজ করা যায় (can be serialized)" (Serializable)।
  • যখন আপনার কাছে সম্পর্কিত ক্লাসগুলোর একটি পরিবার থাকে এবং তারা কিছু সাধারণ বা কমন কোড শেয়ার করে, তখন একটি অ্যাবস্ট্রাক্ট ক্লাস (abstract class) ব্যবহার করুন। যেমন "সব প্রাণী খায় এবং ঘুমায়, কিন্তু একেক ধরনের প্রাণী একেক রকম শব্দ করে।"
  • যখন আপনার শেয়ার করা বৈশিষ্ট্যের (অ্যাবস্ট্রাক্ট ক্লাস) পাশাপাশি বাড়তি কিছু সক্ষমতা বা গুণেরও (ইন্টারফেস) প্রয়োজন হয়, তখন এই দুটিকেই একসাথে ব্যবহার করুন।

ডিফল্ট মেথড এবং অ্যাবস্ট্রাক্ট + ইন্টারফেসের একত্রীকরণ (Default Methods & Combining Abstract + Interface)

public class Main {
// Interface with default method
interface Greetable {
String getName();
// Default method — classes get this for free
default void greet() {
System.out.println("Hello! I'm " + getName());
}
}
interface Farewell {
String getName();
default void sayBye() {
System.out.println("Goodbye from " + getName() + "!");
}
}
// Implements both interfaces
static class Person implements Greetable, Farewell {
private String name;
Person(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
// Can override default methods too
@Override
public void greet() {
System.out.println("Hey there! The name's " + name + ".");
}
}
static class Robot implements Greetable {
@Override
public String getName() {
return "RoboHelper-3000";
}
// Uses the default greet() — no override needed
}
public static void main(String[] args) {
Person alex = new Person("Asif");
alex.greet(); // overridden version
alex.sayBye(); // default version
Robot bot = new Robot();
bot.greet(); // default version
}
}
Output
Hey there! The name's Asif.
Goodbye from Asif!
Hello! I'm RoboHelper-3000
Note: এখানে মনে রাখার মতো একটি সহজ নিয়ম হলো: ইন্টারফেস আমাদের বলে দেয় যে একটি ক্লাস কী কী কাজ করতে পারে (সক্ষমতা), অন্যদিকে অ্যাবস্ট্রাক্ট ক্লাস আমাদের বলে দেয় যে ক্লাসটি আসলে কী (পরিচয়)। একটি পাখি (Bird) হলো একটি প্রাণী বা Animal (অ্যাবস্ট্রাক্ট ক্লাস) এবং সে উড়তেও বা Fly করতে পারে (ইন্টারফেস)। একইভাবে একটি বিমান (Plane) হলো একটি যানবাহন বা Vehicle এবং সে-ও কিন্তু উড়তে বা Fly করতে পারে। ফ্লাইয়েবল (Flyable) ইন্টারফেসটি মূলত একে অপরের সাথে সম্পর্ক নেই এমন জিনিসগুলোকে একটি সাধারণ সক্ষমতার মাধ্যমে যুক্ত করে।
চ্যালেঞ্জ

ছোট কুইজ

জাভাতে একটি ক্লাস কতগুলো ইন্টারফেসকে ইমপ্লিমেন্ট (implement) করতে পারে?
Inheritance & PolymorphismCollections Framework