Inheritance, Interfaces, and Polymorphism

Polymorphic Methods and Collections

ReadingPreview

You are viewing a 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/switch logic 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:

  1. Method prints title and duration
  2. Method downloads a file
  3. Method calculates quiz score
  4. Method publishes any publishable object
  5. 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 তৈরি করতে পারে
  • instanceof safe cast-এর আগে compatibility check করে
  • Pattern matching check এবং cast একত্র করে
  • Optional capability detection-এর জন্য instanceof reasonable হতে পারে
  • 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