এই পর্বে যা শিখবেন
abstractclass আর abstract method — কেন object বানানো যায় না, তবু constructor থাকে- Template Method pattern — parent ঠিক করে ধাপের ক্রম, child পূরণ করে একটি ধাপ
- interface ঘোষণা ও implement, একাধিক interface, constant, default, static আর private method
- Diamond সংঘর্ষ আর
X.super.m(); interface-কে টাইপ হিসেবে ব্যবহার আর dependency inversion - Marker ও functional interface, JDK-র চেনা interface, আর abstract class না interface — সিদ্ধান্তের টেবিল
১ · দুটি সমস্যা — ভাগ করা কোড আর একই চুক্তি
পর্ব ২০-এ polymorphism দেখেছেন: parent টাইপের একটি ভ্যারিয়েবল (সেখানে Payment, এখানে ধরুন Shape) আসলে Circle বা Rect object দেখাতে পারে, আর area() ডাকলে চলার সময় আসল object-এর method চলে (dynamic dispatch). কিন্তু সেখানে একটি অস্বস্তিকর প্রশ্ন রয়ে গিয়েছিল। Parent Shape-এর নিজের area() কী ফেরত দেবে? শূন্য? “আকৃতি” বলে কোনো আসল জিনিসই নেই — আছে বৃত্ত, আয়ত, ত্রিভুজ। শূন্য ফেরত দেওয়া মানে একটি মিথ্যা উত্তর, আর কেউ subclass-এ area() লিখতে ভুলে গেলে প্রোগ্রাম নীরবে ০ দেখাবে।
দ্বিতীয় সমস্যাটি আরও বড়। একটি অনলাইন দোকানে অর্ডার পাঠানোর পর গ্রাহককে খবর দিতে হবে — কখনো SMS-এ, কখনো Email-এ, পরে হয়তো WhatsApp-এ। SmsSender আর EmailSender-এর মধ্যে কোনো পারিবারিক সম্পর্ক নেই; একটি মোবাইল অপারেটরের সাথে কথা বলে, আরেকটি মেইল সার্ভারের সাথে। তাদের কোনো field বা কোড ভাগ করার নেই। তবু দোকানের কোড চায় দুটিকেই একইভাবে ডাকতে: “এই নম্বরে/ঠিকানায় এই বার্তাটা পাঠাও।”
Java এই দুই সমস্যার জন্য দুটি আলাদা হাতিয়ার দেয়:
abstract class
অর্ধেক-তৈরি parent. নাম, field, constructor, সাধারণ method — সব subclass-এর সাথে ভাগ করে। শুধু যে কাজ subclass ছাড়া কেউ জানে না (যেমন area()), সেটা ফাঁকা রাখে।
interface
একটি চুক্তি। কোনো state নেই, শুধু প্রতিশ্রুতি: “আমাকে যে implement করবে, সে send(...) দেবেই।” সম্পর্কহীন class-ও এই চুক্তি সই করতে পারে।
দোকানের কাউন্টারে একটি QR কোড ভাবুন। দোকানদার জানতে চান না আপনি কোন অ্যাপ ব্যবহার করছেন — যে অ্যাপ সেই QR-এর নিয়ম মানে, তা দিয়েই টাকা দেওয়া যায়। অ্যাপগুলো আলাদা কোম্পানির, ভেতরের কোড সম্পূর্ণ আলাদা, তবু সবাই একই “চুক্তি” মানে। এটাই interface. অন্যদিকে, একটি গার্মেন্টস কারখানার শার্টের ছাঁচ ভাবুন: কলার, হাতা, বোতামের ঘর সব কাটা আছে, শুধু বুকের পকেটের নকশা প্রতিটি ব্র্যান্ড নিজে বসাবে। ছাঁচটি একা বিক্রি করা যায় না, কিন্তু বেশিরভাগ কাজ সে করে দিয়েছে। এটাই abstract class.
দুটি ধারণাই আসলে abstraction-এর হাতিয়ার: ব্যবহারকারী জানবে কী করা যায়, কীভাবে হয় তা লুকানো থাকবে। পার্থক্য হলো — abstract class কোড আর state ভাগ করে, interface শুধু আচরণের প্রতিশ্রুতি ভাগ করে।
২ · abstract class — যে parent-এর object হয় না
কোনো class-এর আগে abstract keyword বসালে সে হয় abstract class. তার ভেতরে শরীরহীন method ঘোষণা করা যায় — শুধু signature, তারপর সেমিকোলন, কোনো { } নেই। এমন method-কে বলে abstract method:
abstract class Shape {
private final String name; // state — সব আকৃতির নাম থাকে
Shape(String name) { // constructor-ও আছে!
this.name = name;
}
abstract double area(); // শরীর নেই — subclass দেবে
String describe() { // সাধারণ (concrete) method
return String.format("%s: area %.2f", name, area());
}
}এখানে তিনটি জিনিস একসাথে আছে, আর তিনটিই গুরুত্বপূর্ণ। name হলো ভাগ করা state; describe() হলো ভাগ করা আচরণ, যা সব আকৃতির জন্য একইভাবে কাজ করে; আর area() হলো ফাঁকা ঘর, যা প্রতিটি আকৃতিকে নিজে পূরণ করতে হবে। সব method-এর শরীর আছে এমন সাধারণ class-কে বলা হয় concrete class.
নিয়ম ১ — abstract class-এর object বানানো যায় না
Shape s = new Shape("কিছু একটা"); // ✗Main.java:6: error: Shape is abstract; cannot be instantiated
Shape s = new Shape("কিছু একটা");
^
1 error
যুক্তিটা সহজ: এই object-এ কেউ area() ডাকলে কোন কোড চলবে? কোনো কোডই নেই। তাই compiler আগেভাগেই আটকে দেয়। কিন্তু Shape টাইপের ভ্যারিয়েবল রাখা সম্পূর্ণ বৈধ — Shape s = new Circle(2); — কারণ ভ্যারিয়েবল যে object দেখাবে, সেটি সবসময় কোনো concrete subclass-এর হবে।
নিয়ম ২ — subclass হয় সব ফাঁকা ঘর পূরণ করবে, নয়তো নিজেও abstract হবে
class Circle extends Shape {
// area() লিখতে ভুলে গেছি
}Main.java:4: error: Circle is not abstract and does not override abstract method area() in Shape
class Circle extends Shape {
^
1 error
ঠিক এই error-টিই পর্ব ২০-এর “parent-এ শূন্য ফেরত দেওয়া” সমাধানের চেয়ে abstract method-কে ভালো করে তোলে। ভুলে যাওয়া আর নীরব bug থাকে না — compile-ই হয় না। আর যদি এমন একটি মাঝের class চান যেটা কিছু ঘর পূরণ করবে, বাকিটা তার subclass-এর জন্য রেখে দেবে, তাহলে সেটিকেও abstract ঘোষণা করুন।
আরও কয়েকটি নিয়ম, এক নজরে
- Constructor থাকতে পারে। Object সরাসরি বানানো না গেলেও subclass-এর constructor
super(...)দিয়ে একে ডাকে (পর্ব ১৯)। ভাগ করা field-এর শুরুর মান এখানেই বসে। - একটিও abstract method না থাকলেও class abstract হতে পারে — শুধু এটুকু বোঝাতে যে “আমাকে সরাসরি ব্যবহার করবেন না।” কিন্তু উল্টোটা নয়: যে class-এ একটি abstract method আছে, তাকে abstract হতেই হবে।
abstract-এর সাথেfinal,privateবাstaticমেলে না। abstract method-এর অর্থই হলো “subclass override করবে” —final(override নিষেধ),private(subclass দেখতেই পায় না) বাstatic(object-এর ওপর নির্ভর করে না) এর সাথে সাংঘর্ষিক। একইভাবেabstract final class-ও অর্থহীন, compile error.
এবার দেখা যাক সবচেয়ে সুন্দর অংশটি: parent-এর সাধারণ method describe() ভেতর থেকে abstract area()-কে ডাকে — যার কোড parent লেখেইনি। তাহলে কোন কোড চলে? “পরে” চাপুন:
1abstract class Shape {2 private final String name;3 Shape(String n) { name = n; }4 abstract double area();5 String describe() {6 return name + ": " + area();7 }8}9class Circle extends Shape {10 private final double r;11 Circle(double r) { super("Circle"); this.r = r; }12 double area() { return Math.PI * r * r; }13}14Shape s = new Circle(2);15System.out.println(s.describe());
| name (Shape-এর অংশ) | |
| r (Circle-এর অংশ) | |
| এখন চলছে |
new Circle(2). JVM heap-এ একটি object বানায়। খেয়াল করুন, object একটিই — তার ভেতরেShape-এর অংশ (name) আরCircle-এর অংশ (r) দুটিই আছে, আপাতত ডিফল্ট মানে। Abstract class-এর আলাদা কোনো object তৈরি হয় না।super("Circle")— parent-এর constructor চলল। Abstract class-এর constructor এভাবেই কাজে লাগে: সে নিজের অংশ (name) গুছিয়ে দেয়। এটাnew Shape(...)নয় — child-এর ভেতর থেকে ডাকা।- Child নিজের অংশ গোছাল।
r = 2.0. এখন object সম্পূর্ণ। ভ্যারিয়েবলs-এর টাইপShape, কিন্তু সে দেখাচ্ছে একটিCircleobject. s.describe().Circleনিজেdescribe()লেখেনি, তাই উত্তরাধিকার সূত্রে পাওয়াShape-এর কোডটিই চলছে।- Parent-এর কোড থেকে
area().Shape-এarea()-এর কোনো শরীর নেই। কিন্তু ডাকটি হচ্ছে এই object-এর ওপর, আর object-টি আসলেCircle— তাই পর্ব ২০-এর dynamic dispatchCircle.area()চালায়। Parent কাঠামো লিখেছে, child ফাঁকা ঘর ভরেছে। - ফল ফিরে এল parent-এর method-এ।
describe()নাম আর ক্ষেত্রফল জুড়ে ফেরত দিল। এখানে সংক্ষেপের জন্য+দিয়ে জোড়া হয়েছে, তাই পুরো দশমিক ছাপা হলো; নিচের চালানো যায় এমন কোডেString.formatদিয়ে দুই ঘর পর্যন্ত গোল করা হয়েছে।
এবার পুরো উদাহরণটি — দুটি আলাদা আকৃতি, একটি Shape[] array, আর একটিই লুপ। চালিয়ে দেখুন:
abstract class Shape {
private final String name;
Shape(String name) { this.name = name; }
abstract double area();
String describe() {
return String.format("%s: area %.2f", name, area());
}
}
class Circle extends Shape {
private final double r;
Circle(double r) { super("Circle"); this.r = r; }
@Override double area() { return Math.PI * r * r; }
}
class Rect extends Shape {
private final double w, h;
Rect(double w, double h) { super("Rect"); this.w = w; this.h = h; }
@Override double area() { return w * h; }
}
class Main {
public static void main(String[] args) {
Shape[] shapes = { new Circle(2), new Rect(3, 4) };
for (Shape s : shapes) System.out.println(s.describe());
}
}আউটপুট: Circle: area 12.57 আর Rect: area 12.00. লুপ জানে শুধু Shape-কে; নতুন একটি Triangle যোগ করলে লুপে একটি অক্ষরও বদলাতে হবে না — শুধু নতুন subclass লিখে area() পূরণ করুন। describe()-এর format কোড আছে মাত্র এক জায়গায়, আর name field-ও। @Override annotation compiler-কে বলে “এটা parent-এর একটি method পূরণ করছে” — বানান ভুল করে aera() লিখলে সে সাথে সাথে ধরবে। (টীকা: অনলাইন runner ফাইলটিকে অন্য নামে compile করে, তাই এখানে কোনো class public নয়; নিজের প্রজেক্টে public class Main থাকে Main.java ফাইলে।)
৩ · Template Method — parent ঠিক করে ক্রম, child পূরণ করে ধাপ
ওপরের describe() আসলে একটি বিখ্যাত নকশার ছোট রূপ। ধরুন একটি দোকানের সফটওয়্যারে অনেক ধরনের রিপোর্ট লাগে — বিক্রির রিপোর্ট, স্টকের রিপোর্ট, বাকির রিপোর্ট। প্রতিটির শুরুতে একই শিরোনাম, শেষে একই পাদটীকা; শুধু মাঝের অংশ আলাদা। প্রতিটি রিপোর্ট class-এ শিরোনাম আর পাদটীকা কপি করলে, দোকানের নাম বদলালে দশ জায়গায় বদলাতে হবে।
abstract class ReportGenerator {
public final String generate() { // ক্রম — কেউ বদলাতে পারবে না
return header() + body() + footer();
}
protected String header() { return "=== ABCL Shop ===\n"; }
protected abstract String body(); // একমাত্র ফাঁকা ঘর
protected String footer() { return "--- শেষ ---\n"; }
}
class SalesReport extends ReportGenerator {
@Override protected String body() { return "আজকের বিক্রি: 12500 টাকা\n"; }
}
class StockReport extends ReportGenerator {
@Override protected String body() { return "চা পাতা: 40 প্যাকেট বাকি\n"; }
}এই কাঠামোর নাম Template Method pattern. generate() হলো “template” — কাজের ধাপগুলোর ক্রম সে নিজে ধরে রাখে, আর final হওয়ায় কোনো subclass সেই ক্রম ভাঙতে পারে না। ধাপগুলোর কিছু parent নিজে করে (header, footer), কিছু subclass-কে দিয়ে করায় (body). protected মানে subclass দেখতে পায়, কিন্তু বাইরের সাধারণ কোড সরাসরি ডাকবে না (পর্ব ১৭)। new SalesReport().generate() ডাকলে কী ঘটে, ধাপে ধাপে দেখুন:
- generate()
ReportGeneratorfinal — ক্রম স্থির - header()
ReportGeneratorparent-এর কোড - body()
SalesReportchild পূরণ করেছে - footer()
ReportGeneratorparent-এর কোড - রিপোর্ট
Stringতিন অংশ জোড়া
SalesReport-এ generate() নেই → parent-এরটা চলে
ক্রম: header() → body() → footer()
=== ABCL Shop ===
=== ABCL Shop ===
আজকের বিক্রি: 12500 টাকা
=== ABCL Shop ===
আজকের বিক্রি: 12500 টাকা
--- শেষ ---
=== ABCL Shop ===
চা পাতা: 40 প্যাকেট বাকি
--- শেষ ---
- ডাক যায় parent-এর method-এ।
SalesReportশুধুbody()লিখেছে,generate()নয় — তাই চলেReportGenerator-এরgenerate(). ক্রমের নিয়ন্ত্রণ parent-এর হাতে। - প্রথম ধাপ parent নিজে করে।
header()-এর কোডReportGenerator-এ, সব রিপোর্টের জন্য এক। - দ্বিতীয় ধাপে নিয়ন্ত্রণ যায় child-এর কাছে।
body()abstract; object-টিSalesReport, তাই তারbody()চলে। একে কখনো বলা হয় “Hollywood principle” — আমাদের ফোন করবেন না, আমরাই আপনাকে ফোন করব: child parent-কে ডাকে না, parent child-কে ডাকে। - শেষ ধাপ আবার parent-এর। পাদটীকা এক জায়গায় লেখা, তাই বদলাতেও এক জায়গায়।
- অন্য child, একই ক্রম।
StockReport-এর জন্য শুধু মাঝের লাইন বদলায়। নতুন রিপোর্ট মানে নতুন একটি ছোট class, আরgenerate()ভুল ক্রমে চালানোর কোনো সুযোগই নেই।
JDK-তেও এই pattern আছে: java.io.InputStream একটি abstract class, যার abstract method শুধু একটি — একটি byte পড়া, read(). একসাথে অনেক byte পড়ার read(byte[], int, int)-এর মতো method-গুলো parent নিজেই সেই একটি method দিয়ে বানিয়ে দেয়। ফাইল, নেটওয়ার্ক, মেমরি — যেকোনো উৎসের জন্য শুধু একটি method লিখলেই পুরো stream পাওয়া যায় (দরকার হলে দ্রুততার জন্য বাকিগুলোও override করা যায়)। একইভাবে AbstractList থেকে get আর size লিখলেই একটি পুরো List পেয়ে যান।
৪ · interface — একটি চুক্তি, অনেক সইকারী
এবার দ্বিতীয় সমস্যায় ফেরা যাক: SMS আর Email পাঠানোর দুটি সম্পর্কহীন class. এদের মধ্যে ভাগ করার মতো কোনো কোড নেই, শুধু একটি প্রতিশ্রুতি আছে — “আমি একটি বার্তা পাঠাতে পারি।” এই প্রতিশ্রুতিকে Java-তে লেখা হয় একটি interface হিসেবে:
interface MessageSender {
boolean send(String to, String text); // শুধু চুক্তি, শরীর নেই
}
class SmsSender implements MessageSender {
public boolean send(String to, String text) {
// মোবাইল অপারেটরের gateway-তে পাঠানো
return true;
}
}
class EmailSender implements MessageSender {
public boolean send(String to, String text) {
// মেইল সার্ভারে পাঠানো
return true;
}
}Class-এর পরে implements লিখে সে ঘোষণা করে: “আমি এই চুক্তি মানি।” তারপর চুক্তির প্রতিটি method-এর শরীর দেওয়া তার দায়িত্ব — না দিলে abstract class-এর মতোই is not abstract and does not override abstract method error. Interface-এর ভেতরে লেখা সাধারণ method নিজে থেকেই public abstract; আলাদা করে লিখতে হয় না।
নতুনরা এখানে একটি নির্দিষ্ট ভুল প্রায়ই করেন: implement করা method-এ public লিখতে ভুলে যাওয়া।
Main.java:3: error: print() in Doc cannot implement print() in Printable
void print() { }
^
attempting to assign weaker access privileges; was public
1 error
Interface-এর method public, তাই যে class তা implement করে সে access কমাতে পারে না — চুক্তিতে লেখা আছে “সবাই ডাকতে পারবে”, আর আপনি বলছেন “শুধু package-এর ভেতরের লোক”। সেটা চুক্তিভঙ্গ। ঠিক এই কারণেই ওপরের কোডে SmsSender-এর send-এর সামনে public লেখা।
Inheritance (extends) বলে “আমি কী” — Circle একটি Shape (is-a). Interface (implements) বলে “আমি কী পারি” — SmsSender বার্তা পাঠাতে পারে। তাই interface-এর নাম প্রায়ই একটি ক্ষমতা বোঝায়: Comparable (তুলনা করা যায়), Runnable (চালানো যায়), Printable, Shareable.
একটি class, অনেক interface
Java-তে একটি class-এর parent class একটিই হতে পারে (পর্ব ১৯)। কিন্তু interface যত খুশি implement করা যায়। একটি অফিস অ্যাপের Invoice ছাপানো যায়, আবার লিংক দিয়ে শেয়ারও করা যায়:
interface Printable { void print(); }
interface Shareable { String link(); }
class Invoice implements Printable, Shareable {
public void print() { System.out.println("Printing invoice"); }
public String link() { return "abcl.link/inv-7"; }
}এটাকে বলা হয় টাইপের multiple inheritance: একটি Invoice object একই সাথে Invoice, Printable আর Shareable — তিনটি টাইপের ভ্যারিয়েবলেই রাখা যায়। প্রিন্টারের কোড চায় শুধু Printable, শেয়ারের কোড চায় শুধু Shareable; কেউই জানে না বা জানার দরকার নেই যে জিনিসটা আসলে একটি চালান। কিন্তু একটি সূক্ষ্ম নিয়ম আছে — ভ্যারিয়েবলের টাইপ ঠিক করে আপনি কোন method ডাকতে পারবেন:
1interface Printable { void print(); }2interface Shareable { String link(); }3class Invoice implements Printable, Shareable {4 public void print() { ... }5 public String link() { ... }6}7Invoice inv = new Invoice();8Printable p = inv;9Shareable s = inv;10p.print();11String url = s.link();12p.link(); // ?
| inv : Invoice | |
| p : Printable | |
| s : Shareable | |
| p দিয়ে ডাকা যায় |
- একটি object তৈরি হলো।
inv-এর টাইপInvoice, তাই সেprint()আরlink()দুটিই ডাকতে পারে। - Printable চশমা।
Printable p = inv;— কোনো কপি বা রূপান্তর নয়, একই object-এর reference. Invoice চুক্তি সই করেছে বলে compiler কোনো cast ছাড়াই এটা মেনে নেয়। - Shareable চশমা। এখন তিনটি ভ্যারিয়েবল, একটিই object. কোনো এক দিক থেকে object-এর ভেতরে কিছু বদলালে বাকি দুই দিক থেকেও তা দেখা যাবে।
p.print()চলে।Printableচুক্তিতেprint()আছে, তাই compiler খুশি। চলার সময় আসল objectInvoice, তাই তারprint()চলে — ছাপা হয়Printing invoice.s.link()চলে। একই যুক্তি —Shareable-এর চুক্তিতেlink()আছে।p.link()— compile error! Object-টির কাছেlink()আছে, তবু compiler আটকায়। কারণ compiler দেখে শুধু ভ্যারিয়েবলের টাইপ:Printableচুক্তিতেlink()নেই।pযে অন্য কোনোPrintableobject দেখাবে না — যেমন একটিPhoto, যার লিংক নেই — compiler তা জানে না। এই কড়াকড়িই interface-কে নিরাপদ করে।
Interface অন্য interface-কেও extends করতে পারে, এমনকি একসাথে কয়েকটিকে: interface Document extends Printable, Shareable { }. যে class Document implement করবে, তাকে দুটি চুক্তিরই সব method দিতে হবে।
৫ · Interface-এর ভেতরে আর কী থাকে — constant, default, static, private
শুরুতে (Java 1.0 থেকে Java 7 পর্যন্ত) interface-এ থাকতে পারত মাত্র দুটি জিনিস: abstract method আর constant. এখন আরও তিনটি যোগ হয়েছে। প্রতিটির পেছনে একটি বাস্তব কারণ আছে।
Constant — এবং কেন “constant interface” একটি ভুল নকশা
Interface-এ লেখা যেকোনো field নিজে থেকেই public static final — অর্থাৎ একটি constant, যা কেউ বদলাতে পারে না:
interface Limits {
int MAX = 5; // আসলে public static final int MAX = 5;
}
// কোথাও: Limits.MAX = 6;Main.java:3: error: cannot assign a value to static final variable MAX
তাই interface-এ কোনো object-ভিত্তিক state রাখা অসম্ভব — প্রতিটি object-এর আলাদা মান থাকবে এমন field interface-এ হয় না। এটাই abstract class-এর সাথে সবচেয়ে বড় পার্থক্য।
পুরোনো Java কোডে একটি অভ্যাস দেখা যেত: শুধু constant রাখার জন্য একটি interface বানানো, তারপর যেকোনো class-এ implements AppConstants লিখে constant-গুলো নাম ধরে সরাসরি ব্যবহার করা। একে বলে constant interface anti-pattern. Joshua Bloch Effective Java-তে একে interface-এর অপব্যবহার বলেছেন। কারণ implements মানে একটি প্রকাশ্য প্রতিশ্রুতি — “আমি এই টাইপ।” OrderService implements AppConstants বলছে যে order service একটি “constant-এর তালিকা” টাইপের জিনিস, যা অর্থহীন। তার ওপর constant-গুলো class-এর public API-তে ঢুকে যায় আর সব subclass-এও ছড়ায়। সঠিক পথ: সম্পর্কিত constant থাকুক যে class-এর সাথে সম্পর্কিত তার ভেতরে (Integer.MAX_VALUE-এর মতো), নির্দিষ্ট কয়েকটি মান হলে enum (পর্ব ২৪), আর নাম ছোট করতে চাইলে import static.
default method — Java 8-এর “interface evolution” সমস্যার সমাধান
২০১৪ সালে Java 8 lambda নিয়ে এলো, আর সাথে একটি কঠিন প্রশ্ন। সবাই চাইছিল list.forEach(x -> ...) লিখতে। কিন্তু List, Collection, Iterable — এগুলো interface, আর পৃথিবীজুড়ে লাখো class এগুলো implement করে, যার অনেকগুলো JDK-র বাইরে, অন্য কোম্পানির library-তে। Interface-এ একটি নতুন abstract method যোগ করলেই সেই সব class হঠাৎ “does not override abstract method” error দিত। Interface একবার প্রকাশ হলে আর বদলানো যায় না — এই ছিল পুরোনো নিয়ম।
সমাধান: default method — শরীরসহ একটি interface method, যার সামনে default লেখা। যে class নিজে না লেখে, সে এই শরীরটি পায়; যে চায়, override করে। Iterable-এ Java 8-এ ঠিক এভাবে forEach যোগ হয়েছে:
// JDK-র Iterable-এর সরল রূপ
public interface Iterable<T> {
Iterator<T> iterator(); // পুরোনো চুক্তি
default void forEach(Consumer<? super T> action) { // Java 8-এ যোগ
for (T t : this) action.accept(t);
}
}পুরোনো সব class কোনো পরিবর্তন ছাড়াই forEach পেয়ে গেল, কারণ তারা iterator() আগেই দিত, আর default method সেটার ওপর ভর করেই লেখা। একইভাবে Collection-এ এলো stream() আর removeIf(), List-এ এলো sort(). খেয়াল করুন, default method নিজের কোনো field রাখতে পারে না — সে শুধু interface-এর অন্য method ডেকে কাজ করে। তাই default method থাকা সত্ত্বেও interface state-হীনই থাকে।
static method আর private method
Java 8 থেকে interface-এ static method-ও রাখা যায় — সাধারণত সেই interface-এর object বানানোর বা সহায়ক কাজের জন্য। আগে এর জন্য আলাদা “সঙ্গী” class লাগত: Collection-এর জন্য Collections, Path-এর জন্য Paths. এখন List.of(...), Map.of(...), Comparator.comparing(...) সরাসরি interface-এই থাকে। এগুলো ডাকতে হয় interface-এর নাম দিয়েই — implement করা class এগুলো উত্তরাধিকার সূত্রে পায় না।
Java 9 থেকে এলো private interface method. দুটি default method-এ একই কয়েক লাইন লাগলে সেটা একটি private method-এ রাখা যায়, যা চুক্তির অংশ নয় — বাইরে থেকে দেখা যায় না:
interface PaymentGateway {
boolean charge(String orderId, long amountPaisa); // চুক্তি
default long totalWithFee(long amountPaisa) { // সবাই পায়
return amountPaisa + fee(amountPaisa);
}
private long fee(long amountPaisa) { // ভেতরের সাহায্যকারী
return amountPaisa / 100; // ধরা যাক ১%
}
static PaymentGateway alwaysOk() { // কারখানা method
return (id, amount) -> true;
}
}শেষ লাইনের (id, amount) -> true একটি lambda — এটা কীভাবে একটি interface-এর object হয়ে যায়, তা অংশ ৭-এ সংক্ষেপে আর পর্ব ৩৪-এ বিস্তারিত দেখবেন। এক নজরে সারাংশ:
| কী | কখন থেকে | নিজে থেকে যা ধরা হয় | কাজ |
|---|---|---|---|
| abstract method | Java 1.0 | public abstract | চুক্তি — implement করা class দেবে |
| constant | Java 1.0 | public static final | বদলায় না এমন মান (সাবধানে) |
| default method | Java 8 | public | পুরোনো class না ভেঙে নতুন আচরণ |
| static method | Java 8 | public (চাইলে Java 9 থেকে private static) | সহায়ক ও কারখানা method |
| private method | Java 9 | — | default method-গুলোর ভাগ করা ভেতরের কোড |
Diamond সংঘর্ষ — দুটি interface, একই default method
Default method আসার পর একটি পুরোনো ভয় ফিরে এলো। একটি class যদি দুটি interface implement করে, আর দুটিতেই একই নামের default method থাকে — কোনটা চলবে? C++-এ multiple inheritance-এর এই ধাঁধাকে বলে diamond problem. Java-র উত্তর পরিষ্কার: compiler অনুমান করে না, আপনাকে সিদ্ধান্ত নিতে বাধ্য করে।
1interface Printable {2 default String info() { return "Printable"; }3}4interface Shareable {5 default String info() { return "Shareable"; }6}7class Report implements Printable, Shareable {8 @Override9 public String info() {10 return Printable.super.info() + " + " + Shareable.super.info();11 }12}13System.out.println(new Report().info());
| Report-এ নিজের info() | |
| compile হয়? | |
| জমা হওয়া ফল |
- দুটি স্বাধীন interface. দুই দল আলাদাভাবে লিখেছে, দুজনেই একটি সহায়ক default method
info()রেখেছে। একা একা কোনো সমস্যা নেই। - দুটিই implement করলে — compile error. লাইন ৮–১১ না থাকলে
Reportদুটি সম্পর্কহীন default পায়, আর কোনটা বেশি “ঠিক” তা বলার কোনো ভিত্তি নেই। javac এলোমেলো বাছাই না করে থেমে যায় — ওপরের বার্তাটি OpenJDK 21-এর আসল error. - নিজে override করুন। Java-র প্রথম নিয়ম: class (বা superclass)-এ লেখা method সব default method-এর ওপরে জেতে। তাই
Reportনিজেরinfo()লিখলেই সংঘর্ষ মিটে যায়। (দ্বিতীয় নিয়ম: একটি interface অন্যটিকে extend করলে বেশি নির্দিষ্টটি জেতে; তৃতীয়: তাতেও না মিটলে এই ধাপের মতো নিজে লিখতে হবে।) Printable.super.info(). নিজের method-এর ভেতর থেকে কোনো একটি interface-এর default সংস্করণ ডাকার বিশেষ syntax —X.super.m(). এটা শুধু সেই interface-এর জন্যই চলে, যেটা এই class সরাসরি implement করে।- দুটিকেই কাজে লাগানো। চাইলে একটিকে বেছে নিতে পারতেন, এখানে দুটিকে জুড়ে দেওয়া হয়েছে। আউটপুট
Printable + Shareable— OpenJDK 21-এ চালিয়ে যাচাই করা। মূল কথা: সিদ্ধান্ত প্রোগ্রামারের, compiler-এর অনুমান নয়।
কেন এখানে C++-এর মতো জটিলতা হয় না? কারণ interface-এ state নেই। C++-এর diamond-এর আসল যন্ত্রণা হলো একই parent-এর field দুই পথে দুবার চলে আসা; Java-র interface-এ field-ই নেই (constant ছাড়া), তাই সংঘর্ষ হতে পারে শুধু method-এর শরীরে — আর সেটা মেটানোর পরিষ্কার নিয়ম আছে।
৬ · Interface একটি টাইপ — implementation নয়, চুক্তির ওপর নির্ভর করুন
আপনি সম্ভবত এমন লাইন দেখেছেন, আর পর্ব ৩০-এ নিজেও লিখবেন:
List<String> names = new ArrayList<>();বাঁ পাশে List — একটি interface; ডান পাশে ArrayList — একটি concrete class. কেন ArrayList<String> names লেখা হলো না? কারণ বাকি কোড জানবে শুধু List-এর চুক্তি (add, get, size…)। কাল যদি দেখা যায় শুরুতে ঢোকানো বেশি হচ্ছে আর LinkedList ভালো, বা একটি অপরিবর্তনীয় তালিকা চাই — শুধু ডান পাশ বদলাবেন। Method-এর parameter হিসেবে List নিলে যে কেউ যেকোনো ধরনের তালিকা পাঠাতে পারবে। ১৯৯৪ সালের বিখ্যাত বই Design Patterns-এ (“Gang of Four”) একে বলা হয়েছে program to an interface, not an implementation.
NotificationService — নিচের স্তরের ওপর নয়, চুক্তির ওপর ভর
অংশ ১-এর দোকানে ফিরে যাই। অর্ডার পাঠানো হলে খবর দেওয়ার কোড যদি সরাসরি new SmsSender() বানায়, তাহলে তিনটি সমস্যা: Email-এ বদলাতে service-এর কোড বদলাতে হবে; টেস্ট চালালেই আসল SMS চলে যাবে (আর টাকা কাটবে!); আর SMS gateway বন্ধ থাকলে টেস্টও ব্যর্থ হবে। সমাধান — service জানবে শুধু MessageSender চুক্তি, আর কোন sender ব্যবহার হবে সেটা বাইরে থেকে constructor দিয়ে দেওয়া হবে:
interface MessageSender {
boolean send(String to, String text);
default String channel() { return "unknown"; }
}
class SmsSender implements MessageSender {
public boolean send(String to, String text) {
System.out.println("SMS -> " + to + ": " + text);
return true;
}
@Override public String channel() { return "SMS"; }
}
class FakeSender implements MessageSender { // টেস্টের জন্য নকল
String last = "none";
public boolean send(String to, String text) { last = to; return true; }
}
class NotificationService {
private final MessageSender sender;
NotificationService(MessageSender sender) { this.sender = sender; }
void orderShipped(String phone, int orderId) {
sender.send(phone, "Order #" + orderId + " is on the way");
}
}
class Main {
public static void main(String[] args) {
var sms = new SmsSender();
new NotificationService(sms).orderShipped("01711000000", 42);
var fake = new FakeSender();
new NotificationService(fake).orderShipped("01811000000", 7);
System.out.println("fake last: " + fake.last + ", channels: " + sms.channel() + "/" + fake.channel());
}
}আউটপুট: SMS -> 01711000000: Order #42 is on the way আর fake last: 01811000000, channels: SMS/unknown. NotificationService-এর কোডে SmsSender বা FakeSender নামটি একবারও নেই — সে শুধু চুক্তি চেনে। একই service প্রথমবার আসল (এখানে ছাপানো) SMS পাঠাল, দ্বিতীয়বার নকল sender-এ, যে কিছু না পাঠিয়ে শুধু মনে রাখে কাকে পাঠানোর কথা ছিল — টেস্টে ঠিক এটাই যাচাই করা হয়। channel() একটি default method: SmsSender override করেছে, FakeSender করেনি, তাই সে interface-এর unknown পেল। ফোন নম্বরগুলো কাল্পনিক। কোডটি অন্য উদাহরণের চেয়ে একটু লম্বা — একটি interface আর চারটি class একসাথে দেখানোর জন্য।
এই নকশার দুটি নাম আছে, আর দুটিই ইন্টারভিউতে আসে। কে কার ওপর নির্ভর করবে — সেই নীতির নাম Dependency Inversion Principle: ওপরের স্তরের নিয়ম (খবর দেওয়া) নিচের স্তরের খুঁটিনাটির (SMS gateway) ওপর নির্ভর করবে না; দুটিই নির্ভর করবে একটি abstraction-এর (MessageSender) ওপর। আর কীভাবে নির্ভরতা পৌঁছায় — বাইরে থেকে constructor দিয়ে দেওয়া — তার নাম dependency injection. পর্ব ৪৭-এ JUnit-এর সাথে এমন নকল object-কে বলা হবে mock, আর পর্ব ৫০-এ Spring Boot এই injection নিজে থেকেই করে দেবে।
পেমেন্ট gateway — একই চুক্তি, অনেক প্রদানকারী
একটি ই-কমার্স সাইট মোবাইল ওয়ালেট আর কার্ড দুই পথেই টাকা নেয়। প্রতিটি প্রদানকারীর API সম্পূর্ণ আলাদা। কিন্তু checkout-এর কোড চায় একটিই প্রশ্ন করতে: “এই অর্ডারের এত টাকা কাটো।” তাই অংশ ৫-এর PaymentGateway চুক্তি, আর প্রতিটি প্রদানকারীর জন্য একটি class — BkashGateway, NagadGateway, CardGateway. নতুন প্রদানকারী যোগ হলে checkout-এর কোড বদলায় না, শুধু নতুন class আসে। চুক্তিতে টাকা long পয়সায় রাখা হয়েছে — double-এ টাকার হিসাব কেন বিপজ্জনক, তা পর্ব ০৬-এ দেখেছেন।
চুক্তি মানে শুধু method-এর নাম নয়। Compiler যাচাই করে শুধু signature. কিন্তু চুক্তির আসল অর্থ — “charge একই orderId-এ দুবার ডাকলে দুবার টাকা কাটবে না”, “ব্যর্থ হলে false দেবে, exception নয়” — সেটা Javadoc-এ লিখতে হয়, আর প্রতিটি implementation-কে তা মানতে হয়। কোনো implementation চুক্তির কথা মানলে কিন্তু অর্থ না মানলে, যে কোড interface দেখে লেখা, সে ভুল আচরণ পাবে। এই নীতির নাম Liskov Substitution Principle — পর্ব ৪৮-এ SOLID-এর সাথে আসবে।
৭ · Marker, functional আর JDK-র চেনা interface
Marker interface — শূন্য চুক্তি, শুধু একটি চিহ্ন
কিছু interface-এ একটিও method নেই। যেমন java.io.Serializable: একটি class implements Serializable লিখলে সে শুধু ঘোষণা করে “আমার object-কে byte-এ রূপান্তর করে ফাইলে বা নেটওয়ার্কে পাঠানো নিরাপদ।” Java-র serialization ব্যবস্থা এই চিহ্ন দেখে কাজ করে, না থাকলে NotSerializableException ছোড়ে। এমন interface-কে বলে marker interface; Cloneable আর RandomAccess-ও এর উদাহরণ। Java 5-এ annotation আসার পর নতুন “চিহ্ন” সাধারণত annotation দিয়ে দেওয়া হয় (পর্ব ৪৫), তবে marker interface-এর একটি সুবিধা থেকে যায় — সে একটি টাইপ, তাই compiler parameter-এ সেটা যাচাই করতে পারে। Serialization-এর বিস্তারিত পর্ব ৩৯-এ।
Functional interface — একটি abstract method, তাই একটি lambda
যে interface-এ ঠিক একটি abstract method আছে (default আর static method যত খুশি থাকুক), তাকে বলে functional interface. Runnable (run()), Comparator (compare()), আর ওপরের MessageSender — সবাই তাই। এমন interface-এর object বানাতে পুরো class লেখার দরকার নেই, একটি lambda-ই যথেষ্ট:
MessageSender console = (to, text) -> {
System.out.println(to + " <- " + text);
return true;
};Compiler জানে MessageSender-এ একটিই ফাঁকা ঘর, তাই lambda-টি সেই send-এর শরীর হয়ে যায়। Interface-এর ওপরে @FunctionalInterface লিখে রাখলে কেউ ভুল করে দ্বিতীয় abstract method যোগ করলে compiler আটকাবে। পুরো গল্প পর্ব ৩৪-এ।
JDK-তে প্রতিদিন যে interface-গুলো দেখবেন
| interface | চুক্তির method | implement করলে যা পান | কোথায় আবার |
|---|---|---|---|
Comparable<T> | compareTo(T) | Collections.sort, TreeMap আপনার object সাজাতে পারে | পর্ব ২৩, ৩৩ |
Runnable | run() | একটি thread-এ চালানোর মতো কাজ | পর্ব ৪০ |
AutoCloseable | close() | try-with-resources শেষে নিজে থেকে বন্ধ করে | পর্ব ২৯ |
Iterable<T> | iterator() | for-each লুপে চালানো যায়, সাথে default forEach | পর্ব ১১, ৩০ |
প্রতিটি সারির শিক্ষা একই: JDK আপনার class চেনে না, চেনে চুক্তি। আপনি চুক্তি সই করলেই JDK-র বিশাল যন্ত্রপাতি আপনার class-এর জন্য কাজ শুরু করে — একেই interface-এর আসল শক্তি বলে। Iterable সই করুন, for-each পাবেন; AutoCloseable সই করুন, try-with-resources পাবেন।
৮ · abstract class না interface — কীভাবে বেছে নেবেন
Java 8-এর পর interface-এও শরীরসহ method রাখা যায়, তাই সীমারেখা কিছুটা ঝাপসা হয়েছে। তবু চারটি প্রশ্ন করলে প্রায় সবসময় উত্তর পরিষ্কার হয়ে যায়:
| প্রশ্ন | abstract class | interface |
|---|---|---|
| State — প্রতিটি object-এর নিজস্ব field লাগবে? | হ্যাঁ, যেকোনো field রাখা যায় | না — শুধু public static final constant |
| Constructor — শুরুর মান বসানো বা যাচাই করা দরকার? | হ্যাঁ, subclass super(...) দিয়ে ডাকে | নেই |
| একাধিক — একটি class কয়টি নিতে পারে? | একটিই (extends) | যত খুশি (implements A, B, C) |
| সম্পর্ক — কী বোঝায়? | “আমি একটি …” — ঘনিষ্ঠ পরিবার | “আমি … পারি” — সম্পর্কহীন class-ও |
| Access — method-গুলো কেমন হতে পারে? | protected, package-private সব চলে | চুক্তির method সবসময় public |
| বিবর্তন — পরে নতুন method যোগ? | concrete method যোগ করা সহজ | default method দিয়ে সম্ভব, তবে সাবধানে |
ব্যবহারিক নিয়ম: শুরু করুন interface দিয়ে। যদি দেখেন কয়েকটি implementation-এ একই field আর একই কোড বারবার আসছে, তখন তাদের মাঝে একটি abstract class বসান — interface-টা রেখেই। JDK নিজে ঠিক এটাই করে: List হলো interface (চুক্তি), AbstractList হলো abstract class (ভাগ করা কোড), আর ArrayList দুটিকেই কাজে লাগায়। বাইরের কোড সবসময় List-এর কথাই বলে। আর সম্ভাব্য implementation-গুলো যদি আপনি নিজে সীমিত করতে চান — যেমন “পেমেন্টের ফল শুধু সফল, ব্যর্থ অথবা অপেক্ষমাণ” — তার জন্য Java 17-এর sealed interface আছে, পর্ব ২৬-এ।
interface: “আমি কী করতে পারি — প্রতিশ্রুতি দিলাম।”
C++-এ আলাদা interface নেই: অন্তত একটি pure virtual function (virtual double area() = 0;) থাকলেই class abstract, আর শুধু pure virtual function-ওয়ালা class-ই সেখানে “interface”; multiple inheritance পুরোপুরি চলে, তাই diamond-এর জন্য virtual inheritance লাগে। Java সেই জটিলতা এড়াতে class-এ একটি parent আর interface-এ অনেক — এই ভাগ করেছে। Python-এ abc.ABC আর @abstractmethod দিয়ে abstract class হয়, আর typing.Protocol দিয়ে “নাম না মিলিয়ে আচরণ মিলিয়ে” চুক্তি — Java-তে কিন্তু implements স্পষ্ট লিখতেই হয়। তুলনার জন্য দেখুন C++-এর abstract class ও interface আর Python-এর ABC ও Protocol পর্ব।
- Abstract class-এর object বানানো যায়? — না,
is abstract; cannot be instantiated; তবে তার টাইপের ভ্যারিয়েবল আর constructor দুটিই থাকে, constructor চলে subclass-এরsuper(...)থেকে। - Abstract class আর interface-এর মূল পার্থক্য? — abstract class state, constructor আর যেকোনো access-এর method রাখে কিন্তু একটিই extend করা যায়; interface state-হীন চুক্তি, একসাথে অনেকগুলো implement করা যায়।
- Default method কেন আনা হলো? — Java 8-এ পুরোনো implementation না ভেঙে
Iterable.forEach,Collection.stream-এর মতো method interface-এ যোগ করার জন্য। - দুটি interface-এ একই default method থাকলে? — compile error; class-কে নিজে override করতে হয়, দরকারে
A.super.m()দিয়ে একটিকে ডাকা যায়। - Interface-এর field কেমন? — নিজে থেকেই
public static final, তাই বদলানো যায় না আর object-ভিত্তিক state হয় না।
৯ · AI-এর যুগে — interface অলংকার না প্রয়োজন, সেটা আপনি ঠিক করুন
AI-কে “একটি user management system লিখে দাও” বললে প্রায়ই দেখবেন: IUserService interface আর তার একমাত্র implementation UserServiceImpl, IUserRepository আর UserRepositoryImpl — প্রতিটি class-এর পাশে একটি করে interface, যার দ্বিতীয় কোনো implementation নেই, আসার সম্ভাবনাও নেই। অনেক enterprise কোডবেস থেকে শেখা অভ্যাস। কোড চলে, কিন্তু ফাইল দ্বিগুণ, আর method খুঁজতে প্রতিবার একটি অতিরিক্ত লাফ।
| AI যা করে | কেন সমস্যা | আপনি যা করবেন |
|---|---|---|
প্রতিটি class-এর জন্য IXxx interface, একটিমাত্র implementation | কোনো বিকল্প নেই, তবু দ্বিগুণ কোড; “I” prefix Java-র রীতিও নয় (C#-এর) | জিজ্ঞেস করুন: দ্বিতীয় implementation বা টেস্টের নকল কি আসলে লাগবে? |
| Interface-এর জায়গায় abstract class, যাতে কোনো field নেই | যে class অন্য parent চায়, সে আর এটা নিতে পারে না | State না থাকলে interface চান |
Interface-এ constant ভরে দেয়, class-এ implements Constants | constant interface anti-pattern — টাইপের অর্থ নষ্ট | enum বা সংশ্লিষ্ট class-এ constant রাখতে বলুন |
Service-এর ভেতরে সরাসরি new SmsSender() | বদলানো ও টেস্ট করা কঠিন | constructor দিয়ে interface টাইপে নির্ভরতা নিতে বলুন |
| Default method দিয়ে interface-এ আসল ব্যবসার যুক্তি আর অবস্থা চালায় | default method-এর উদ্দেশ্য বিবর্তন, state-এর বিকল্প নয় | যুক্তি class-এ রাখুন, default শুধু সুবিধার জন্য |
AI-কে নকশা করতে বা রিভিউ করতে বলার সময় এই কাঠামো ব্যবহার করুন — শেষের প্রশ্নগুলোই AI-কে নিজের অভ্যাসের বাইরে ভাবতে বাধ্য করে:
AI-এর নকশা হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১C++-এ class দিয়েই সব হয়। Java কেন “interface” নামে আলাদা একটি জিনিস বানাল, আর class-এ multiple inheritance বাদ দিল?
১৯৯০-এর দশকের শুরুতে Sun Microsystems-এ James Gosling, Patrick Naughton, Mike Sheridan প্রমুখের ছোট দলটি যখন Oak (পরে Java) নকশা করছিল, তখন object-oriented দুনিয়ার প্রধান ভাষা ছিল C++. C++-এ একটি class একাধিক parent class থেকে inherit করতে পারে — এবং সেই ক্ষমতা যে কত সমস্যা আনে, দলটি তা কাছ থেকে দেখেছিল।
C++-এর অভিজ্ঞতা। দুটি parent-এর একই নামের method থাকলে কোনটা চলবে? দুটি parent যদি একই দাদা-class থেকে আসে, তাহলে দাদার field কি দুবার থাকবে? এর সমাধানে C++-কে virtual inheritance নামে আলাদা ব্যবস্থা আনতে হয়েছে, যার নিয়ম জটিল আর object-এর মেমরি-বিন্যাসও জটিল। Gosling পরে বিভিন্ন সাক্ষাৎকারে বলেছেন, Java-র লক্ষ্য ছিল C++-এর যে অংশগুলো বেশিরভাগ প্রোগ্রামার ঠিকমতো বোঝে না বা ভুল ব্যবহার করে, সেগুলো বাদ দেওয়া।
Objective-C-র প্রভাব। কিন্তু multiple inheritance পুরো বাদ দিলে একটি দরকারি জিনিস হারায়: একটি object-কে একাধিক “ভূমিকায়” দেখা। অনেক সূত্রমতে সমাধানের অনুপ্রেরণা এসেছিল Objective-C থেকে, যে ভাষা তখন NeXT কোম্পানির কম্পিউটারে ব্যবহৃত হতো। Objective-C-তে “protocol” ছিল — শুধু method-এর তালিকা, কোনো কোড বা state নেই, আর একটি class অনেকগুলো protocol মানতে পারে। Java-র interface দেখতে ঠিক এমনই। দলের সদস্য Patrick Naughton পরে এই প্রভাবের কথা বলেছেন বলে প্রায়ই উদ্ধৃত হয়; তবে এটি একমাত্র উৎস ছিল কি না, তা নিশ্চিত করে বলা কঠিন — ধারণাটি তখন অন্য ভাষাতেও ছড়িয়ে ছিল।
ভাগটা আসলে কী। Java দুই ধরনের inheritance আলাদা করল। Implementation-এর inheritance (কোড আর state) — একটিই parent থেকে, কারণ সমস্যা এখানেই। Type-এর inheritance (কোন ভূমিকায় দেখা যায়) — যত খুশি, কারণ শুধু চুক্তিতে কোনো সংঘর্ষ নেই: দুটি interface-এ একই void print() থাকলে class একটি শরীর দিলেই দুটো চুক্তিই পূরণ হয়। ফলে C++-এর diamond-এর আসল যন্ত্রণা — একই field দুবার — Java-তে তৈরিই হতে পারে না।
পরে কী বদলাল। Java 8-এর default method interface-এ কোড ঢোকাল, তাই সংঘর্ষের একটি ছোট রূপ ফিরে এলো — এই পর্বের diamond stepper সেটাই দেখায়। কিন্তু state এখনো interface-এ ঢোকেনি, আর সংঘর্ষ মেটানোর নিয়ম স্পষ্ট ও compile-এর সময়েই যাচাই হয়। C#, Kotlin, Swift-সহ পরের অনেক ভাষা একই রকম ভাগ নিয়েছে — একটি parent class, অনেক interface বা protocol.
মূল উপলব্ধি: Java multiple inheritance পুরো বাদ দেয়নি, ভাগ করেছে — যেখানে সমস্যা (state আর কোড), সেখানে একটি; যেখানে নিরাপদ (চুক্তি), সেখানে অনেক।
প্র ০২Java 8-এর default method নিয়ে অনেকে বলেন “interface আর interface থাকল না।” Brian Goetz-এর দল কোন সমস্যার মুখে পড়েছিল, আর সমাধানের দাম কী?
Java 8 (২০১৪)-এর প্রধান লক্ষ্য ছিল lambda আর Stream API — Project Lambda, যার নেতৃত্বে ছিলেন Oracle-এর Java Language Architect Brian Goetz. Lambda বানানো তুলনামূলক সহজ অংশ ছিল। আসল প্রশ্ন ছিল: lambda দিয়ে ব্যবহারযোগ্য method-গুলো Collections library-তে বসবে কোথায়?
Interface evolution সমস্যা। সবাই চাইছিল list.forEach(...), list.stream(), list.removeIf(...). কিন্তু List আর Collection interface, যা ১৯৯৮ সাল (Java 1.2) থেকে অপরিবর্তিত। হাজার হাজার library আর অ্যাপ্লিকেশনে নিজস্ব List বা Collection implementation আছে — Hibernate-এর মতো framework থেকে শুরু করে কোনো ব্যাংকের পুরোনো কোড পর্যন্ত। Interface-এ একটি abstract method যোগ করা মানে এই সবগুলোকে একসাথে ভেঙে ফেলা, আর Java-র সবচেয়ে বড় প্রতিশ্রুতি — পুরোনো কোড নতুন JDK-তে চলবে — ভঙ্গ করা। আলাদা Collections2 বা নতুন collection library বানানো যেত, কিন্তু তাতে দুটি সমান্তরাল জগৎ তৈরি হতো।
সমাধান। Goetz-এর দল interface-এ শরীরসহ method রাখার ব্যবস্থা প্রস্তাব করল। খসড়ায় এর নাম ছিল “virtual extension method”, আলোচনায় “defender method” — কারণ এটা পুরোনো implementation-কে “রক্ষা” করে। শেষে keyword দাঁড়াল default. পুরোনো class নিজে কিছু না করেই নতুন method পায়; যার ভালো সংস্করণ আছে (যেমন ArrayList নিজের ভেতরের array দিয়ে দ্রুত forEach করতে পারে), সে override করে।
দাম। প্রথমত, interface এখন আচরণও উত্তরাধিকার দেয়, তাই diamond সংঘর্ষের নিয়ম লাগল। দ্বিতীয়ত, default method কখনো কখনো কোনো পুরোনো implementation-এর গোপন শর্ত জানে না — যেমন একটি synchronized collection-এর default removeIf লক না নিয়ে চললে thread-safety ভাঙতে পারে; JDK-কে নিজের এমন কিছু class-এ override যোগ করতে হয়েছে। তৃতীয়ত, কারও নিজের class-এ যদি আগে থেকেই একই নামের কিন্তু ভিন্ন অর্থের method থাকে, তাহলে আচরণ বা compile দুটোই প্রভাবিত হতে পারে। Goetz নিজে জোর দিয়েছেন যে default method-এর উদ্দেশ্য interface-এর বিবর্তন, interface-কে abstract class বানানো নয়; তাই interface-এ এখনো field রাখা যায় না।
মূল উপলব্ধি: একটি জনপ্রিয় API-র সবচেয়ে বড় শত্রু তার নিজের সাফল্য — যত বেশি মানুষ ব্যবহার করে, তত কম বদলানো যায়। Default method হলো বদলানোর একটি সংকীর্ণ, সাবধানী দরজা; প্রতিদিনের নকশার হাতিয়ার নয়।
প্র ০৩“Program to an interface, not an implementation” — ১৯৯৪ সালের এই উপদেশ আজও কেন এত উদ্ধৃত হয়? আর এর মানে কি “সব জায়গায় Java interface”?
১৯৯৪ সালে Erich Gamma, Richard Helm, Ralph Johnson আর John Vlissides প্রকাশ করেন Design Patterns: Elements of Reusable Object-Oriented Software. চার লেখকের কারণে বইটি “Gang of Four” বা GoF বই নামে পরিচিত। এতে ২৩টি pattern আছে — এই পর্বের Template Method তার একটি — কিন্তু বইয়ের ভূমিকায় দেওয়া দুটি সাধারণ নীতি হয়তো pattern-গুলোর চেয়েও বেশি প্রভাব ফেলেছে: “Program to an interface, not an implementation” আর “Favor object composition over class inheritance.”
নীতিটির আসল অর্থ। বইটির উদাহরণ মূলত C++ আর Smalltalk-এ — যেখানে Java-র মতো interface keyword নেই। তাই এখানে “interface” মানে একটি object বাইরে যে method-গুলো প্রকাশ করে, তার সমষ্টি; সেটা একটি abstract class-ও হতে পারে। নীতিটি বলে: ভ্যারিয়েবল আর parameter ঘোষণা করুন সেই abstract টাইপে, কোনো নির্দিষ্ট concrete class-এ নয়। তাহলে ব্যবহারকারী কোড জানে না কোন class-এর object আসছে — শুধু জানে সেটা চুক্তি মানে। ফলে implementation বদলানো, নতুন যোগ করা আর পরীক্ষার সময় নকল বসানো সহজ হয়।
Java-তে দৈনন্দিন রূপ। List<String> x = new ArrayList<>(), method-এর parameter-এ Collection বা Iterable নেওয়া, return type-এ Map দেওয়া — সব এই নীতির প্রয়োগ। Joshua Bloch-এর Effective Java-তেও একটি আলাদা পরামর্শ আছে: object-কে তার interface দিয়ে উল্লেখ করুন, যদি উপযুক্ত interface থাকে।
ভুল ব্যাখ্যা। নীতিটি বলে না “প্রতিটি class-এর জন্য একটি নতুন Java interface বানাও।” যেখানে JDK বা আপনার নকশায় একটি অর্থপূর্ণ abstraction আগে থেকেই আছে (List, MessageSender), সেটা ব্যবহার করুন। যেখানে নেই, শুধু নীতি মানার জন্য একটি বানিয়ে ফেলা আরেক রকম অপচয় — প্রশ্ন ৫-এ সেটা দেখবেন। আর কিছু ক্ষেত্রে concrete টাইপই ঠিক: String, BigDecimal বা একটি record-এর জন্য interface খোঁজার কোনো মানে হয় না, কারণ এগুলো মান, ভূমিকা নয়।
কেন টিকে আছে। কারণ নীতিটি একটি স্থায়ী সত্য ধরে: সফটওয়্যারে সবচেয়ে বেশি বদলায় খুঁটিনাটি — কোন ডেটাবেস, কোন SMS কোম্পানি, কোন তালিকা — আর সবচেয়ে কম বদলায় উদ্দেশ্য। উদ্দেশ্যের ওপর নির্ভর করলে খুঁটিনাটি বদলালে কম কোড ভাঙে।
মূল উপলব্ধি: নীতিটি keyword নিয়ে নয়, নির্ভরতা নিয়ে — আপনার কোড যত কম জানে, তত কম ভাঙে। তবে abstraction খুঁজে নিন, জোর করে বানাবেন না।
প্র ০৪Dependency Inversion Principle-এ “inversion” শব্দটি কেন? কী উল্টে যায়?
Robert C. Martin (“Uncle Bob”) ১৯৯৬ সালে C++ Report পত্রিকায় “The Dependency Inversion Principle” শিরোনামে একটি প্রবন্ধ লেখেন। পরে তিনি এটিকে আরও চারটি নীতির সাথে একত্র করেন, আর ২০০০-এর দশকের শুরুতে Michael Feathers এদের প্রথম অক্ষর মিলিয়ে নাম দেন SOLID — DIP হলো তার শেষ “D”. নীতির দুটি অংশ: (ক) ওপরের স্তরের module নিচের স্তরের module-এর ওপর নির্ভর করবে না, দুটিই নির্ভর করবে abstraction-এর ওপর; (খ) abstraction খুঁটিনাটির ওপর নির্ভর করবে না, খুঁটিনাটি নির্ভর করবে abstraction-এর ওপর।
“স্বাভাবিক” দিক। পুরোনো structured programming-এ নকশা হতো ওপর থেকে নিচে: main ডাকে processOrder, সে ডাকে sendSms, সে ডাকে অপারেটরের library. কোডের নির্ভরতা আর ডাকার দিক একই — ওপর থেকে নিচে। ফল: সবচেয়ে মূল্যবান কোড (ব্যবসার নিয়ম, “অর্ডার পাঠালে খবর দাও”) সবচেয়ে অস্থির কোডের (একটি নির্দিষ্ট SMS কোম্পানির API) ওপর নির্ভরশীল। কোম্পানি বদলালে ব্যবসার কোড বদলায়।
কী উল্টায়। DIP-এ ডাকার দিক একই থাকে — চলার সময় NotificationService এখনো SMS পাঠানোর কোড ডাকে। কিন্তু source code-এর নির্ভরতার তীর উল্টে যায়। MessageSender interface-এর মালিক ওপরের স্তর; তার ভাষায় লেখা (“send(to, text)”), SMS কোম্পানির ভাষায় নয়। এখন SmsSender সেই interface-কে implements করে — অর্থাৎ নিচের স্তর ওপরের স্তরের চুক্তির ওপর নির্ভর করে। তীর নিচ থেকে ওপরে। এই পর্বের runnable উদাহরণে NotificationService-এর কোডে SmsSender শব্দটি একবারও নেই — এটাই উল্টানোর প্রমাণ।
বাস্তব ফল। ব্যবসার নিয়ম আলাদা করে compile, টেস্ট আর পুনর্ব্যবহার করা যায়। Plugin-ব্যবস্থা এভাবেই কাজ করে — JDBC (পর্ব ৪৯) একটি চুক্তি দেয়, আর প্রতিটি ডেটাবেস কোম্পানি তার driver দিয়ে সেটা implement করে; আপনার কোড MySQL না PostgreSQL তা জানে না। Spring framework-এর পুরো কাঠামো (পর্ব ৫০) এই নীতি আর dependency injection-এর ওপর দাঁড়ানো।
সতর্কতা। DIP মানে নয় যে প্রতিটি নির্ভরতার জন্য interface লাগবে। String বা ArrayList-এর মতো স্থির, বিশ্বস্ত জিনিসের ওপর সরাসরি নির্ভর করাই ঠিক। নীতি প্রয়োগ করুন সীমানায় — যেখানে কোড বাইরের জগতের সাথে কথা বলে (নেটওয়ার্ক, ডেটাবেস, ফাইল, ঘড়ি), কারণ সেগুলোই বদলায় আর টেস্টে ঝামেলা করে।
মূল উপলব্ধি: “Inversion” মানে নির্ভরতার তীর উল্টানো — চুক্তির মালিক যে ব্যবহার করে সে, যে implement করে সে নয়। তাতে মূল্যবান কোড অস্থির কোড থেকে মুক্ত থাকে।
প্র ০৫একটিমাত্র implementation-ওয়ালা interface কি overengineering? YAGNI কী বলে, আর কখন তবু interface বানানো যুক্তিসংগত?
YAGNI — “You Aren't Gonna Need It” — এসেছে ১৯৯০-এর দশকের শেষে Kent Beck, Ron Jeffries প্রমুখের Extreme Programming (XP) আন্দোলন থেকে। মূল কথা: ভবিষ্যতে হয়তো লাগবে ভেবে আজ কোনো ক্ষমতা বা কাঠামো বানাবেন না; যখন সত্যিই লাগবে, তখন বানান। কারণ অনুমান প্রায়ই ভুল হয়, আর ভুল অনুমানের কাঠামো রক্ষণাবেক্ষণের খরচ চিরকাল দিতে হয়।
“IFooService + FooServiceImpl” সংস্কৃতি। ২০০০-এর দশকের enterprise Java-তে — বিশেষ করে পুরোনো Spring আর EJB-র যুগে — প্রায় প্রতিটি service-এর জন্য interface বানানো নিয়ম হয়ে দাঁড়িয়েছিল। তার কিছু বাস্তব কারণ ছিল: পুরোনো proxy-প্রযুক্তি (JDK dynamic proxy) শুধু interface-এর জন্যই কাজ করত, আর তখনকার অনেক mock library সহজে concrete class-এর নকল বানাতে পারত না। আজ এই দুটি কারণ বেশিরভাগই নেই — Spring class-এর proxy বানাতে পারে, Mockito-র মতো library concrete class-এরও mock বানায়। তবু অভ্যাসটি থেকে গেছে, আর AI সেই কোড থেকেই শিখেছে।
একমাত্র implementation-এর খরচ। দ্বিগুণ ফাইল; method-এর signature বদলালে দুই জায়গায় বদল; IDE-তে “go to definition” চাপলে আসল কোডের বদলে ফাঁকা চুক্তিতে পৌঁছানো; আর সবচেয়ে বড় — একটি মিথ্যা সংকেত, যেন এখানে বিকল্প সম্ভব। Interface আসলে কোনো abstraction যোগ করছে না, শুধু একই জিনিস দুবার লিখছে।
কখন তবু যুক্তিসংগত। (১) সীমানায়: যে class নেটওয়ার্ক, ডেটাবেস, পেমেন্ট বা ঘড়ির সাথে কথা বলে, তার interface থাকলে টেস্টে নকল বসানো সহজ — দ্বিতীয় implementation হলো সেই টেস্টের নকল, তাই “একমাত্র” কথাটাই ভুল। (২) আপনি একটি library লিখছেন, আর বাইরের লোক নিজস্ব implementation দেবে — JDBC বা Comparable-এর মতো। (৩) module-এর সীমানা — একটি দল আরেক দলের কোডের ভেতর না দেখে শুধু চুক্তি দেখবে। (৪) Dependency Inversion: ওপরের স্তর নিজের ভাষায় চুক্তি লিখতে চায় (প্রশ্ন ৪)।
বদলানোর খরচ কম। আধুনিক IDE-তে “Extract Interface” একটি ক্লিকের কাজ। যে class আজ concrete, দরকার হলে কাল তার interface বের করে নেওয়া যায় — ব্যবহারকারীদের ভ্যারিয়েবলের টাইপ বদলাতে হয়, কিন্তু IDE সেটাও করে দেয়। তাই YAGNI এখানে নিরাপদে মানা যায়: ভবিষ্যতের অনিশ্চিত দরকারের জন্য আজ খরচ না করে, দরকার দেখা দিলে কাঠামো বদলান।
মূল উপলব্ধি: Interface-এর সংখ্যা নকশার মান মাপে না। প্রশ্ন একটাই — এই চুক্তির ওপাশে কি সত্যিই একাধিক জিনিস দাঁড়াবে (টেস্টের নকলসহ)? হ্যাঁ হলে interface, না হলে সাধারণ class.
অনুশীলন
বেশিরভাগ কাজে নতুন কোড লিখতে হবে না — ওপরের কোড বদলে দেখা আর ভাবাই আসল কাজ।
-
Error অনুমান করুন: অংশ ২-এর চালানো যায় এমন প্রোগ্রামে
Rectথেকেarea()method-টি মুছে দিন। না চালিয়ে বলুন compiler কী বলবে আর কোন লাইনে। তারপরclass Rect-এর আগেabstractবসালে কী হবে?প্রথমে:
error: Rect is not abstract and does not override abstract method area() in Shape,class Rectলাইনে। তারপরabstract class Rectকরলে সেই error চলে যাবে, কিন্তু নতুন error আসবেmain-এ:Rect is abstract; cannot be instantiated, কারণnew Rect(3, 4)লেখা আছে। শিক্ষা: abstract চিহ্ন দিয়ে দায়িত্ব শুধু নিচে ঠেলে দেওয়া যায়; শেষ পর্যন্ত কোনো concrete class-কে ফাঁকা ঘর পূরণ করতেই হবে, নইলে object বানানো যাবে না। -
নতুন sender যোগ করুন: অংশ ৬-এর প্রোগ্রামে একটি
EmailSenderলিখুন, যা"Email -> " + to + ": " + textছাপে আরchannel()-এ"Email"দেয়।main-এnew NotificationService(new EmailSender()).orderShipped("rahim@example.com", 9);যোগ করুন।NotificationService-এ কয়টি লাইন বদলাতে হলো?class EmailSender implements MessageSender { public boolean send(String to, String text) { System.out.println("Email -> " + to + ": " + text); return true; } @Override public String channel() { return "Email"; } }নতুন লাইন ছাপবে
Email -> rahim@example.com: Order #9 is on the way.NotificationService-এ একটি লাইনও বদলাতে হয়নি — শূন্য। এটাই dependency inversion-এর ফল: নতুন ক্ষমতা মানে নতুন class, পুরোনো কোডে হাত নেই। খেয়াল করুন,toparameter-এর নাম “ফোন” নয় বলে Email ঠিকানাও বসানো গেল — চুক্তির নাম সাধারণ রাখার সুফল। -
সিদ্ধান্ত নিন: নিচের প্রতিটি ক্ষেত্রে abstract class, interface, না দুটিই — এক লাইনের কারণসহ বলুন। (ক)
Vehicle: সবার নিবন্ধন নম্বর আরfare(km), ভাড়ার নিয়ম আলাদা। (খ)Exportable: Invoice, Student আর Product — তিনটিকেই CSV-তে রপ্তানি করা যায়। (গ) একটি অ্যাপের সব রিপোর্টের একই শিরোনাম-পাদটীকা, ভিন্ন মাঝের অংশ।(ক) abstract class — প্রতিটি object-এর নিজস্ব state (নিবন্ধন নম্বর) আর তা বসানোর constructor লাগে, আর রিকশা, বাস, CNG সবাই একই পরিবারের (“আমি একটি যান”)। চাইলে বাইরের কোডের জন্য একটি
FareCalculatorinterface-ও রাখা যায়। (খ) interface — তিনটি class সম্পর্কহীন, তাদের আলাদা parent থাকতে পারে, ভাগ করার state নেই; ভাগ করা একমাত্র জিনিস হলো ক্ষমতা। (গ) abstract class, Template Method — ক্রম স্থির (final generate()), শিরোনাম-পাদটীকার কোড ভাগ করা, শুধুbody()abstract. -
Diamond নিজে মেটান: অংশ ৫-এর diamond stepper-এর
Report-এ এমনভাবেinfo()লিখুন, যাতে শুধুShareable-এর সংস্করণ ফেরত আসে। তারপর ভাবুন:ReportযদিPrintable-কে সরাসরি implement না করে (শুধুShareable), তবুPrintable.super.info()লিখলে কী হবে?@Override public String info() { return Shareable.super.info(); }— ফলShareable. দ্বিতীয় প্রশ্ন: compile error.X.super.m()শুধু সেই interface-এর জন্য চলে, যা class সরাসরি implement করে (তার ঘোষণারimplementsতালিকায় আছে)। যে interface-এর সাথে কোনো সম্পর্ক নেই, তার default method এভাবে ডাকা যায় না — না হলে যেকোনো class যেকোনো interface-এর ভেতরের কোড ধার করতে পারত, আর চুক্তির ধারণাই অর্থহীন হয়ে যেত।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমJava-তে একটি abstract class-কে কাজে লাগিয়ে চালানো যায় এমন object পাওয়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 🧩 abstract class-এ ভাগ করা field, constructor আর abstract method ঘোষণা
- 🏗️ একটি subclass
extendsদিয়ে সেই class নেয় - ✏️ subclass সব abstract method-এর শরীর লেখে
- 🆕
newদিয়ে subclass-এর object তৈরি, তার constructorsuper(...)ডাকে - 📞 parent টাইপের ভ্যারিয়েবল দিয়ে method ডাকলে চলার সময় child-এর কোড চলে
abstract class Shape { abstract double area(); } — এরপর main-এ Shape s = new Shape(); লিখলে কী হবে?
- চলার সময় NullPointerException
- compile error: Shape is abstract; cannot be instantiated
- object তৈরি হবে, area() ডাকলে 0.0 আসবে
- constructor না থাকায় শুধু সতর্কবার্তা আসবে
Java-তে interface-এর ভেতরে int MAX = 5; লেখা একটি field আসলে কী?
- প্রতিটি implement করা object-এর নিজস্ব instance field
- private, শুধু interface-এর ভেতরে ব্যবহারযোগ্য
- public static final constant
- public static, পরে মান বদলানো যায়
একটি class দুটি interface implement করে, আর দুটিতেই default String info() আছে। class নিজে info() না লিখলে কী হয়?
- compile error; class-কে নিজে info() override করতে হবে
- implements তালিকার প্রথম interface-এর সংস্করণ চলে
- চলার সময় JVM যেকোনো একটি বেছে নেয়
- implements তালিকার শেষ interface-এর সংস্করণ চলে
Java 8-এ interface-এ default method যোগ করার প্রধান কারণ কী ছিল?
- interface-এ instance field রাখার সুযোগ দেওয়া
- class-এ multiple inheritance চালু করা
- interface-এ private method রাখা
- পুরোনো implementation না ভেঙে interface-এ নতুন method যোগ করা
interface Printable { void print(); } আর class Doc implements Printable { void print() { } } — compile করলে কী হবে?
- ঠিকঠাক compile হবে
- compile error: weaker access privileges, কারণ public লেখা হয়নি
- compile error: Doc must be abstract
- চলার সময় IllegalAccessError
নিচের কোন পরিস্থিতিতে interface-এর চেয়ে abstract class বেশি উপযুক্ত?
- সম্পর্কহীন কয়েকটি class-কে একটি ভাগ করা ক্ষমতা দিতে হবে
- একটি class-কে একসাথে কয়েকটি ভূমিকায় দেখাতে হবে
- subclass-গুলোর সবার নিজস্ব field ও শুরুর মান বসানোর একই constructor-যুক্তি ভাগ করতে হবে
- একটি lambda দিয়ে object বানাতে হবে
Printable p = new Invoice(); — Invoice দুটি interface (Printable আর Shareable) implement করে আর link() method আছে। p.link() লিখলে কী হবে?
- compile error: cannot find symbol
- Invoice-এর link() চলবে, কারণ আসল object Invoice
- চলার সময় ClassCastException
- Java নিজে থেকে Shareable-এ cast করে link() চালাবে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- abstract class = অর্ধেক-তৈরি parent: field, constructor, সাধারণ method ভাগ করে; abstract method subclass পূরণ করবে। Object সরাসরি বানানো যায় না, আর পূরণ না করলে subclass-কেও abstract হতে হয়।
- interface = চুক্তি: method নিজে থেকে
public abstract, fieldpublic static final; একটি class অনেক interface implement করে, কিন্তু parent class একটিই। - Java 8: default ও static, Java 9: private method — পুরোনো কোড না ভেঙে interface বদলানোর জন্য; দুটি default সংঘর্ষ করলে নিজে override করুন, দরকারে
X.super.m(). - ভ্যারিয়েবলের টাইপ ঠিক করে কী ডাকা যায়, object ঠিক করে কোন কোড চলে।
List<T> x = new ArrayList<>()— চুক্তির ওপর নির্ভর করুন, নির্ভরতা constructor দিয়ে নিন। - শুরু interface দিয়ে, ভাগ করা state ও কোড এলে abstract class — কিন্তু একটিমাত্র implementation-এর জন্য শুধু অভ্যাসবশে interface নয়; constant interface নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২২ · মাঝপথের প্রজেক্ট — একটি CLI টুলপরবর্তী পর্বমডিউল ৩-এর সব ধারণা একটি আসল ছোট প্রোগ্রামে।
- Java প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- C++ · abstract class ও interfaceগভীরে যানpure virtual function, virtual inheritance আর diamond-এর আসল চেহারা।
- Python · ABC ও Protocolপাশের ট্র্যাকabstract base class আর আচরণ মিলিয়ে চলা চুক্তি।