Object-Oriented Programming Foundations
Project: Build a Course Enrollment System
You are viewing a free preview lesson.
Project Overview
এই practice project-এ আমরা একটি ছোট console-based course enrollment system তৈরি করব।
Systemটি দিয়ে আমরা পারব:
- Free এবং paid course তৈরি করতে
- Course publish করতে
- Learner তৈরি করতে
- Published course-এ learner enroll করতে
- Completed lesson count update করতে
- Progress calculate করতে
- Enrollment cancel করতে
- Invalid state prevent করতে
- Related objects composition দিয়ে connect করতে
এই project-এ Module 2-এর প্রায় সব গুরুত্বপূর্ণ concept ব্যবহার করা হবে:
- Classes এবং objects
- Fields এবং methods
- Parameters এবং return values
- Constructors
this- Method overloading-এর পরিবর্তে meaningful naming
- Encapsulation
- Access control
- Object invariants
- Static members
- Static factory methods
- Object references
- Equality এবং
hashCode() toString()- Composition
- Immutable value objects
- Package organization
Project Goal
আমরা নিচের domain model তৈরি করব:
CourseCode
Course
Learner
Enrollment
Relationship:
Enrollment
├── has a Learner
└── has a Course
Responsibility:
CourseCode
একটি valid এবং immutable course code represent করবে
Course
Course information এবং publication state manage করবে
Learner
Learner identity এবং profile information represent করবে
Enrollment
একটি learner-এর একটি course-এ progress এবং enrollment state manage করবে
Domain Rules
Code লেখা শুরু করার আগে business rules define করা গুরুত্বপূর্ণ।
Course Code Rules
- Required
- Blank হতে পারবে না
- Uppercase format-এ store হবে
- Letters, numbers এবং hyphens support করবে
- Creation-এর পরে change করা যাবে না
Valid:
JAVA
JAVA-FOUNDATION
BACKEND-2026
Invalid:
""
" "
"-JAVA"
"JAVA-"
"JAVA COURSE"
Course Rules
- Course code required
- Title required
- Title সর্বোচ্চ 150 characters
- Total lessons zero-এর বেশি
- Price negative হতে পারবে না
- Free course-এর price
0 - Paid course-এর price zero-এর বেশি
- Course একবার publish করা যাবে
- Published course-এর title এই simplified model-এ change করা যাবে না
Learner Rules
- Learner ID positive হতে হবে
- Name required
- Email required
- Email-এ
@থাকতে হবে - Email lowercase format-এ store হবে
- Learner identity ID দ্বারা determined হবে
Enrollment Rules
- Learner required
- Course required
- শুধু published course-এ enroll করা যাবে
- New enrollment zero progress দিয়ে শুরু হবে
- Completed lessons negative হতে পারবে না
- Completed lessons total lessons exceed করতে পারবে না
- Cancelled enrollment-এর progress update করা যাবে না
- Completed enrollment cancel করা যাবে না
Recommended Project Structure
src/
└── main/
└── java/
└── io/
└── liveklass/
├── Main.java
├── course/
│ ├── Course.java
│ └── CourseCode.java
├── enrollment/
│ └── Enrollment.java
└── learner/
└── Learner.java
Part 1: Design Before Coding
Code দেখার আগে নিজে নিচের questions-এর answer চিন্তা করুন।
CourseCode
- এটি entity নাকি value object?
- কেন immutable হওয়া উচিত?
- Equality কোন field-এর ওপর based হবে?
Course
- কোন fields final হওয়া উচিত?
- কোন state mutable হওয়া দরকার?
- Free এবং paid course creation কীভাবে clear করা যায়?
- কেন generic
setPublished(boolean)avoid করা উচিত?
Learner
- Learner-এর equality name দিয়ে হবে, email দিয়ে হবে, নাকি ID দিয়ে?
- Email কি
toString()-এ expose করা উচিত?
Enrollment
- Progress কার responsibility?
- Course total lesson count duplicate করা উচিত কি?
- Enrollment cancel হলে progress update কীভাবে prevent করা যায়?
নিজে design করার চেষ্টা করার পরে reference implementation দেখুন।
Part 2: Implement CourseCode
Path:
src/main/java/io/liveklass/course/CourseCode.java
package io.liveklass.course;
import java.util.Objects;
public final class CourseCode {
private final String value;
public CourseCode(
String value
) {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
String normalizedValue =
value.strip()
.toUpperCase();
if (
!normalizedValue.matches(
"[A-Z0-9]+(?:-[A-Z0-9]+)*"
)
) {
throw new IllegalArgumentException(
"Course code may contain only letters, numbers, and single hyphens between parts."
);
}
this.value =
normalizedValue;
}
public String getValue() {
return value;
}
@Override
public boolean equals(
Object other
) {
if (this == other) {
return true;
}
if (
other == null
|| getClass()
!= other.getClass()
) {
return false;
}
CourseCode that =
(CourseCode) other;
return value.equals(
that.value
);
}
@Override
public int hashCode() {
return Objects.hash(
value
);
}
@Override
public String toString() {
return value;
}
}
CourseCode Design Review
Why Is It final?
public final class CourseCode
Subclass তৈরি করে immutability contract পরিবর্তন করা prevent করে।
Why Is the Field private final?
private final String value;
- External caller direct access করতে পারে না
- Constructor assignment-এর পরে reference reassign করা যায় না
Stringনিজেও immutable
Why Normalize in the Constructor?
Input:
" java-foundation "
Stored value:
JAVA-FOUNDATION
সব CourseCode object consistent representation ব্যবহার করবে।
Why Value-Based Equality?
new CourseCode("java")
এবং:
new CourseCode("JAVA")
normalize হওয়ার পরে same value represent করে।
তাই:
first.equals(second)
true হওয়া উচিত।
Part 3: Implement Course
Path:
src/main/java/io/liveklass/course/Course.java
package io.liveklass.course;
import java.util.Objects;
public class Course {
public static final int MAX_TITLE_LENGTH =
150;
private static int totalCoursesCreated;
private final CourseCode code;
private final int totalLessons;
private final long priceInPaisa;
private String title;
private boolean published;
private Course(
CourseCode code,
String title,
int totalLessons,
long priceInPaisa
) {
if (code == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
validateTitle(title);
if (totalLessons <= 0) {
throw new IllegalArgumentException(
"Total lessons must be greater than zero."
);
}
if (priceInPaisa < 0) {
throw new IllegalArgumentException(
"Course price cannot be negative."
);
}
this.code = code;
this.title = title.strip();
this.totalLessons =
totalLessons;
this.priceInPaisa =
priceInPaisa;
this.published = false;
totalCoursesCreated++;
}
public static Course freeCourse(
CourseCode code,
String title,
int totalLessons
) {
return new Course(
code,
title,
totalLessons,
0
);
}
public static Course paidCourse(
CourseCode code,
String title,
int totalLessons,
long priceInPaisa
) {
if (priceInPaisa <= 0) {
throw new IllegalArgumentException(
"Paid course price must be greater than zero."
);
}
return new Course(
code,
title,
totalLessons,
priceInPaisa
);
}
public boolean changeTitle(
String newTitle
) {
if (published) {
return false;
}
if (!isValidTitle(newTitle)) {
return false;
}
title =
newTitle.strip();
return true;
}
public boolean publish() {
if (published) {
return false;
}
published = true;
return true;
}
public boolean isFree() {
return priceInPaisa == 0;
}
public CourseCode getCode() {
return code;
}
public String getTitle() {
return title;
}
public int getTotalLessons() {
return totalLessons;
}
public long getPriceInPaisa() {
return priceInPaisa;
}
public boolean isPublished() {
return published;
}
public static int getTotalCoursesCreated() {
return totalCoursesCreated;
}
private static void validateTitle(
String title
) {
if (!isValidTitle(title)) {
throw new IllegalArgumentException(
"Course title is required and cannot exceed "
+ MAX_TITLE_LENGTH
+ " characters."
);
}
}
private static boolean isValidTitle(
String title
) {
return title != null
&& !title.isBlank()
&& title.strip().length()
<= MAX_TITLE_LENGTH;
}
@Override
public boolean equals(
Object other
) {
if (this == other) {
return true;
}
if (
other == null
|| getClass()
!= other.getClass()
) {
return false;
}
Course course =
(Course) other;
return code.equals(
course.code
);
}
@Override
public int hashCode() {
return Objects.hash(
code
);
}
@Override
public String toString() {
return "Course{"
+ "code="
+ code
+ ", title='"
+ title
+ '\''
+ ", totalLessons="
+ totalLessons
+ ", priceInPaisa="
+ priceInPaisa
+ ", published="
+ published
+ '}';
}
}
Course Design Review
Why Is the Constructor Private?
private Course(...)
Caller direct constructor use করতে পারবে না।
Object creation clear static factory methods-এর মাধ্যমে হবে:
Course.freeCourse(...)
অথবা:
Course.paidCourse(...)
এতে creation intent explicit।
Why Not Use One Constructor Everywhere?
This call:
new Course(
code,
title,
20,
0
);
দেখে 0-এর meaning immediately clear নয়।
This is clearer:
Course.freeCourse(
code,
title,
20
);
Why Does paidCourse() Validate Price Again?
Base constructor allows:
price >= 0
কারণ free course-এর price zero।
Paid factory-এর stronger rule:
price > 0
Different creation paths different constraints enforce করতে পারে।
Why Is Title Mutable but Code Final?
Course title editorially change হতে পারে।
Course code stable identity।
Title changes
Identity remains
Why Can Published Course Title Not Change?
এটি project-এর simplified business rule।
if (published) {
return false;
}
Real system-এ published course title change support করা যেতে পারে।
Important point হলো ruleটি setter-এর বাইরে meaningful behavior method-এ enforce করা।
Why Is Equality Based on Code?
Course code stable identity।
এই দুইটি objects logically same course represent করতে পারে:
Course first =
Course.freeCourse(
new CourseCode("JAVA"),
"Java Foundation",
20
);
Course second =
Course.freeCourse(
new CourseCode("java"),
"Java and OOP Foundation",
20
);
Code normalization-এর পরে same identity।
Static Counter Limitation
private static int totalCoursesCreated;
এটি শুধু current Java process-এ created objects count করে।
এটি:
- Database count নয়
- Restart-এর পরে থাকবে না
- Multiple server combine করে না
- Thread-safe নয়
Learning purpose-এর জন্য ব্যবহার করা হয়েছে।
Part 4: Implement Learner
Path:
src/main/java/io/liveklass/learner/Learner.java
package io.liveklass.learner;
import java.util.Objects;
public final class Learner {
private final long id;
private final String name;
private final String email;
public Learner(
long id,
String name,
String email
) {
if (id <= 0) {
throw new IllegalArgumentException(
"Learner ID must be positive."
);
}
if (
name == null
|| name.isBlank()
) {
throw new IllegalArgumentException(
"Learner name is required."
);
}
if (
email == null
|| email.isBlank()
|| !email.contains("@")
|| email.contains(" ")
) {
throw new IllegalArgumentException(
"A valid learner email is required."
);
}
this.id = id;
this.name = name.strip();
this.email =
email.strip()
.toLowerCase();
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
@Override
public boolean equals(
Object other
) {
if (this == other) {
return true;
}
if (
other == null
|| getClass()
!= other.getClass()
) {
return false;
}
Learner learner =
(Learner) other;
return id == learner.id;
}
@Override
public int hashCode() {
return Objects.hash(
id
);
}
@Override
public String toString() {
return "Learner{"
+ "id="
+ id
+ ", name='"
+ name
+ '\''
+ '}';
}
}
Learner Design Review
Why Is Learner Immutable Here?
এই simplified model-এ:
- ID change হবে না
- Name change support করা হয়নি
- Email change support করা হয়নি
এই কারণে object immutable।
Real platform-এ learner profile update support করা হলে controlled mutation বা replacement strategy প্রয়োজন হতে পারে।
Why Is Equality Based on ID?
দুইজন learner-এর same name থাকতে পারে।
Nur
Nur
Email changeও হতে পারে।
Stable learner ID identity-এর stronger candidate।
Why Is Email Excluded from toString()?
Email সবসময় secret নয়, কিন্তু personal information।
Logs-এ unnecessary personal data reduce করা ভালো।
toString() শুধু debugging-এর প্রয়োজনীয় information expose করছে।
Part 5: Implement Enrollment
Path:
src/main/java/io/liveklass/enrollment/Enrollment.java
package io.liveklass.enrollment;
import io.liveklass.course.Course;
import io.liveklass.learner.Learner;
public final class Enrollment {
private final Learner learner;
private final Course course;
private int completedLessons;
private boolean active;
private Enrollment(
Learner learner,
Course course
) {
if (learner == null) {
throw new IllegalArgumentException(
"Learner is required."
);
}
if (course == null) {
throw new IllegalArgumentException(
"Course is required."
);
}
if (!course.isPublished()) {
throw new IllegalArgumentException(
"Cannot enroll in an unpublished course."
);
}
this.learner = learner;
this.course = course;
this.completedLessons = 0;
this.active = true;
}
public static Enrollment enroll(
Learner learner,
Course course
) {
return new Enrollment(
learner,
course
);
}
public boolean completeLesson() {
return completeLessons(
1
);
}
public boolean completeLessons(
int lessonCount
) {
if (!active) {
return false;
}
if (isCompleted()) {
return false;
}
if (lessonCount <= 0) {
return false;
}
int updatedCount =
completedLessons
+ lessonCount;
if (
updatedCount
> course.getTotalLessons()
) {
return false;
}
completedLessons =
updatedCount;
return true;
}
public boolean cancel() {
if (!active) {
return false;
}
if (isCompleted()) {
return false;
}
active = false;
return true;
}
public int calculateRemainingLessons() {
return course.getTotalLessons()
- completedLessons;
}
public double calculateProgress() {
return completedLessons
* 100.0
/ course.getTotalLessons();
}
public boolean isCompleted() {
return completedLessons
== course.getTotalLessons();
}
public Learner getLearner() {
return learner;
}
public Course getCourse() {
return course;
}
public int getCompletedLessons() {
return completedLessons;
}
public boolean isActive() {
return active;
}
@Override
public String toString() {
return "Enrollment{"
+ "learnerId="
+ learner.getId()
+ ", courseCode="
+ course.getCode()
+ ", completedLessons="
+ completedLessons
+ ", totalLessons="
+ course.getTotalLessons()
+ ", active="
+ active
+ '}';
}
}
Enrollment Design Review
Why Does It Reference Objects?
private final Learner learner;
private final Course course;
Enrollment learner এবং course data duplicate করে না।
Avoided fields:
private String learnerName;
private String learnerEmail;
private String courseTitle;
private int totalLessons;
Related objects নিজেদের state own করে।
Why Is completedLessons Stored in Enrollment?
Progress কোনো learner-এর general property নয়।
Course-এরও general property নয়।
এটি specific relationship-এর state:
Nur's progress in Java course
তাই Enrollment এটি own করে।
Why Is totalLessons Not Duplicated?
Course already owns total lesson count।
course.getTotalLessons()
Enrollment same process-এর live course reference ব্যবহার করছে।
Historical snapshot প্রয়োজন হলে design different হতে পারে।
Why Use enroll()?
Enrollment.enroll(
learner,
course
);
Creation intent direct constructor-এর চেয়ে clearer।
Factory future-এ additional creation rules coordinate করার জায়গা দিতে পারে।
Why Does completeLesson() Delegate?
public boolean completeLesson() {
return completeLessons(1);
}
Core update logic duplicate করা হয়নি।
সব validation থাকে:
completeLessons(int lessonCount)
method-এ।
Why No setCompletedLessons()?
Caller arbitrary progress set করতে পারলে:
enrollment.setCompletedLessons(
-500
);
অথবা:
enrollment.setCompletedLessons(
10_000
);
invariant ভাঙতে পারে।
Meaningful transition:
completeLesson()
completeLessons(3)
Why Is Completion Calculated?
Stored field নেই:
private boolean completed;
Completion existing state থেকে derive করা যায়।
completedLessons
== course.getTotalLessons()
Duplicate derived state avoid করা হয়েছে।
Part 6: Implement Main
Path:
src/main/java/io/liveklass/Main.java
package io.liveklass;
import io.liveklass.course.Course;
import io.liveklass.course.CourseCode;
import io.liveklass.enrollment.Enrollment;
import io.liveklass.learner.Learner;
public class Main {
public static void main(
String[] args
) {
Course javaCourse =
Course.freeCourse(
new CourseCode(
"java-foundation"
),
"Java Foundation",
20
);
boolean titleChanged =
javaCourse.changeTitle(
"Java and OOP Foundation"
);
boolean published =
javaCourse.publish();
Learner nur =
new Learner(
1L,
"Nur",
"NUR@EXAMPLE.COM"
);
Enrollment enrollment =
Enrollment.enroll(
nur,
javaCourse
);
boolean firstUpdate =
enrollment.completeLessons(
5
);
boolean secondUpdate =
enrollment.completeLesson();
boolean invalidUpdate =
enrollment.completeLessons(
100
);
System.out.println(
"Title changed: "
+ titleChanged
);
System.out.println(
"Course published: "
+ published
);
System.out.println(
"Total course objects created: "
+ Course
.getTotalCoursesCreated()
);
System.out.println(
"Course: "
+ javaCourse
);
System.out.println(
"Learner: "
+ nur
);
System.out.println(
"Enrollment: "
+ enrollment
);
System.out.println(
"First progress update: "
+ firstUpdate
);
System.out.println(
"Second progress update: "
+ secondUpdate
);
System.out.println(
"Invalid progress update: "
+ invalidUpdate
);
System.out.println(
"Progress: "
+ "%.2f%%".formatted(
enrollment
.calculateProgress()
)
);
System.out.println(
"Remaining lessons: "
+ enrollment
.calculateRemainingLessons()
);
System.out.println(
"Completed: "
+ enrollment.isCompleted()
);
System.out.println(
"Active: "
+ enrollment.isActive()
);
}
}
Expected Output
Title changed: true
Course published: true
Total course objects created: 1
Course: Course{code=JAVA-FOUNDATION, title='Java and OOP Foundation', totalLessons=20, priceInPaisa=0, published=true}
Learner: Learner{id=1, name='Nur'}
Enrollment: Enrollment{learnerId=1, courseCode=JAVA-FOUNDATION, completedLessons=6, totalLessons=20, active=true}
First progress update: true
Second progress update: true
Invalid progress update: false
Progress: 30.00%
Remaining lessons: 14
Completed: false
Active: true
Part 7: Compile and Run
Project root থেকে:
javac -d out \
src/main/java/io/liveklass/course/CourseCode.java \
src/main/java/io/liveklass/course/Course.java \
src/main/java/io/liveklass/learner/Learner.java \
src/main/java/io/liveklass/enrollment/Enrollment.java \
src/main/java/io/liveklass/Main.java
Run:
java -cp out io.liveklass.Main
macOS বা Linux-এ সব source একসঙ্গে compile করতে:
javac -d out \
$(find src/main/java -name "*.java")
Then:
java -cp out io.liveklass.Main
IDE ব্যবহার করলে Main.main() run করা যাবে।
Part 8: Verify Object Invariants
Project run করার পরে নিচের invalid scenarios test করুন।
Test 1: Blank Course Code
new CourseCode(
" "
);
Expected:
IllegalArgumentException
Test 2: Invalid Course Code Format
new CourseCode(
"JAVA COURSE"
);
Expected:
IllegalArgumentException
Test 3: Negative Course Price
Course.paidCourse(
new CourseCode(
"BACKEND"
),
"Backend Development",
30,
-100
);
Expected:
IllegalArgumentException
Test 4: Zero Paid Course Price
Course.paidCourse(
new CourseCode(
"BACKEND"
),
"Backend Development",
30,
0
);
Expected:
IllegalArgumentException
A free course should use:
Course.freeCourse(...)
Test 5: Enroll Before Publishing
Course course =
Course.freeCourse(
new CourseCode(
"JAVA"
),
"Java Foundation",
20
);
Enrollment.enroll(
learner,
course
);
Expected:
IllegalArgumentException
Test 6: Complete Too Many Lessons
boolean updated =
enrollment.completeLessons(
100
);
Expected:
false
Existing state unchanged থাকবে।
Test 7: Cancel Then Update Progress
enrollment.cancel();
boolean updated =
enrollment.completeLesson();
Expected:
false
Test 8: Complete Then Cancel
একটি 2-lesson course তৈরি করুন।
Enrollment shortEnrollment =
Enrollment.enroll(
learner,
shortCourse
);
shortEnrollment.completeLessons(
2
);
boolean cancelled =
shortEnrollment.cancel();
Expected:
false
Completed enrollment cancel করা যাবে না।
Part 9: Equality Verification
Course Code Equality
CourseCode first =
new CourseCode(
"java-foundation"
);
CourseCode second =
new CourseCode(
"JAVA-FOUNDATION"
);
System.out.println(
first == second
);
System.out.println(
first.equals(second)
);
Expected:
false
true
References different।
Logical values same।
Course Equality
Course first =
Course.freeCourse(
new CourseCode(
"JAVA"
),
"Java Foundation",
20
);
Course second =
Course.freeCourse(
new CourseCode(
"java"
),
"Modern Java",
30
);
System.out.println(
first.equals(second)
);
Expected:
true
কারণ equality course code-based।
এখানে একটি গুরুত্বপূর্ণ design observation আছে:
Same identity
Different state
Real system-এ same course identity-এর conflicting copies handle করার responsibility repository, persistence context, versioning, বা application architecture-এর হতে পারে।
Part 10: Design Decisions Explained
Why Not Create One Giant Class?
Weak design:
public class CourseEnrollmentSystem {
private String learnerName;
private String learnerEmail;
private String courseCode;
private String courseTitle;
private long price;
private int totalLessons;
private int completedLessons;
private boolean published;
}
Problems:
- Multiple responsibilities
- Repeated data
- Weak ownership
- Hard-to-test logic
- Invalid combinations
- Future changes difficult
Separate objects better model domain concepts।
Why Not Use Public Fields?
public int completedLessons;
Caller business rules bypass করতে পারে।
Encapsulation forces:
enrollment.completeLessons(3);
Why Not Add Setters for Everything?
Avoid:
setPublished(true);
setCompletedLessons(10);
setCourseCode("NEW");
setActive(false);
These methods low-level state manipulation expose করে।
Meaningful behavior:
publish();
completeLessons(10);
cancel();
Why Is CourseCode a Separate Class?
Without value object:
String courseCode;
Every callerকে separately মনে রাখতে হয়:
- Blank allowed নয়
- Uppercase করতে হবে
- Spaces allowed নয়
- Format validate করতে হবে
CourseCode object একবার validation করে guaranteed valid value represent করে।
Why Not Validate Duplicate Enrollment?
Rule:
A learner cannot enroll in the same course twice.
শুধু নতুন Enrollment object এই rule check করতে পারে না।
কারণ existing enrollments জানতে database বা collection query প্রয়োজন।
Possible application service:
EnrollmentService.enroll(
learner,
course
);
Service:
- Existing enrollment search করবে
- Course availability check করবে
- Payment status check করতে পারে
- তারপর
Enrollmentতৈরি করবে
সব rules domain object-এর ভেতরে force করা উচিত নয়।
Part 11: Extension Tasks
Reference implementation complete করার পরে নিচের extensions নিজে implement করুন।
Extension 1: Add Course Price Display
Course-এ method add করুন:
public String formatPrice()
Expected:
Free
অথবা:
BDT 4990.00
Presentation logic domain class-এ রাখা উচিত কি না, সেটিও evaluate করুন।
Alternative:
CoursePriceFormatter
Extension 2: Add Enrollment Status
Boolean:
active
এর পরিবর্তে enum concept ব্যবহার করুন:
ACTIVE
COMPLETED
CANCELLED
Enum detailedভাবে পরবর্তী content-এ শেখানো হতে পারে।
Think about allowed transitions:
ACTIVE → COMPLETED
ACTIVE → CANCELLED
COMPLETED → ?
CANCELLED → ?
Extension 3: Add Completion Time
Enrollment complete হলে timestamp record করুন।
Possible type:
LocalDateTime
Questions:
- Field initial value কী হবে?
- Completion-এর আগে
nullacceptable? - Immutable time type কেন useful?
Extension 4: Add Money
Raw:
long priceInPaisa
এর পরিবর্তে immutable Money value object ব্যবহার করুন।
Requirements:
- Negative amount reject
- Value-based equality
add()applyDiscount()- Meaningful
toString()
Extension 5: Prevent Duplicate Completion
Current code:
if (isCompleted()) {
return false;
}
Complete হওয়ার পরে progress update reject করে।
Test লিখে verify করুন।
Extension 6: Add Enrollment Summary
Add:
public String createSummary()
Possible result:
Nur is 30.00% through Java and OOP Foundation.
Evaluate whether this is domain behavior or presentation concern।
Extension 7: Add Multiple Enrollments
Main-এ:
- Subu
- Sumu
- Jalisa
তিনজন learner তৈরি করুন।
একই course-এ separate enrollments তৈরি করুন।
Verify করুন প্রতিটি enrollment independent progress maintain করে।
Part 12: Review Questions
Question 1
CourseCode কেন immutable?
Question 2
Course কেন static factory methods ব্যবহার করছে?
Question 3
Enrollment কেন learner name এবং course title copy করছে না?
Question 4
Course code এবং course title-এর mutability আলাদা কেন?
Question 5
completeLessons() assignment-এর আগে candidate state calculate করে কেন?
Question 6
isCompleted() কেন stored boolean নয়?
Question 7
Course equality কোন field-এর ওপর based?
Question 8
Enrollment duplicate enrollment rule enforce করতে পারে না কেন?
Question 9
Static course counter production database count নয় কেন?
Question 10
toString()-এ learner email না রাখার benefit কী?
Review Answers
Answer 1
Course code stable value এবং identity represent করে। Value change মানে নতুন code।
Answer 2
Free এবং paid creation paths meaningful names দিয়ে express করতে এবং invalid combinations prevent করতে।
Answer 3
Learner এবং Course objects নিজেদের data own করে। Copy করলে stale বা inconsistent state তৈরি হতে পারে।
Answer 4
Code stable identity। Title editable descriptive state।
Answer 5
Invalid state commit হওয়ার আগে updated value validate করতে।
Answer 6
Completion existing progress এবং total lesson count থেকে derive করা যায়। আলাদা boolean stale হতে পারে।
Answer 7
Immutable CourseCode।
Answer 8
Existing enrollments জানতে external collection, repository, বা database প্রয়োজন।
Answer 9
Counter process-local, non-persistent, multi-server unaware এবং concurrency-safe নয়।
Answer 10
Logs-এ unnecessary personal information exposure কমে।
Completion Checklist
Project complete ধরা যাবে যদি:
- Package structure declaration-এর সঙ্গে match করে
- সব source files compile করে
-
CourseCodeimmutable - Course code normalize হয়
- Free course price zero
- Paid course price positive
- Course publish করা যায়
- Published course-এ learner enroll করা যায়
- Unpublished course enrollment rejected
- Progress total lessons exceed করতে পারে না
- Cancelled enrollment update reject করে
- Completed enrollment cancel reject করে
- Course equality code-based
- Learner equality ID-based
- Private fields direct mutation prevent করে
- No blind setters
- Meaningful
toString()implementations আছে - Sensitive data unnecessarily log হয় না
Module Practice Summary
এই project-এ আমরা একটি small কিন্তু production-minded domain model তৈরি করেছি।
আমরা দেখেছি:
- Value object কীভাবে validation centralize করে
- Constructor valid initial state establish করে
- Static factory methods creation intent clear করে
- Encapsulation arbitrary mutation prevent করে
- Meaningful methods state transition express করে
- Composition related objects connect করে
- Duplicate data avoid করলে consistency improve হয়
- Immutable identity equality stable রাখে
- Derived state duplicate না করে calculate করা যায়
- Package structure code ownership communicate করে
- Domain object শুধু নিজের জানা rules enforce করে
- External data-dependent rules application service-এর responsibility হতে পারে
- Simple console application-এর মধ্যেও strong object design apply করা যায়
Strong OOP design-এর লক্ষ্য বেশি classes তৈরি করা নয়।
লক্ষ্য হলো:
সঠিক responsibility
সঠিক object-এর কাছে রাখা
Next Lesson
পরবর্তী lesson:
Module Review and Assessment
আমরা Module 2-এর complete revision করব:
- Core concepts summary
- Design judgment questions
- Code review exercises
- Predict-the-output problems
- Bug-fixing tasks
- Short implementation challenges
- Final module assessment