Inheritance, Interfaces, and Polymorphism
Polymorphic Methods and Collections
আপনি একটি free preview lesson দেখছেন।
Lesson Overview
আগের lessons-এ আমরা দেখেছি parent reference একটি child object hold করতে পারে।
ContentItem content =
new VideoLesson(...);
এবং overridden method call করলে runtime actual object-এর implementation execute হয়।
content.calculateEstimatedMinutes();
কিন্তু polymorphism-এর আসল value বোঝা যায় যখন:
- একটি method multiple implementations accept করে
- Different object types একই collection-এ রাখা হয়
- Loop-এর মধ্যে type-specific behavior automatically execute হয়
- Caller concrete class না জেনেও common contract ব্যবহার করে
- New implementation add করলেও existing processing code change করতে হয় না
ধরা যাক LiveKlass catalog-এ আছে:
VideoLesson
ArticleLesson
QuizLesson
এগুলো আলাদা classes হলেও সবগুলো ContentItem।
তাই আমরা লিখতে পারি:
List<ContentItem> contentItems =
List.of(
videoLesson,
articleLesson,
quizLesson
);
এরপর:
for (
ContentItem content
: contentItems
) {
System.out.println(
content
.calculateEstimatedMinutes()
);
}
প্রতিটি iteration-এ runtime appropriate implementation select করবে।
এই lesson-এ আমরা শিখব:
- Parent type method parameter
- Interface type method parameter
- Polymorphic collections
- Runtime dispatch inside loops
- Common processing without type-specific conditions
- Polymorphic return types
- Safe casting
instanceof- Pattern matching with
instanceof - Type checks কখন reasonable
- Repeated type checks কখন design smell
- Capability-based processing
- Open design এবং new implementation support
Learning Objectives
এই lesson শেষ করার পর আপনি পারবেন:
- Parent type parameter গ্রহণ করে reusable method লিখতে
- Interface type parameter দিয়ে capability-based method তৈরি করতে
- Different child objects একই collection-এ রাখতে
- Loop-এর মধ্যে runtime polymorphism ব্যবহার করতে
- Large type-based
if/switchlogic avoid করতে - Safeভাবে
instanceofএবং casting ব্যবহার করতে - Pattern matching syntax বুঝতে
- Polymorphic return type design করতে
- Type-specific operation এবং common operation আলাদা করতে
- Existing code না বদলে নতুন implementation add করার benefit explain করতে
Concrete-Type Methods Create Duplication
Suppose আমরা প্রতিটি content type-এর summary print করতে আলাদা method লিখি।
public static void printVideoSummary(
VideoLesson video
) {
System.out.println(
video.getTitle()
);
System.out.println(
video.calculateEstimatedMinutes()
);
}
public static void printArticleSummary(
ArticleLesson article
) {
System.out.println(
article.getTitle()
);
System.out.println(
article.calculateEstimatedMinutes()
);
}
public static void printQuizSummary(
QuizLesson quiz
) {
System.out.println(
quiz.getTitle()
);
System.out.println(
quiz.calculateEstimatedMinutes()
);
}
Methods-এর logic প্রায় same।
Difference শুধু parameter-এর concrete type।
যেহেতু সব classes ContentItem, আমরা common parent type ব্যবহার করতে পারি।
Parent Type as a Method Parameter
public static void printContentSummary(
ContentItem content
) {
System.out.println(
"Type: "
+ content.getContentType()
);
System.out.println(
"Title: "
+ content.getTitle()
);
System.out.println(
"Estimated time: "
+ content
.calculateEstimatedMinutes()
+ " minutes"
);
}
এই method accept করতে পারে:
VideoLesson
ArticleLesson
QuizLesson
Usage:
printContentSummary(
videoLesson
);
printContentSummary(
articleLesson
);
printContentSummary(
quizLesson
);
কারণ প্রতিটি child object parent type হিসেবে substitute হতে পারে।
What the Method Knows
Method parameter:
ContentItem content
Method শুধু ContentItem contract জানে।
এটি call করতে পারে:
content.getTitle();
content.publish();
content.getContentType();
content.calculateEstimatedMinutes();
কিন্তু এটি direct call করতে পারে না:
content.getVideoUrl();
content.calculateScore(...);
কারণ এগুলো ContentItem contract-এর অংশ নয়।
এটি limitation নয়; এটি abstraction boundary।
Method শুধু নিজের প্রয়োজনীয় common behavior-এর ওপর depend করছে।
Runtime Behavior Inside a Method
public static int getEstimatedMinutes(
ContentItem content
) {
return content
.calculateEstimatedMinutes();
}
Calls:
getEstimatedMinutes(
new VideoLesson(...)
);
Video implementation execute হবে।
getEstimatedMinutes(
new ArticleLesson(...)
);
Article implementation execute হবে।
Method body change হয়নি।
Runtime object different হওয়ায় behavior different।
Interface Type as a Method Parameter
Parent type shared hierarchy represent করে।
Interface type shared capability represent করে।
Suppose:
public interface Downloadable {
String getDownloadUrl();
default boolean hasDownloadAvailable() {
String url =
getDownloadUrl();
return url != null
&& !url.isBlank();
}
}
A method শুধু downloadable objects accept করতে পারে।
public static void printDownloadDetails(
Downloadable downloadable
) {
if (
!downloadable
.hasDownloadAvailable()
) {
System.out.println(
"Download is not available."
);
return;
}
System.out.println(
"Download URL: "
+ downloadable
.getDownloadUrl()
);
}
Usage:
printDownloadDetails(
videoLesson
);
printDownloadDetails(
articleLesson
);
QuizLesson যদি Downloadable implement না করে:
printDownloadDetails(
quizLesson
);
compile হবে না।
Compiler capability requirement enforce করছে।
Accept the Narrowest Useful Type
Suppose method-এর শুধু download URL প্রয়োজন।
Weak parameter:
public static void printDownload(
ContentItem content
)
এতে সব ContentItem pass করা যায়, যদিও সব downloadable নয়।
Better:
public static void printDownload(
Downloadable downloadable
)
Strong principle:
Method-এর কাজের জন্য যতটুকু contract প্রয়োজন, parameter হিসেবে ততটুকুই গ্রহণ করুন।
যদি method-এর প্রয়োজন:
Title and duration
use:
ContentItem
যদি প্রয়োজন:
Download behavior only
use:
Downloadable
যদি প্রয়োজন:
Quiz-specific question count
use:
QuizLesson
সব methodকে parent বা interface type নিতে হবে—এমন নয়।
Polymorphic Collections
Different child objects common parent type collection-এ রাখা যায়।
import java.util.List;
List<ContentItem> contentItems =
List.of(
new VideoLesson(
1L,
"Runtime Polymorphism",
"https://cdn.liveklass.io/videos/polymorphism",
18
),
new ArticleLesson(
2L,
"Polymorphic Collections",
"A polymorphic collection stores objects through a shared type."
),
new QuizLesson(
3L,
"Polymorphism Assessment",
10,
70
)
);
Collection element type:
ContentItem
Actual objects:
VideoLesson
ArticleLesson
QuizLesson
Runtime Dispatch Inside a Loop
for (
ContentItem content
: contentItems
) {
System.out.println(
content.getContentType()
);
System.out.println(
content.getTitle()
);
System.out.println(
content
.calculateEstimatedMinutes()
);
}
প্রতিটি iteration-এ variable type:
ContentItem
কিন্তু runtime object change হয়।
Execution:
VideoLesson.calculateEstimatedMinutes()
ArticleLesson.calculateEstimatedMinutes()
QuizLesson.calculateEstimatedMinutes()
Loop type-specific logic জানে না।
Calculating Total Learning Time
public static int calculateTotalMinutes(
List<ContentItem> contentItems
) {
int totalMinutes = 0;
for (
ContentItem content
: contentItems
) {
totalMinutes +=
content
.calculateEstimatedMinutes();
}
return totalMinutes;
}
Usage:
int totalMinutes =
calculateTotalMinutes(
contentItems
);
Method:
- Video duration জানে না
- Article reading formula জানে না
- Quiz timing formula জানে না
প্রতিটি object নিজের calculation own করে।
Publishing All Content
public static int publishAll(
List<ContentItem> contentItems
) {
int publishedCount = 0;
for (
ContentItem content
: contentItems
) {
if (content.publish()) {
publishedCount++;
}
}
return publishedCount;
}
Parent publication contract ব্যবহার করা হয়েছে।
Child-specific readiness method runtime-এ execute হতে পারে।
Interface Collections
Collection parent hierarchy-এর ওপরই হতে হবে না।
Interface type collectionও possible।
List<Downloadable> downloads =
List.of(
videoLesson,
articleLesson
);
Loop:
for (
Downloadable downloadable
: downloads
) {
System.out.println(
downloadable
.getDownloadUrl()
);
}
Collection guaranteedভাবে শুধু downloadable objects রাখে।
One Object Can Appear Through Different Types
VideoLesson video =
new VideoLesson(...);
Same object different references দিয়ে দেখা যায়।
ContentItem content =
video;
Downloadable downloadable =
video;
Each reference different contract expose করে।
Through ContentItem:
content.getTitle();
content.publish();
Through Downloadable:
downloadable.getDownloadUrl();
Through VideoLesson:
video.getVideoUrl();
video.calculateEstimatedMinutes();
Runtime object same।
Compile-time view different।
Polymorphism Reduces Type-Based Conditions
Weak processing:
for (
ContentItem content
: contentItems
) {
if (
content instanceof VideoLesson
) {
VideoLesson video =
(VideoLesson) content;
System.out.println(
video.getDurationInMinutes()
);
} else if (
content instanceof ArticleLesson
) {
ArticleLesson article =
(ArticleLesson) content;
System.out.println(
article
.calculateEstimatedMinutes()
);
} else if (
content instanceof QuizLesson
) {
QuizLesson quiz =
(QuizLesson) content;
System.out.println(
quiz
.calculateEstimatedMinutes()
);
}
}
এখানে caller প্রতিটি concrete type সম্পর্কে জানে।
New type add করলে caller modify করতে হবে।
AudioLesson
LiveLesson
CodingExercise
Better:
for (
ContentItem content
: contentItems
) {
System.out.println(
content
.calculateEstimatedMinutes()
);
}
Behavior object-এর কাছে থাকে।
Adding a New Content Type
Suppose:
public final class AudioLesson
extends ContentItem {
private final int durationInMinutes;
public AudioLesson(
long id,
String title,
int durationInMinutes
) {
super(
id,
title
);
if (durationInMinutes <= 0) {
throw new IllegalArgumentException(
"Audio duration must be positive."
);
}
this.durationInMinutes =
durationInMinutes;
}
@Override
public int calculateEstimatedMinutes() {
return durationInMinutes;
}
@Override
public String getContentType() {
return "AUDIO";
}
@Override
protected boolean isReadyForPublication() {
return durationInMinutes > 0;
}
}
Existing method:
calculateTotalMinutes(
contentItems
);
change করার প্রয়োজন নেই।
শুধু collection-এ object add করা যায়।
new AudioLesson(...)
এটি polymorphic design-এর গুরুত্বপূর্ণ benefit।
Open for New Implementations
Strong abstraction-based processing নতুন implementation accept করতে পারে।
Existing processing:
public static void printContentSummary(
ContentItem content
)
New subclass:
AudioLesson
Method unchanged।
এটি commonly এমন design হিসেবে describe করা হয় যেখানে code:
New types-এর জন্য extend করা যায়
Existing processing repeatedly modify করতে হয় না
তবে abstraction badly designed হলে new type existing contract fit নাও করতে পারে।
Polymorphism automaticভাবে good architecture guarantee করে না।
Polymorphic Return Types
A method parent বা interface type return করতে পারে।
public static ContentItem createFeaturedContent(
boolean preferVideo
) {
if (preferVideo) {
return new VideoLesson(
1L,
"Featured Video",
"https://cdn.liveklass.io/videos/featured",
15
);
}
return new ArticleLesson(
2L,
"Featured Article",
"This article explains polymorphic return types."
);
}
Caller:
ContentItem featured =
createFeaturedContent(
true
);
System.out.println(
featured.getContentType()
);
Method different child objects return করতে পারে, কারণ return type common parent।
What a Polymorphic Return Hides
Return type:
ContentItem
Callerকে concrete construction detail থেকে আলাদা করে।
Caller জানে:
I receive a valid ContentItem.
Caller জানে না বা জানার প্রয়োজন নেই:
VideoLesson না ArticleLesson?
যদি caller immediately concrete type check করে, abstraction benefit কমে যেতে পারে।
Interface Return Type
public static Downloadable createDownload(
boolean video
) {
if (video) {
return new VideoLesson(...);
}
return new ArticleLesson(...);
}
Caller শুধু download contract ব্যবহার করবে।
Downloadable downloadable =
createDownload(
true
);
System.out.println(
downloadable.getDownloadUrl()
);
When Concrete Return Type Is Better
সব return type abstract হওয়া উচিত নয়।
Suppose method specifically quiz create করে:
public static QuizLesson createQuiz(
String title,
int questionCount
)
Return:
QuizLesson
appropriate, কারণ caller quiz-specific operations প্রয়োজন করতে পারে।
Use abstraction when implementation independence valuable।
Casting
Parent reference child-specific operation expose করে না।
ContentItem content =
new VideoLesson(...);
This does not compile:
content.getVideoUrl();
Explicit cast:
VideoLesson video =
(VideoLesson) content;
System.out.println(
video.getVideoUrl()
);
এটি safe only if runtime object সত্যিই VideoLesson।
Unsafe Casting
ContentItem content =
new ArticleLesson(...);
VideoLesson video =
(VideoLesson) content;
Compile করতে পারে।
Runtime-এ:
ClassCastException
Compiler শুধু জানে conversion theoretically possible।
Runtime actual object incompatible।
Checking with instanceof
if (
content instanceof VideoLesson
) {
VideoLesson video =
(VideoLesson) content;
System.out.println(
video.getVideoUrl()
);
}
instanceof runtime object compatible কি না check করে।
False হলে cast execute হয় না।
Pattern Matching with instanceof
Modern Java-তে check এবং cast একসঙ্গে করা যায়।
if (
content instanceof VideoLesson video
) {
System.out.println(
video.getVideoUrl()
);
}
এখানে:
video
শুধু condition true branch-এর মধ্যে available।
Old style:
if (
content instanceof VideoLesson
) {
VideoLesson video =
(VideoLesson) content;
}
Modern style duplication কমায়।
Capability Check with instanceof
Mixed ContentItem collection থেকে downloadable content process করতে:
for (
ContentItem content
: contentItems
) {
if (
content
instanceof Downloadable downloadable
) {
System.out.println(
downloadable
.getDownloadUrl()
);
}
}
এটি concrete class check নয়।
এটি capability check।
Does this content support downloading?
এ ধরনের check কিছু পরিস্থিতিতে reasonable।
When instanceof Is Reasonable
instanceof useful হতে পারে যখন:
- External বা legacy object inspect করতে হয়
- Mixed collection থেকে optional capability detect করতে হয়
- Logging বা diagnostic information প্রয়োজন
- Serialization boundary handle করতে হয়
- Framework callback broad type দেয়
- One-time adaptation প্রয়োজন
- Type-specific data সত্যিই প্রয়োজন
Example:
if (
content
instanceof Downloadable downloadable
) {
addDownloadButton(
downloadable
.getDownloadUrl()
);
}
UI optional capability অনুযায়ী button দেখাতে পারে।
When instanceof Is a Design Smell
Repeated code:
if (
content instanceof VideoLesson
) {
// Calculate video duration
} else if (
content instanceof ArticleLesson
) {
// Calculate article duration
} else if (
content instanceof QuizLesson
) {
// Calculate quiz duration
}
এখানে behavior already polymorphic method হতে পারে।
content.calculateEstimatedMinutes();
Smell indicators:
- Same type chain multiple files-এ repeated
- New subtype add করলে many conditions update করতে হয়
- Caller child internals জানে
- Type condition business behavior select করে
- Parent contract meaningful behavior expose করছে না
Do Not Cast Just to Reach Existing Common Behavior
Unnecessary:
if (
content instanceof VideoLesson video
) {
System.out.println(
video.getTitle()
);
}
getTitle() already parent method।
Simply:
System.out.println(
content.getTitle()
);
Use cast only for genuinely type-specific contract।
Separate Collections Can Remove Capability Checks
Mixed collection:
List<ContentItem> contentItems
প্রতিবার downloadable check করতে হয়।
যদি operation শুধু downloadable objects নিয়ে হয়, collectionটি এমন হতে পারে:
List<Downloadable> downloads
Then:
for (
Downloadable downloadable
: downloads
) {
processDownload(
downloadable
);
}
No instanceof প্রয়োজন।
Data structure itself capability guarantee করছে।
Common Behavior Belongs in the Contract
Suppose every content item-এর display label প্রয়োজন।
Weak:
if (
content instanceof VideoLesson
) {
return "Video";
}
if (
content instanceof ArticleLesson
) {
return "Article";
}
Better parent contract:
public abstract String getContentType();
Then:
content.getContentType();
Type-Specific Data May Remain Type-Specific
Not every method parent-এ move করা উচিত।
Only VideoLesson has:
videoUrl
resolution
playback duration
Adding to parent:
public String getVideoUrl()
would force non-video classes to return:
null
empty string
unsupported exception
That weakens abstraction।
Keep genuinely specialized behavior in child or capability interface।
Polymorphic Collections and Mutation
Suppose mutable collection:
List<ContentItem> contentItems =
new ArrayList<>();
Add:
contentItems.add(
videoLesson
);
contentItems.add(
articleLesson
);
Collection stores object references।
It does not copy objects।
contentItems.get(0)
.publish();
Original videoLesson objectও published হবে, কারণ same object reference।
Object reference behavior আগের module-এ শেখানো হয়েছে।
Null Values in Polymorphic Collections
A collection technically null contain করতে পারে।
contentItems.add(
null
);
Then:
for (
ContentItem content
: contentItems
) {
content.publish();
}
NullPointerException হতে পারে।
Better:
- Null add না করা
- Collection creation boundary validate করা
- Empty collection use করা
- Required object relationships enforce করা
Polymorphism null safety automatically provide করে না।
Method Parameters Should Validate Required References
public static int calculateTotalMinutes(
List<ContentItem> contentItems
) {
if (contentItems == null) {
throw new IllegalArgumentException(
"Content items are required."
);
}
int totalMinutes = 0;
for (
ContentItem content
: contentItems
) {
if (content == null) {
throw new IllegalArgumentException(
"Content item cannot be null."
);
}
totalMinutes +=
content
.calculateEstimatedMinutes();
}
return totalMinutes;
}
কোথায় validation হবে তা application design-এর ওপর depend করে।
Repeated validation avoid করতে validated collection objectও design করা যায়।
Complete Example
ContentItem.java
public abstract class ContentItem {
private final long id;
private final String title;
private boolean published;
protected ContentItem(
long id,
String title
) {
if (id <= 0) {
throw new IllegalArgumentException(
"Content ID must be positive."
);
}
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Content title is required."
);
}
this.id = id;
this.title = title.strip();
this.published = false;
}
public abstract int calculateEstimatedMinutes();
public abstract String getContentType();
protected abstract boolean isReadyForPublication();
public final boolean publish() {
if (published) {
return false;
}
if (!isReadyForPublication()) {
return false;
}
published = true;
return true;
}
public final long getId() {
return id;
}
public final String getTitle() {
return title;
}
public final boolean isPublished() {
return published;
}
}
Downloadable.java
public interface Downloadable {
String getDownloadUrl();
default boolean hasDownloadAvailable() {
String url =
getDownloadUrl();
return url != null
&& !url.isBlank();
}
}
VideoLesson.java
public final class VideoLesson
extends ContentItem
implements Downloadable {
private final String videoUrl;
private final int durationInMinutes;
public VideoLesson(
long id,
String title,
String videoUrl,
int durationInMinutes
) {
super(
id,
title
);
if (
videoUrl == null
|| videoUrl.isBlank()
) {
throw new IllegalArgumentException(
"Video URL is required."
);
}
if (durationInMinutes <= 0) {
throw new IllegalArgumentException(
"Video duration must be positive."
);
}
this.videoUrl =
videoUrl.strip();
this.durationInMinutes =
durationInMinutes;
}
@Override
public int calculateEstimatedMinutes() {
return durationInMinutes;
}
@Override
public String getContentType() {
return "VIDEO";
}
@Override
protected boolean isReadyForPublication() {
return videoUrl.startsWith(
"https://"
);
}
@Override
public String getDownloadUrl() {
return videoUrl;
}
}
ArticleLesson.java
public final class ArticleLesson
extends ContentItem
implements Downloadable {
private static final int WORDS_PER_MINUTE =
200;
private final String content;
private final String downloadUrl;
public ArticleLesson(
long id,
String title,
String content,
String downloadUrl
) {
super(
id,
title
);
if (
content == null
|| content.isBlank()
) {
throw new IllegalArgumentException(
"Article content is required."
);
}
this.content =
content.strip();
this.downloadUrl =
downloadUrl == null
? ""
: downloadUrl.strip();
}
@Override
public int calculateEstimatedMinutes() {
int wordCount =
content
.split(
"\\s+"
)
.length;
int minutes =
wordCount
/ WORDS_PER_MINUTE;
return Math.max(
1,
minutes
);
}
@Override
public String getContentType() {
return "ARTICLE";
}
@Override
protected boolean isReadyForPublication() {
return content.length()
>= 20;
}
@Override
public String getDownloadUrl() {
return downloadUrl;
}
}
QuizLesson.java
public final class QuizLesson
extends ContentItem {
private static final int MINUTES_PER_QUESTION =
2;
private final int questionCount;
private final int passingScore;
public QuizLesson(
long id,
String title,
int questionCount,
int passingScore
) {
super(
id,
title
);
if (questionCount <= 0) {
throw new IllegalArgumentException(
"Question count must be positive."
);
}
if (
passingScore < 0
|| passingScore > 100
) {
throw new IllegalArgumentException(
"Passing score must be between 0 and 100."
);
}
this.questionCount =
questionCount;
this.passingScore =
passingScore;
}
@Override
public int calculateEstimatedMinutes() {
return questionCount
* MINUTES_PER_QUESTION;
}
@Override
public String getContentType() {
return "QUIZ";
}
@Override
protected boolean isReadyForPublication() {
return questionCount > 0;
}
public int calculateScore(
int correctAnswers
) {
if (
correctAnswers < 0
|| correctAnswers > questionCount
) {
throw new IllegalArgumentException(
"Correct answer count is invalid."
);
}
return correctAnswers
* 100
/ questionCount;
}
public boolean hasPassed(
int correctAnswers
) {
return calculateScore(
correctAnswers
) >= passingScore;
}
}
ContentCatalog.java
import java.util.List;
public final class ContentCatalog {
private final List<ContentItem> contentItems;
public ContentCatalog(
List<ContentItem> contentItems
) {
if (contentItems == null) {
throw new IllegalArgumentException(
"Content items are required."
);
}
if (
contentItems.stream()
.anyMatch(
item -> item == null
)
) {
throw new IllegalArgumentException(
"Content items cannot contain null."
);
}
this.contentItems =
List.copyOf(
contentItems
);
}
public int publishAll() {
int publishedCount = 0;
for (
ContentItem content
: contentItems
) {
if (content.publish()) {
publishedCount++;
}
}
return publishedCount;
}
public int calculateTotalMinutes() {
int totalMinutes = 0;
for (
ContentItem content
: contentItems
) {
totalMinutes +=
content
.calculateEstimatedMinutes();
}
return totalMinutes;
}
public void printSummaries() {
for (
ContentItem content
: contentItems
) {
System.out.println(
content.getContentType()
+ ": "
+ content.getTitle()
);
System.out.println(
"Estimated minutes: "
+ content
.calculateEstimatedMinutes()
);
System.out.println(
"Published: "
+ content.isPublished()
);
System.out.println();
}
}
public void printAvailableDownloads() {
for (
ContentItem content
: contentItems
) {
if (
content
instanceof Downloadable downloadable
&& downloadable
.hasDownloadAvailable()
) {
System.out.println(
content.getTitle()
);
System.out.println(
downloadable
.getDownloadUrl()
);
System.out.println();
}
}
}
public List<ContentItem> getContentItems() {
return contentItems;
}
}
Main.java
import java.util.List;
public class Main {
public static void main(
String[] args
) {
VideoLesson video =
new VideoLesson(
1L,
"Polymorphic Methods",
"https://cdn.liveklass.io/videos/methods",
18
);
ArticleLesson article =
new ArticleLesson(
2L,
"Polymorphic Collections",
"A polymorphic collection stores different implementations through a shared type.",
"https://cdn.liveklass.io/articles/collections.pdf"
);
QuizLesson quiz =
new QuizLesson(
3L,
"Polymorphism Assessment",
10,
70
);
ContentCatalog catalog =
new ContentCatalog(
List.of(
video,
article,
quiz
)
);
int publishedCount =
catalog.publishAll();
catalog.printSummaries();
System.out.println(
"Published items: "
+ publishedCount
);
System.out.println(
"Total learning time: "
+ catalog
.calculateTotalMinutes()
+ " minutes"
);
System.out.println();
System.out.println(
"Available downloads:"
);
catalog.printAvailableDownloads();
printQuizResult(
quiz,
8
);
}
private static void printQuizResult(
QuizLesson quiz,
int correctAnswers
) {
System.out.println(
"Quiz score: "
+ quiz.calculateScore(
correctAnswers
)
);
System.out.println(
"Passed: "
+ quiz.hasPassed(
correctAnswers
)
);
}
}
Important Design Observation
ContentCatalog common operations-এর জন্য polymorphism ব্যবহার করেছে।
content.publish();
content.calculateEstimatedMinutes();
content.getContentType();
Download optional capability হওয়ায়:
content instanceof Downloadable downloadable
ব্যবহার করেছে।
কিন্তু quiz-specific grading ContentCatalog-এ রাখা হয়নি।
if (
content instanceof QuizLesson quiz
) {
quiz.calculateScore(...);
}
এভাবে catalog-এর মধ্যে grading যোগ করলে catalog multiple responsibilities নিতে পারে।
Grading আলাদা workflow বা Gradable interface-based service-এর responsibility হওয়া better।
Engineering Note: Collections Reveal Abstraction Quality
একটি parent বা interface abstraction ভালো কি না বোঝার useful test:
Different implementations কি একই collection-এ রেখে meaningful common operations করা যায়?
যদি প্রতিটি loop-এ first কাজ হয়:
instanceof
তাহলে abstraction হয়তো যথেষ্ট useful behavior expose করছে না।
অথবা objects সত্যিকার অর্থে same processing group নয়।
Common Mistakes
Writing One Method per Concrete Type
printVideo()
printArticle()
printQuiz()
Common contract থাকলে parent parameter use করা যায়।
Using a Broad Type for a Narrow Capability
Download method-এর parameter ContentItem না হয়ে Downloadable হওয়া better।
Casting Without Checking
VideoLesson video =
(VideoLesson) content;
Wrong runtime type হলে fail করবে।
Using instanceof for Behavior Already in the Parent
Duration calculation-এর জন্য type checks unnecessary।
Moving Child-Specific Methods into the Parent
সব content-এর getVideoUrl() প্রয়োজন নেই।
Returning null for Unsupported Capabilities
public String getDownloadUrl() {
return null;
}
সব childকে method implement করতে force করার চেয়ে separate interface better।
Repeating Type Conditions Across the Codebase
New subtype add করলে many files modify করতে হয়।
Assuming Collection Copies Objects
Collection references store করে।
Allowing Null Elements Accidentally
Loop-এর মধ্যে unexpected NullPointerException হতে পারে।
Hiding Useful Concrete Type Unnecessarily
A quiz-specific method quiz return করলে concrete QuizLesson return appropriate হতে পারে।
Practice Exercises
Exercise 1: Common Summary Method
Write:
static void printSummary(
ContentItem content
)
It should print:
- Type
- Title
- Estimated minutes
- Published status
Call with three child types।
Exercise 2: Total Duration
Write:
static int calculateTotalMinutes(
List<ContentItem> contentItems
)
Use polymorphism।
Do not use instanceof।
Exercise 3: Interface Parameter
Create:
static void printDownload(
Downloadable downloadable
)
Do not accept a broader ContentItem parameter।
Exercise 4: Find the Design Smell
Refactor:
if (
content instanceof VideoLesson video
) {
total +=
video.getDurationInMinutes();
} else if (
content instanceof ArticleLesson article
) {
total +=
article.calculateEstimatedMinutes();
}
Use common polymorphic behavior।
Exercise 5: Safe Capability Detection
Given:
List<ContentItem> contentItems
Print download URL only for items implementing Downloadable।
Use pattern matching with instanceof।
Exercise 6: Add AudioLesson
Create:
AudioLesson
extends ContentItem
Then add it to the existing ContentCatalog।
Verify that:
publishAll()
calculateTotalMinutes()
printSummaries()
work without modification।
Exercise 7: Choose the Parameter Type
Choose the narrowest appropriate parameter:
- Method prints title and duration
- Method downloads a file
- Method calculates quiz score
- Method publishes any publishable object
- Method needs video resolution
Possible types:
ContentItem
Downloadable
QuizLesson
Publishable
VideoLesson
Predict the Result
Question 1
ContentItem content =
new VideoLesson(
1L,
"Polymorphism",
"https://example.com/video",
15
);
System.out.println(
content
.calculateEstimatedMinutes()
);
Question 2
List<ContentItem> items =
List.of(
new VideoLesson(...),
new ArticleLesson(...),
new QuizLesson(...)
);
Can the list contain all three objects?
Question 3
ContentItem content =
new ArticleLesson(...);
VideoLesson video =
(VideoLesson) content;
What happens?
Question 4
if (
content instanceof VideoLesson video
) {
System.out.println(
video.getVideoUrl()
);
}
Is an explicit cast needed inside the block?
Question 5
void process(
Downloadable downloadable
)
Can a non-downloadable QuizLesson be passed?
Predict the Result Answers
Answer 1
15
Runtime VideoLesson implementation execute হবে।
Answer 2
হ্যাঁ।
সবগুলো ContentItem subtype।
Answer 3
Runtime-এ:
ClassCastException
Actual object VideoLesson নয়।
Answer 4
না।
Pattern matching variable video already correctly typed।
Answer 5
না।
Unless QuizLesson implements Downloadable।
Knowledge Check
Question 1
Parent type method parameter-এর benefit কী?
Question 2
Interface type parameter কখন useful?
Question 3
Polymorphic collection কী?
Question 4
Loop-এর মধ্যে কোন implementation execute হয়?
Question 5
Parent reference child-specific method direct call করতে পারে কি?
Question 6
Casting কখন প্রয়োজন হতে পারে?
Question 7
Unsafe cast-এর result কী হতে পারে?
Question 8
instanceof কী check করে?
Question 9
Pattern matching with instanceof কী simplify করে?
Question 10
Repeated type checks কেন design smell হতে পারে?
Question 11
New subtype add করলেও existing common processing কেন কাজ করতে পারে?
Question 12
Method parameter হিসেবে broadest type নেওয়া উচিত কি?
Question 13
Interface collection-এর benefit কী?
Question 14
Polymorphic return type কী?
Question 15
সব instanceof usage কি wrong?
Knowledge Check Answers
Answer 1
একটি method multiple child implementations common contract-এর মাধ্যমে process করতে পারে।
Answer 2
Method-এর specific capability প্রয়োজন হলে এবং concrete implementation জানা প্রয়োজন না হলে।
Answer 3
Common parent বা interface type-এর collection, যেখানে different implementation objects রাখা যায়।
Answer 4
Actual runtime object-এর most specific overridden implementation।
Answer 5
না, যদি methodটি parent contract-এ না থাকে।
Answer 6
Genuinely child-specific behavior প্রয়োজন হলে।
Answer 7
ClassCastException।
Answer 8
Runtime object একটি specific class বা interface-এর compatible instance কি না।
Answer 9
Type check এবং typed variable creation একসঙ্গে করে।
Answer 10
Behavior common contract-এ থাকা উচিত হতে পারে এবং new type add করলে conditions update করতে হয়।
Answer 11
Processing parent বা interface contract-এর ওপর depend করে, concrete implementations-এর ওপর নয়।
Answer 12
না। Narrowest useful abstraction নেওয়া ভালো।
Answer 13
Collection-এর প্রতিটি element required capability support করে—এটি compile-time guarantee দেয়।
Answer 14
Method common parent বা interface type declare করে different concrete implementations return করতে পারে।
Answer 15
না। Optional capability detection এবং boundary adaptation-এর ক্ষেত্রে reasonable হতে পারে।
Lesson Summary
এই lesson-এ আমরা শিখেছি:
- Parent type parameter multiple child implementations accept করতে পারে
- Interface type parameter specific capability require করে
- Method-এর narrowest useful contract-এর ওপর depend করা ভালো
- Different child objects common parent collection-এ রাখা যায়
- Interface collection capability guarantee করে
- Runtime dispatch loop-এর মধ্যেও কাজ করে
- Common processing concrete object type জানার প্রয়োজন হয় না
- Each object নিজের specialized behavior execute করে
- Type-based conditions polymorphic methods দিয়ে replace করা যায়
- New subtype existing common processing-এর সঙ্গে কাজ করতে পারে
- Polymorphic return type concrete construction detail hide করতে পারে
- Concrete return type specific operations-এর জন্য appropriate হতে পারে
- Parent reference child-specific API expose করে না
- Explicit casting runtime failure তৈরি করতে পারে
instanceofsafe cast-এর আগে compatibility check করে- Pattern matching check এবং cast একত্র করে
- Optional capability detection-এর জন্য
instanceofreasonable হতে পারে - Common behavior select করার জন্য repeated type checks design smell হতে পারে
- Type-specific behavior unnecessarily parent-এ তোলা উচিত নয়
- Collections object references store করে, copies নয়
- Polymorphism null safety guarantee করে না
- Collection processing abstraction quality reveal করতে পারে
- Strong polymorphic code behavior-এর ওপর depend করে, concrete type-এর ওপর নয়
Next Lesson
পরবর্তী lesson:
Interfaces and Dependency Design
আমরা শিখব:
- Concrete dependency কী
- Abstraction-এর ওপর dependency
- Constructor injection
- Replaceable implementations
- Interface-based services
- Fake implementation দিয়ে testing
- External integration boundaries
- Interface কোথায় useful
- Every class-এর interface কেন প্রয়োজন নেই
- Spring dependency injection-এর foundation