Inheritance, Interfaces, and Polymorphism
Inheritance Design Traps and Composition-Based Alternatives
You are viewing a free preview lesson.
Lesson Overview
Inheritance Java-এর powerful feature।
এটি আমাদের সাহায্য করে:
- Common type hierarchy তৈরি করতে
- Parent contract share করতে
- Runtime polymorphism ব্যবহার করতে
- Specialized behavior implement করতে
কিন্তু inheritance ভুলভাবে ব্যবহার করলে codebase দ্রুত rigid এবং fragile হয়ে যেতে পারে।
একটি common mistake:
দুইটি classes-এর মধ্যে কিছু code common, তাই একটি class অন্যটিকে extend করবে।
Code duplication inheritance consider করার signal হতে পারে, কিন্তু যথেষ্ট reason নয়।
Inheritance একটি strong semantic relationship তৈরি করে:
Child is a Parent
এবং একটি behavioral promise তৈরি করে:
Parent-এর জায়গায় Child ব্যবহার করা নিরাপদ
এই promise valid না হলে hierarchy compile করলেও design incorrect হতে পারে।
এই lesson-এ আমরা শিখব:
- Inheritance শুধু code reuse-এর জন্য কেন নয়
is-aএবংhas-arelationship- Substitutability failure
- Parent contract দুর্বল করা
- Fragile base class problem
- Deep hierarchy
- Empty এবং configuration-only subclasses
- Excessive
protectedstate - Unsupported inherited behavior
- Interface explosion
- Delegation
- Composition দিয়ে variable behavior model করা
- Inheritance থেকে composition-এ refactoring
- কখন inheritance এখনো best choice
Learning Objectives
এই lesson শেষ করার পর আপনি পারবেন:
- Weak inheritance hierarchy identify করতে
- Code reuse এবং valid type relationship আলাদা করতে
- Child parent contract preserve করছে কি না evaluate করতে
UnsupportedOperationException-based hierarchy smell detect করতে- Configuration-based subclasses refactor করতে
- Composition এবং delegation ব্যবহার করতে
- Inheritance coupling explain করতে
- Deep hierarchy-এর risk বুঝতে
- Excessive
protectedstate avoid করতে - Inheritance, interface এবং composition-এর মধ্যে deliberate choice নিতে
Inheritance Is a Behavioral Contract
Consider:
public class ContentItem {
public boolean publish() {
return true;
}
}
public class VideoLesson
extends ContentItem {
}
এই declaration শুধু code reuse করছে না।
এটি বলছে:
VideoLesson is a ContentItem
এবং:
ContentItem যেখানে expected,
VideoLesson সেখানে validভাবে ব্যবহার করা যাবে।
Method:
public static void publishContent(
ContentItem content
) {
boolean published =
content.publish();
if (!published) {
System.out.println(
"Publication failed."
);
}
}
Caller expect করে প্রতিটি ContentItem publication contract follow করবে।
Child সেই expectation ভাঙলে inheritance relationship invalid হতে পারে।
Code Reuse Alone Is Not Enough
Suppose two classes title validation reuse করতে চায়।
Weak design:
public class TitleValidator {
protected boolean isValidTitle(
String title
) {
return title != null
&& !title.isBlank();
}
}
public class Course
extends TitleValidator {
}
Question:
Is a Course a TitleValidator?
না।
Course শুধু validation behavior ব্যবহার করতে চায়।
Better options:
Private Method
public final class Course {
private static boolean isValidTitle(
String title
) {
return title != null
&& !title.isBlank();
}
}
Collaborator
public final class Course {
private final CourseTitlePolicy titlePolicy;
}
Value Object
public final class CourseTitle {
private final String value;
}
Correct solution domain need-এর ওপর depend করে।
কিন্তু invalid is-a relationship তৈরি করা উচিত নয়।
The is-a Test
Inheritance-এর আগে simple sentence test করুন।
VideoLesson is a ContentItem
QuizLesson is a ContentItem
Meaningful।
Course is a List
Enrollment is a Learner
Payment is a Logger
Meaningful নয়।
তবে sentence test alone sufficient নয়।
আরও important test:
Child কি parent-এর complete behavioral contract follow করতে পারে?
Substitutability
Suppose method:
public static void publish(
ContentItem content
) {
content.publish();
}
Substitutability means:
VideoLesson
ArticleLesson
QuizLesson
প্রতিটিকে ContentItem হিসেবে pass করলে method-এর assumptions valid থাকবে।
Child যদি parent behavior reject, weaken বা unexpectedly change করে, substitutability break হয়।
Trap 1: Unsupported Parent Behavior
Parent:
public class ContentItem {
public boolean publish() {
return true;
}
public String getDownloadUrl() {
return "";
}
}
Quiz child:
public class QuizLesson
extends ContentItem {
@Override
public String getDownloadUrl() {
throw new UnsupportedOperationException(
"Quiz cannot be downloaded."
);
}
}
Problem:
Parent contract বলছে every ContentItem has download behavior।
কিন্তু child সেই behavior support করে না।
Caller:
public static void printDownload(
ContentItem content
) {
System.out.println(
content.getDownloadUrl()
);
}
Parent type দেখে caller operation safe মনে করবে।
QuizLesson runtime-এ fail করবে।
Refactor Unsupported Capability into an Interface
Parent:
public abstract class ContentItem {
// Common content behavior
}
Capability:
public interface Downloadable {
String getDownloadUrl();
}
Only supported types implement করবে।
public final class VideoLesson
extends ContentItem
implements Downloadable {
@Override
public String getDownloadUrl() {
return videoUrl;
}
}
public final class QuizLesson
extends ContentItem {
}
Download method:
public static void printDownload(
Downloadable downloadable
) {
System.out.println(
downloadable
.getDownloadUrl()
);
}
এখন unsupported objects compile-time-এ reject হয়।
Trap 2: Child Weakens Parent Guarantees
Parent contract:
publish() returns true only when content becomes published.
Bad child:
@Override
public boolean publish() {
return true;
}
State change করেনি।
Caller parent contract trust করে:
if (content.publish()) {
savePublishedContent(
content
);
}
Child true return করলেও content actually published নয়।
Method signature valid।
Behavioral contract invalid।
Compiler এটি ধরবে না।
Trap 3: Child Strengthens Preconditions
Parent:
public class ContentItem {
public void changeTitle(
String title
) {
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Title is required."
);
}
}
}
Child:
@Override
public void changeTitle(
String title
) {
if (
title == null
|| title.length() < 100
) {
throw new IllegalArgumentException(
"Video title must contain at least 100 characters."
);
}
}
Parent caller reasonably passes:
content.changeTitle(
"Java Inheritance"
);
Valid for parent contract।
Child unexpectedly rejects।
Subclass parent-এর তুলনায় much stronger input requirement impose করেছে।
এটি substitutability দুর্বল করতে পারে।
Trap 4: Child Introduces Surprising Side Effects
Parent query:
public int calculateEstimatedMinutes() {
return 0;
}
Child:
@Override
public int calculateEstimatedMinutes() {
publish();
analyticsClient.trackView();
return durationInMinutes;
}
Caller শুধু duration জানতে চেয়েছে।
কিন্তু method:
- Content publish করছে
- External analytics call করছে
Parent method query-like হলেও child command behavior introduce করেছে।
Surprising side effect hierarchyকে difficult to reason about করে।
Trap 5: Inheriting a Collection
Weak design:
public class Course
extends ArrayList<Lesson> {
}
Reason হতে পারে:
Course contains lessons
ArrayList contains elements
কিন্তু:
Course is an ArrayList
এটি domain relationship নয়।
Problems with Extending ArrayList
Caller inherited operations পায়:
course.clear();
course.remove(0);
course.add(null);
course.addAll(...);
Course domain rules bypass হতে পারে।
Suppose rules:
Published course থেকে lesson remove করা যাবে না
Null lesson allowed নয়
Duplicate lesson ID allowed নয়
Maximum 100 lessons
Inherited ArrayList methods এসব rules enforce করে না।
Use Composition for Collections
public final class Course {
private final List<Lesson> lessons =
new ArrayList<>();
public boolean addLesson(
Lesson lesson
) {
if (lesson == null) {
return false;
}
if (published) {
return false;
}
if (lessons.size() >= 100) {
return false;
}
lessons.add(
lesson
);
return true;
}
public List<Lesson> getLessons() {
return List.copyOf(
lessons
);
}
}
Relationship:
Course has Lessons
Course controlled API expose করে।
Internal collection implementation hidden থাকে।
Inheritance Exposes More API Than You May Want
Child public parent methods inherit করে।
Suppose parent:
public class MutableCollection {
public void clear() {
}
public void remove(
int index
) {
}
public void replace(
int index,
Object value
) {
}
}
Child সব methods inherit করবে।
Even if domain needs only:
addLesson()
getLessons()
Inheritance unnecessary API expose করতে পারে।
Composition callerকে narrow domain API দিতে সাহায্য করে।
Trap 6: Configuration Represented as Subclasses
Suppose course pricing types:
FreeCourse
PaidCourse
DiscountedCourse
PremiumCourse
SeasonalCourse
EmployeeCourse
Weak hierarchy:
public class FreeCourse
extends Course {
}
public class PaidCourse
extends Course {
}
public class DiscountedCourse
extends PaidCourse {
}
Question:
- Discount change হলে new subtype?
- Free promotion temporary হলে object class change করবে?
- Premium and discounted দুটো একসঙ্গে হলে?
- Regional pricing add হলে hierarchy কী হবে?
- Coupon pricing কোথায় যাবে?
Differences যদি configuration বা policy হয়, subclass explosion হতে পারে।
Subclass Explosion
Suppose dimensions:
Free or Paid
Discounted or Regular
Recorded or Live
Beginner or Advanced
Potential subclasses:
FreeRecordedBeginnerCourse
DiscountedRecordedBeginnerCourse
PaidLiveAdvancedCourse
FreeLiveBeginnerCourse
...
Each new variation combinations multiply করে।
Inheritance variation dimensions compose করতে পারে না।
Composition independent behaviors combine করতে পারে।
Composition with a Pricing Policy
public interface PricingPolicy {
long calculatePriceInPaisa(
long basePriceInPaisa
);
String getPricingLabel();
}
Regular pricing:
public final class RegularPricing
implements PricingPolicy {
@Override
public long calculatePriceInPaisa(
long basePriceInPaisa
) {
return basePriceInPaisa;
}
@Override
public String getPricingLabel() {
return "Regular";
}
}
Free pricing:
public final class FreePricing
implements PricingPolicy {
@Override
public long calculatePriceInPaisa(
long basePriceInPaisa
) {
return 0;
}
@Override
public String getPricingLabel() {
return "Free";
}
}
Percentage discount:
public final class PercentageDiscountPricing
implements PricingPolicy {
private final int percentage;
public PercentageDiscountPricing(
int percentage
) {
if (
percentage < 0
|| percentage > 100
) {
throw new IllegalArgumentException(
"Percentage must be between 0 and 100."
);
}
this.percentage =
percentage;
}
@Override
public long calculatePriceInPaisa(
long basePriceInPaisa
) {
long discount =
basePriceInPaisa
* percentage
/ 100;
return basePriceInPaisa
- discount;
}
@Override
public String getPricingLabel() {
return percentage
+ "% discount";
}
}
Course Has a Pricing Policy
public final class Course {
private final String title;
private final long basePriceInPaisa;
private PricingPolicy pricingPolicy;
public Course(
String title,
long basePriceInPaisa,
PricingPolicy pricingPolicy
) {
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
if (basePriceInPaisa < 0) {
throw new IllegalArgumentException(
"Base price cannot be negative."
);
}
if (pricingPolicy == null) {
throw new IllegalArgumentException(
"Pricing policy is required."
);
}
this.title = title.strip();
this.basePriceInPaisa =
basePriceInPaisa;
this.pricingPolicy =
pricingPolicy;
}
public long calculatePriceInPaisa() {
return pricingPolicy
.calculatePriceInPaisa(
basePriceInPaisa
);
}
public String getPricingLabel() {
return pricingPolicy
.getPricingLabel();
}
public boolean changePricingPolicy(
PricingPolicy pricingPolicy
) {
if (pricingPolicy == null) {
return false;
}
this.pricingPolicy =
pricingPolicy;
return true;
}
}
Relationship:
Course has a PricingPolicy
Course class change না করে pricing behavior replace করা যায়।
Runtime Policy Replacement
Course course =
new Course(
"Java and OOP Foundation",
499_000L,
new RegularPricing()
);
Initial price:
course.calculatePriceInPaisa();
499000
Promotion:
course.changePricingPolicy(
new PercentageDiscountPricing(
20
)
);
Discounted price:
399200
Free launch:
course.changePricingPolicy(
new FreePricing()
);
Price:
0
Object type change হয়নি।
Behavior composed collaborator দিয়ে change হয়েছে।
Delegation
Composition ব্যবহার করলে outer object collaborator-এর method call করে কাজ delegate করতে পারে।
public long calculatePriceInPaisa() {
return pricingPolicy
.calculatePriceInPaisa(
basePriceInPaisa
);
}
Course pricing formula নিজে implement করছে না।
এটি PricingPolicy-কে delegate করছে।
Delegation means:
একটি object operation complete করতে composed collaboratorকে কাজটি দেয়।
Inheritance vs Delegation
Inheritance
class DiscountedCourse
extends Course
Behavior class hierarchy দ্বারা fixed।
Delegation
class Course {
private PricingPolicy pricingPolicy;
}
Behavior collaborator দ্বারা supplied।
Delegation often offers:
- Runtime replacement
- Independent testing
- Shallower hierarchy
- Smaller parent API
- Independent behavior combinations
- Reduced coupling
Composition Does Not Mean “Never Use Inheritance”
Inheritance appropriate যখন:
- True common type exists
- Parent contract meaningful
- Child safely substitutable
- Shared state এবং lifecycle আছে
- Specialization stable
- Hierarchy shallow
- Parent extension intentionally designed
Current example:
VideoLesson is a ContentItem
ArticleLesson is a ContentItem
QuizLesson is a ContentItem
Reasonable inheritance।
Pricing difference:
FreeCourse is a different fundamental kind of Course
সবসময় true নয়।
Often:
Course has pricing behavior
better।
Trap 7: Empty Subclasses
public class FreeCourse
extends Course {
}
No fields।
No methods।
No invariants।
No specialized behavior।
Subclass শুধু type name হিসেবে exist করছে।
Ask:
- Does caller need to distinguish this type polymorphically?
- Does it enforce unique rules?
- Does it provide a stable contract?
- Could a field or policy represent the difference?
If no, subclass unnecessary হতে পারে।
When an Empty Subclass Can Still Be Meaningful
Not every empty subclass automatically wrong।
It may represent a genuine semantic type whose behavior parent already handles through protected hooks or metadata।
But burden of proof আছে।
Example:
InternalCourse
ExternalCourse
If access control, lifecycle, persistence or contracts differ elsewhere, distinct type meaningful হতে পারে।
Still, explicit behavior or invariant usually makes type value clearer।
Trap 8: Deep Hierarchies
ContentItem
└── LearningContent
└── MediaContent
└── TimedMediaContent
└── VideoContent
└── RecordedVideoLesson
To understand RecordedVideoLesson.publish(), developerকে inspect করতে হতে পারে:
RecordedVideoLesson
VideoContent
TimedMediaContent
MediaContent
LearningContent
ContentItem
Behavior কোন level-এ defined বা overridden বোঝা কঠিন হয়।
Problems with Deep Hierarchies
- Constructor chains complex
- Overridden behavior trace করা কঠিন
- Parent changes wide impact তৈরি করে
- Protected state leaks across levels
- Testing combinations বাড়ে
- Substitutability assumptions unclear
- New requirement correct level-এ place করা কঠিন
- Refactoring risky হয়
Practical guideline:
Hierarchy যত shallow রাখা যায়, তত সহজে reason করা যায়।
No universal maximum depth আছে।
Complexity behavior এবং coupling-এর ওপর depend করে।
Flattening a Hierarchy
Instead of:
ContentItem
→ MediaContent
→ TimedContent
→ VideoLesson
Possible:
public final class VideoLesson
extends ContentItem
implements Timed,
Downloadable {
}
Or composed values:
private final Duration duration;
private final MediaResource mediaResource;
Interfaces এবং composition independent capabilities express করতে পারে।
Trap 9: Excessive protected Fields
Parent:
public abstract class ContentItem {
protected long id;
protected String title;
protected boolean published;
protected int estimatedMinutes;
}
Every child direct mutation করতে পারে।
title = null;
published = true;
estimatedMinutes = -500;
Parent invariants effectively unenforced।
Prefer Private State
public abstract class ContentItem {
private final long id;
private final String title;
private boolean published;
}
Expose:
public final String getTitle() {
return title;
}
Controlled template hook:
protected abstract boolean isReadyForPublication();
Extension should happen through deliberate behavior, not raw state access।
Why protected State Creates Fragility
Child classes parent field representation-এর ওপর depend করে।
Suppose parent changes:
protected boolean published;
to:
private PublicationStatus status;
All subclasses using published break।
If subclasses use:
isPublished()
publish()
parent internals change করা easier।
Trap 10: The Fragile Base Class Problem
Parent class evolves।
Child behavior unexpectedly changes বা breaks।
Example parent version 1:
public boolean publish() {
published = true;
return true;
}
Child override:
@Override
public boolean publish() {
validateVideo();
return super.publish();
}
Later parent version 2:
public boolean publish() {
validateContent();
published = true;
notifySubscribers();
return true;
}
Child now may:
- Validate twice
- Trigger unexpected notification
- Fail due new parent rule
- Produce duplicate side effects
Child parent implementation detail-এর ওপর coupled ছিল।
Parent Changes Are Inherited Changes
Composition collaborator interface change না হলে implementation replace করা যায়।
Inheritance-এ parent method behavior change automatically every child affect করে।
This can be desirable for shared fixes।
But risky when subclasses rely on subtle behavior।
Therefore parent class should have:
- Clear contracts
- Stable invariants
- Deliberate override points
- Minimal protected API
- Predictable template methods
Trap 11: Calling Overridable Methods from Constructors
আগের lesson-এ শেখা হয়েছে:
public ContentItem() {
prepare();
}
If child overrides:
@Override
protected void prepare() {
videoUrl.validate();
}
Child field may not be initialized।
Deep hierarchy এই risk আরও বাড়ায়।
Use private/final constructor helpers।
Trap 12: Overriding for Configuration Values
Parent:
public int getMaximumAttempts() {
return 3;
}
Children:
public class BeginnerQuiz
extends Quiz {
@Override
public int getMaximumAttempts() {
return 5;
}
}
public class AdvancedQuiz
extends Quiz {
@Override
public int getMaximumAttempts() {
return 1;
}
}
If difference শুধু data/configuration:
private final int maximumAttempts;
may be simpler।
Quiz beginnerQuiz =
new Quiz(
title,
questionCount,
5
);
Not every different value needs a subtype।
Type or Configuration?
Ask:
Is the Difference Stable and Behavioral?
Video lesson calculates time from media duration
Article calculates time from word count
Subtype may be meaningful।
Is the Difference Just a Value?
Maximum attempts 3 vs 5
Discount 10% vs 20%
Field বা policy may be better।
Does Caller Need Polymorphic Distinction?
If no caller uses the type contract, subclass may not add value।
Trap 13: Interface Explosion
Interfaces:
HasId
HasTitle
HasDescription
CanPublish
CanUnpublish
CanCalculateTime
CanDownload
CanDisplay
CanValidate
A class implements:
implements HasId,
HasTitle,
CanPublish,
CanCalculateTime,
CanDisplay,
CanValidate
Potential problems:
- Too many concepts
- Navigation overhead
- Contracts too small to be meaningful
- Implementations fragmented
- Caller rarely uses interfaces independently
- Changes require many files
Small Interfaces Are Not Automatically Bad
Focused interfaces can be strong:
NotificationSender
PaymentGateway
Downloadable
Gradable
The question:
Is this interface independently useful to a caller?
Downloadable useful because a method can accept:
Downloadable downloadable
HasTitle may add little if no meaningful operation depends solely on title access।
Trap 14: One Interface per Implementation
CourseService
CourseServiceInterface
CourseRepository
CourseRepositoryInterface
Interface name শুধু concrete class-এর সঙ্গে Interface suffix।
This often indicates abstraction implementation থেকে mechanically extracted হয়েছে।
Better interface should express role:
CourseRepository
CourseCatalog
CoursePublisher
CourseFinder
Concrete implementation:
PostgresCourseRepository
InMemoryCourseRepository
Trap 15: Inheritance Across Unstable Technical Layers
Example:
public class CourseService
extends FrameworkBaseController {
}
Domain/application service framework controller hierarchy-এর সঙ্গে tied হয়ে যায়।
Or:
public class Course
extends DatabaseEntityBase {
}
Domain model persistence framework assumptions inherit করে।
Sometimes frameworks require inheritance।
But understand coupling:
- Framework lifecycle
- Serialization rules
- Proxy behavior
- Persistence fields
- Constructor restrictions
Keep domain inheritance domain-meaningful where possible।
Inheritance and Equality
Parent equality design subclasses-এর সঙ্গে difficult হতে পারে।
Parent:
@Override
public boolean equals(
Object other
) {
return other instanceof ContentItem
&& id
== ((ContentItem) other).id;
}
Child may add equality-significant state।
Questions arise:
- Parent equal to child?
- Two different child types same ID হলে equal?
- Symmetry preserved?
- Child override করলে transitivity কী হবে?
Inheritance এবং mutable entities-এর equality advanced design concern।
Simple choices:
- Stable identity in final entity class
getClass()-based equality- Avoid extending value objects
- Make immutable value classes
final
এ কারণেই CourseCode এবং Money classes final রাখা useful।
Inheritance and Serialization/Persistence
Hierarchy persistence করলে questions আসে:
One table or multiple tables?
Type discriminator কোথায়?
Subclass fields কীভাবে stored?
Unknown type কীভাবে handled?
এই module persistence mapping শেখাচ্ছে না।
Important point:
Class hierarchy শুধু Java code নয়; persistence এবং API contracts-এও complexity তৈরি করতে পারে।
Use subtype only when domain benefit cost justify করে।
Composition Can Also Be Overused
Composition automatically perfect নয়।
Suppose:
Course
├── TitlePolicy
├── PricingPolicy
├── PublicationPolicy
├── DurationPolicy
├── VisibilityPolicy
├── EnrollmentPolicy
├── CertificatePolicy
└── NotificationPolicy
Simple object unnecessarily fragmented হতে পারে।
Problems:
- Too many collaborators
- Object behavior difficult to follow
- Configuration burden
- Over-abstraction
- Small change requires many classes
Use composition for real variable behavior বা responsibility boundary।
Every if statementকে policy object বানাতে হবে না।
Choosing Between Inheritance and Composition
Use these questions।
Question 1: Is It a True Type Relationship?
VideoLesson is a ContentItem
If no, prefer composition।
Question 2: Can Child Fulfil Entire Parent Contract?
If child disables methods, hierarchy suspect।
Question 3: Is Difference Behavior or Configuration?
Configuration often field/policy।
Question 4: Does Child Need Parent State and Lifecycle?
If yes, abstract parent may help।
Question 5: Does Behavior Need Runtime Replacement?
Composition is often more flexible।
Question 6: Are Multiple Independent Variations Needed?
Composition avoids subclass combinations।
Question 7: Will Caller Use the Common Type?
If no polymorphic usage, inheritance value may be limited।
Question 8: Is Parent Designed for Safe Extension?
If not, prefer composition or final class।
Decision Guide
| Situation | Likely Choice |
|---|---|
| True common type with shared state | Abstract class or inheritance |
| Optional capability | Interface |
| Object contains another object | Composition |
| Behavior changes independently | Composition with interface |
| Difference is a simple value | Field/configuration |
| External integration boundary | Interface + composition |
| Immutable value object | Usually final class |
| Only code reuse, no type relationship | Private helper or composition |
| Multiple independent behaviors | Multiple interfaces/composed policies |
| Unsupported parent methods | Redesign hierarchy |
Refactoring Example: From Subclasses to Composition
Initial Hierarchy
public class Course {
private final long basePriceInPaisa;
public Course(
long basePriceInPaisa
) {
this.basePriceInPaisa =
basePriceInPaisa;
}
public long calculatePriceInPaisa() {
return basePriceInPaisa;
}
}
public class FreeCourse
extends Course {
public FreeCourse(
long basePriceInPaisa
) {
super(
basePriceInPaisa
);
}
@Override
public long calculatePriceInPaisa() {
return 0;
}
}
public class DiscountedCourse
extends Course {
private final int discountPercentage;
public DiscountedCourse(
long basePriceInPaisa,
int discountPercentage
) {
super(
basePriceInPaisa
);
this.discountPercentage =
discountPercentage;
}
@Override
public long calculatePriceInPaisa() {
long basePrice =
super.calculatePriceInPaisa();
return basePrice
- basePrice
* discountPercentage
/ 100;
}
}
Problems in the Initial Hierarchy
- Pricing state course type define করছে
- Course promotion change করলে object subtype change করতে হয়
- Free and discounted combinations unclear
- Additional policies নতুন subclasses create করে
- Pricing behavior tightly tied to course inheritance
- Testing course এবং pricing together হয়
Coursehierarchy business identity ও pricing variation mix করছে
Step 1: Extract the Variable Contract
public interface PricingPolicy {
long calculatePriceInPaisa(
long basePriceInPaisa
);
}
Step 2: Move Implementations
public final class RegularPricing
implements PricingPolicy {
@Override
public long calculatePriceInPaisa(
long basePriceInPaisa
) {
return basePriceInPaisa;
}
}
public final class FreePricing
implements PricingPolicy {
@Override
public long calculatePriceInPaisa(
long basePriceInPaisa
) {
return 0;
}
}
public final class DiscountPricing
implements PricingPolicy {
private final int percentage;
public DiscountPricing(
int percentage
) {
if (
percentage < 0
|| percentage > 100
) {
throw new IllegalArgumentException(
"Discount must be between 0 and 100."
);
}
this.percentage =
percentage;
}
@Override
public long calculatePriceInPaisa(
long basePriceInPaisa
) {
long discount =
basePriceInPaisa
* percentage
/ 100;
return basePriceInPaisa
- discount;
}
}
Step 3: Compose the Behavior
public final class Course {
private final String title;
private final long basePriceInPaisa;
private PricingPolicy pricingPolicy;
public Course(
String title,
long basePriceInPaisa,
PricingPolicy pricingPolicy
) {
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
if (basePriceInPaisa < 0) {
throw new IllegalArgumentException(
"Base price cannot be negative."
);
}
if (pricingPolicy == null) {
throw new IllegalArgumentException(
"Pricing policy is required."
);
}
this.title = title.strip();
this.basePriceInPaisa =
basePriceInPaisa;
this.pricingPolicy =
pricingPolicy;
}
public long calculatePriceInPaisa() {
return pricingPolicy
.calculatePriceInPaisa(
basePriceInPaisa
);
}
public boolean changePricingPolicy(
PricingPolicy pricingPolicy
) {
if (pricingPolicy == null) {
return false;
}
this.pricingPolicy =
pricingPolicy;
return true;
}
public String getTitle() {
return title;
}
}
Step 4: Use Different Behaviors
Course javaCourse =
new Course(
"Java and OOP Foundation",
499_000L,
new RegularPricing()
);
Course backendCourse =
new Course(
"Backend Development",
999_000L,
new DiscountPricing(
20
)
);
Course freeCourse =
new Course(
"Programming Introduction",
299_000L,
new FreePricing()
);
Same Course class।
Different composed behaviors।
Step 5: Replace Behavior Without Recreating the Course
javaCourse.changePricingPolicy(
new DiscountPricing(
10
)
);
Course identity unchanged।
Pricing strategy changed।
This is useful only if domain allows pricing policy changes।
If price must be immutable after publication, method should enforce that rule।
Composition flexibility must still respect domain invariants।
Delegation Keeps Responsibilities Separate
Course owns:
Title
Base price
Pricing policy selection
Course-level rules
PricingPolicy owns:
Price calculation formula
No class needs to know every pricing variant।
A Valid Inheritance Example
Not all examples should be refactored away।
public abstract class ContentItem {
private final long id;
private final String title;
protected ContentItem(
long id,
String title
) {
this.id = id;
this.title = title;
}
public abstract int calculateEstimatedMinutes();
public final long getId() {
return id;
}
public final String getTitle() {
return title;
}
}
public final class VideoLesson
extends ContentItem {
private final int durationInMinutes;
@Override
public int calculateEstimatedMinutes() {
return durationInMinutes;
}
}
Why reasonable:
- Video lesson সত্যিই content item
- Shared identity and title exist
- Every content supports duration estimation
- Child fulfils parent contract
- Specialized formula meaningful
- Hierarchy shallow
- Parent state protected through private fields
- No unsupported inherited operations
A Valid Composition Inside Inheritance
Inheritance এবং composition together use করা যায়।
public final class VideoLesson
extends ContentItem {
private final MediaResource mediaResource;
private final CompletionPolicy completionPolicy;
}
Meaning:
VideoLesson is a ContentItem
VideoLesson has a MediaResource
VideoLesson has a CompletionPolicy
Different relationships different tools দিয়ে modeled।
Prefer the Simplest Correct Design
Possible solutions:
- One class with a field
- One class with a private helper
- Composition with a concrete collaborator
- Composition through an interface
- Interface-only polymorphism
- Abstract class hierarchy
Select the least complex design that accurately models current requirements।
Do not add inheritance or policies based only on imagined future possibilities।
But do not force growing variations into one large conditional class either।
Design should evolve with evidence।
Common Mistakes
Extending a Class Only for Helper Methods
Use private helpers, utility, value object, or collaborator।
Modeling has-a as is-a
Course extends ArrayList<Lesson>
Course contains lessons; it is not a list implementation।
Keeping Unsupported Methods in the Parent
Optional capability should often be an interface।
Using Subclasses for Every Configuration Value
Fields or policies may be simpler।
Creating Empty Type-Label Subclasses
Distinct type needs semantic value, behavior, invariant, or contract।
Building Deep Hierarchies Before Requirements Exist
Start shallow।
Making Parent Fields Protected
Expose deliberate behavior instead of raw mutable state।
Depending on Parent Implementation Details
Use documented contract, not accidental behavior।
Calling super Without Understanding New Parent Side Effects
Parent evolution can change child behavior।
Replacing Every Conditional with a Strategy Interface
Simple stable conditions may not justify abstraction।
Creating Too Many Tiny Interfaces
Interface should be independently useful to callers।
Assuming Composition Has No Cost
More collaborators add configuration and conceptual overhead।
Practice Exercises
Exercise 1: Identify the Relationship
Choose inheritance or composition:
CourseandLessonVideoLessonandContentItemCourseandPricingPolicyEnrollmentandLearnerQuizLessonandGradableCourseandArrayList<Lesson>
Explain each decision।
Exercise 2: Refactor Unsupported Behavior
Parent:
public abstract class ContentItem {
public abstract String getDownloadUrl();
}
QuizLesson throws:
UnsupportedOperationException
Refactor using Downloadable।
Exercise 3: Remove Protected State
Refactor:
public abstract class ContentItem {
protected String title;
protected boolean published;
}
Use:
- Private fields
- Public queries
- Controlled publication method
- Protected behavior only if genuinely necessary
Exercise 4: Replace Configuration Subclasses
Current hierarchy:
ThreeAttemptQuiz
FiveAttemptQuiz
UnlimitedAttemptQuiz
Refactor to one QuizLesson with appropriate attempt configuration or policy।
Explain which approach is simpler।
Exercise 5: Detect Substitutability Failure
Parent contract:
save() persists the current object and returns true on success.
Child:
@Override
public boolean save() {
return true;
}
No persistence occurs।
Explain why this is invalid even though it compiles।
Exercise 6: Flatten a Hierarchy
Refactor:
ContentItem
→ MediaContent
→ DownloadableMediaContent
→ VideoLesson
Possible tools:
ContentItem abstract class
Downloadable interface
MediaResource composition
Exercise 7: Decide Whether an Interface Is Useful
Evaluate:
HasTitle
PaymentGateway
CanReturnId
NotificationSender
CourseServiceInterface
Downloadable
For each, explain whether a real caller can use the contract independently।
Exercise 8: Pricing Refactor
Implement:
PricingPolicy
RegularPricing
FreePricing
PercentageDiscountPricing
Course
Verify same Course object can use different policies without subclassing।
Predict the Result
Question 1
public class Course
extends ArrayList<Lesson> {
}
Can callers use clear() on a course?
Question 2
Parent method:
public void download() {
}
Child override:
@Override
public void download() {
throw new UnsupportedOperationException();
}
What design problem might this indicate?
Question 3
Course course =
new Course(
"Java",
499_000L,
new RegularPricing()
);
course.changePricingPolicy(
new FreePricing()
);
Does the course object need to become a FreeCourse instance?
Question 4
protected String title;
Can a child assign:
title = null;
Question 5
If a new pricing policy implements PricingPolicy, must Course.calculatePriceInPaisa() change?
Predict the Result Answers
Answer 1
হ্যাঁ।
Course inherits public ArrayList operations।
এটিই design problem-এর অংশ।
Answer 2
Parent contract too broad অথবা child wrong hierarchy-তে আছে।
Answer 3
না।
Composed pricing policy change হয়; course type unchanged।
Answer 4
হ্যাঁ, যদি access rules allow করে।
এতে parent invariant ভাঙতে পারে।
Answer 5
না।
Course interface contract-এর মাধ্যমে delegate করে।
Knowledge Check
Question 1
Code reuse alone inheritance-এর যথেষ্ট reason নয় কেন?
Question 2
Substitutability কী?
Question 3
Child parent method unsupported করলে কী indicate করতে পারে?
Question 4
Course extends ArrayList<Lesson> weak কেন?
Question 5
Configuration-only subclasses কী problem তৈরি করতে পারে?
Question 6
Subclass explosion কী?
Question 7
Delegation কী?
Question 8
Composition runtime flexibility কীভাবে দেয়?
Question 9
Protected fields fragile কেন?
Question 10
Fragile base class problem কী?
Question 11
Deep hierarchy risky কেন?
Question 12
Empty subclass কখন suspicious?
Question 13
Interface explosion কী?
Question 14
Composition কি inheritance-এর universal replacement?
Question 15
Inheritance কখন appropriate?
Knowledge Check Answers
Answer 1
Inheritance একটি semantic is-a relationship এবং behavioral contract তৈরি করে। শুধু implementation reuse সেই contract justify করে না।
Answer 2
Parent expected জায়গায় child ব্যবহার করলেও parent assumptions এবং guarantees valid থাকা।
Answer 3
Parent abstraction too broad অথবা child সেই parent type নয়।
Answer 4
Course একটি list নয়। Inherited mutation methods domain invariants bypass করতে পারে।
Answer 5
Every variation-এর জন্য new type তৈরি হয় এবং combinations দ্রুত বেড়ে যায়।
Answer 6
Multiple variation dimensions-এর combination represent করতে অনেক subclasses তৈরি হওয়া।
Answer 7
একটি object composed collaboratorকে operation সম্পন্ন করার দায়িত্ব দেওয়া।
Answer 8
Collaborator object replace বা configure করে behavior change করা যায়, outer object type change না করে।
Answer 9
Children raw state mutate করে validation bypass করতে পারে এবং parent representation-এর সঙ্গে tightly coupled হয়।
Answer 10
Parent implementation change করলে subclasses unexpectedly break বা behavior change করা।
Answer 11
Behavior trace, constructor chains, coupling এবং change impact বোঝা কঠিন হয়।
Answer 12
যখন এটি কোনো unique behavior, invariant, contract বা polymorphic value যোগ করে না।
Answer 13
অতিরিক্ত small বা mechanically created interfaces codebase fragmented এবং harder to navigate করে।
Answer 14
না। Composition-এরও configuration ও conceptual cost আছে।
Answer 15
যখন true common type, shared state/lifecycle, complete parent contract এবং safe substitutability থাকে।
Lesson Summary
এই lesson-এ আমরা শিখেছি:
- Inheritance একটি behavioral এবং semantic contract
- Code duplication inheritance-এর যথেষ্ট justification নয়
- Valid child parent-এর complete contract fulfil করে
- Substitutability inheritance design-এর central test
- Unsupported inherited behavior hierarchy smell
- Child parent guarantees দুর্বল করতে পারে না
- Child surprising side effects introduce করা উচিত নয়
has-arelationships composition দিয়ে model করা উচিত- Collection inherit করলে excessive mutation API expose হতে পারে
- Configuration values subclasses দিয়ে model করলে subclass explosion হতে পারে
- Pricing behavior policy object দিয়ে compose করা যায়
- Delegation composed collaborator-এর behavior use করে
- Composition runtime behavior replacement support করতে পারে
- Independent variation dimensions composition দিয়ে combine করা সহজ
- Empty subclasses semantic value না দিলে unnecessary
- Deep hierarchies constructor এবং behavior tracing কঠিন করে
- Private parent state protected fields-এর চেয়ে safer
- Excessive
protectedaccess parent implementation coupling বাড়ায় - Parent changes inherited child behavior affect করতে পারে
- Constructor থেকে overridable methods unsafe
- Simple values-এর difference subtype require করে না
- Optional capabilities focused interfaces দিয়ে express করা যায়
- Too many tiny interfacesও design complexity তৈরি করে
- Framework বা persistence inheritance additional coupling আনে
- Compositionও overused হতে পারে
- Inheritance এবং composition একসঙ্গে ব্যবহার করা যায়
- True
is-arelationship-এর জন্য inheritance appropriate - Variable behavior এবং
has-arelationship-এর জন্য composition often stronger - Simplest correct design নির্বাচন করা উচিত
Next Lesson
পরবর্তী lesson:
Module Practice and Assessment
আমরা একটি complete polymorphic content system তৈরি করব:
- Abstract
ContentItem VideoLessonArticleLessonQuizLesson- Overriding
- Runtime polymorphism
- Polymorphic collections
DownloadableGradable- Interface-based notification dependency
- Constructor injection
- Composition-based completion policy
- Invalid hierarchy refactoring
- Final concept review
- Predict-the-output
- Design judgment assessment