← Back to Blog

Composition over Inheritance - এই কথার মানে আসলে কি?

April 4, 202314 Minutes Read
oopcompositioninheritancedesign patternsbest practicesprogramming

"Composition over Inheritance" এই কথার মানে আসলে কি?

অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং!!! কনসেপ্ট তো জানি, পড়েছি শিখেছি, কোড ও লিখি কিন্তু এপ্রোপ্রিয়েট ভাবে এপ্লাই করতে পারিনা, অথবা কেন জানি মনে হয় OOP এর প্রপার ইউটিলাইজেশন টা করতে পারি না, কেন জানি মনে হয়।

এই আর্টিকেল গুলো তাহলে আপনার জন্যই।

OOP নিয়ে যারা এই ষ্ট্রাগলের কথা বলে তাদের আমি একটা আনালজি দেই, এনালজি টা হল এমন, ধরেন আপনি কাউকে কবিতা লিখতে দেখলেন, দেখলেন যে সে, একটা খাতা আর একটা কলম নিয়ে কবিতা লিখতেছে। এটা দেখে আপনি একটা খাতা আর একটা কলম নিয়ে বসে কবিতা লেখার চেষ্টা করলেন। কিন্তু কবিতা লেখা শুধু খাতা কলম থাকলেই হয় না, কবিতা লেখার জন্য দরকার কবিতার ছন্দ, মাত্রা, রসবোধ, অনুভূতি ইত্যাদি।

OOP এর ব্যাপার টাও এমন, কাউকে OOP ব্যাবহার করে সুন্দর গুছানো কোড লেখা দেখে আপনি যদি OOP এর বেসিক শিখে তার মত সুন্দর কোড করতে চান তাহলে সেটা নিছক শুধুই খাতা কলম নিয়ে বসে কবিতা লেখার চেষ্টা করার মত। OOP এর আসল মাহাত্ব টা হল এর প্রিন্সিপাল, ডিজাইন প্যাটার্ন, প্রাক্ট্রিস আর do's and don'ts গুলো।

আমদের এই ইনিশিয়েটিভ এ OOP এর সেই প্রিন্সিপাল, ডিজাইন প্যাটার্ন, প্রাক্ট্রিস আর do's and don'ts গুলো নিয়ে আলোচনা করব যেন OOP এর আসল মাহাত্ব টা আপনার বুঝে আসে।

আজকের আলোচনা ইনহ্যারিটেন্স(inheritance) ও কম্পজিশন(composition) নিয়ে। এবং OOP এর do's and don'ts এর অনেক পপুলার একটা কথা হল "Composition over Inheritance" এটার মানে কি ও এর মাহাত্ব বোঝার চেষ্টা করব আজ।

ইনহ্যারিটেন্স কে Object Oriented Programming অত্যন্ত গুরুত্বপূর্ন পিলার মনে করা হয়, এটাই আমাদের শেখানো হয়েছে, কিন্তু কোড করতে গেলে ইহার বাস্তবতা একেবারেই অন্য রকম।

"Composition over Inheritance" বা "Favour Composition over Inheritance" এরকম কথা জগত বিখ্যাত প্রোগ্রামিং গড(god) রা তাদের বই পত্রে বলে গেছে।

আমাদের অনেকেই এই কথার মাহাত্ব উপলব্ধি করতে ব্যর্থ হই।

আজ এটাই বোঝার চেষ্টা করব।

তার আগে আসেন ইনহ্যারিটেন্স কি সেটা আগে বুঝে নিই।

Inheritance

যেমন ধরেন আপনার এপে ইউজার ম্যানেজমেন্ট সিস্টেমে, Admin, Moderator, AppUser এই প্রত্যেকটি class এ email ও password প্রোপার্টি লাগে ও মেথড লাগে login ও logout।

// Admin.java
class Admin {
    private String email;
    private String password;
    
    public void login() { /* ... */ }
    public void logout() { /* ... */ }
}

// Moderator.java
class Moderator {
    private String email;
    private String password;
    
    public void login() { /* ... */ }
    public void logout() { /* ... */ }
}

// AppUser.java
class AppUser {
    private String email;
    private String password;
    
    public void login() { /* ... */ }
    public void logout() { /* ... */ }
    public void signup() { /* ... */ }
}

আপনি তখন করেন কি প্রত্যেকটি ক্লাসে এই জিনিস গুলো বার বার না লিখে একটা কমন সুপার ক্লাস (super class) বানায়া নেন এবং এবং এই super class মধ্য কমন জিনিস গুলো রেখে দিয়ে বাকী class গুলোকে এর সাব ক্লাস(sub-class) বানিয়ে দেন।

আমরা একটি parent class বা super class বানায়া নিই, নাম দিলাম User:

// User.java
class User {
    protected String email;
    protected String password;
    
    public void login() { /* ... */ }
    public void logout() { /* ... */ }
}

আর বাকি class গুলোকে এই parent class থেকে extend (ইনহ্যারিট) করে দিলাম:

// Admin.java
class Admin extends User {
    // Admin specific methods
}

// Moderator.java
class Moderator extends User {
    // Moderator specific methods
}

// AppUser.java
class AppUser extends User {
    public void signup() { /* ... */ }
}

AppUser class -এ একটা এক্সট্রা signup method ছিল ওটাকে এই ক্লাসে এমনই রেখে দিলাম।

এক্সট্রা অর্ডিনারি, কাজ করেছেন, প্রত্যেকটা class কত স্লিম বানায়া ফেলেছেন।

বাহ, জোস ওয়েতে আপনি সুন্দর একটা class hierarchy বানিয়েছেন এটা ভেবে আপনি নিশ্চিন্তে ঘুমাইতে গেলেন, কিন্তু বিপত্তি টা আসল পরে যখন আর নতুন নতুন রিকোয়ারমেন্ট আসা শুরু করলো Admin, Moderator, AppUser এ।

তার আগে আসেন এই ক্লাস চারটেকে আমরা UML ডায়াগ্রাম দিয়ে রেপ্রেজেন্ট করি কারণ, আমরা শুধু জাভা তে আটকে না থেকে ল্যাঙ্গুয়েজ এগনোষ্টিক থাকতে চাই, তাছাড়া নতুন রিকোয়ারমেন্ট এর বিপত্তি গুলো কোড লিখে বুঝলে অনেক সময় কনফিউজিং হয়ে যায়।

UML ডায়াগ্রাম এর সিম্পল পরিচিতি:

  • আমরা বক্স দিয়ে এক একটা class রেপ্রেজেন্ট করি, class এ প্রপার্টি ও মেথড গুলা থাকবে;
  • মেথড গুলার পাশে পেরেন্থেসিস থাকে, পেরেন্থেসিসের মধ্য প্যারামিটার গুলো থাকবে।
  • প্রোপার্টি ও মেথড এর সামনে (+) অথবা (-) থাকে,
  • "+ : মানে পাবলিক মেথড বা প্রোপার্টি"
  • "— : মানে প্রাইভেট মেথড বা প্রোপার্টি"
  • "method এর পাশে কলন (:) দিয়ে একটা রিটার্ন টাইপ ও থাকতে পারে"

এটুকুই হল UML এর রুল।

UML Diagram Rules

আসেন তবে আমাদের User, Admin, Moderator, AppUser ক্লাস গুলোর UML বানিয়ে ফেলি।

User, Admin, Moderator, AppUser UML Diagram

এখানে Admin, Moderator, AppUser ক্লাস তিনটা User ক্লাস কে ইনহ্যারিট করে, তাহলে ইনহ্যারিটেন্স এর রিলেশনশিপ টা রেপ্রেজেন্ট করতে হবে।

UML ডায়াগ্রাম এ আমরা তীর চিহ্ন বা Arrow দিয়ে রিলেশনশিপ গুলো নির্দেশ করি, বিভিন্ন ধরনের রিলেশনশিপের জন্য Arrow বিভিন্ন রিকম হয়ে থাকে এগুলো নিয়ে আমরা পরবর্তীতে কন্সার্ন হব, আপাতত দরকার নেই।

এখন শুধু ইনহ্যারিটেন্স এর Arrow টা দেখি, ইনহ্যারিটেন্স রিলেশনশিপ দেখাতে আমরা চাইল্ড ক্লাস থেকে প্যারেন্ট ক্লাসের দিকে এরকম একটা Arrow দেই (এই Arrow-র মাথা তিনকোনা ফিলআপ করা)।

Inheritance Arrow

তাহলে আসেন এখন ক্লাস ডায়াগ্রাম টিতে ইনহ্যারিটেন্স এর Arrow দিয়ে রিলেশনশিপ আঁকাই নিই।

Inheritance Class Diagram with Arrows

এভাবে আপনি অসাধারণ একটা ক্লাস hierarchy বানিয়ে ফেলেছেন ভেবে বসে আছেন।

এখন আপনার জন্য নতুন রিকোয়ারমেন্ট এলো যে Admin, Moderator, AppUser সবাইকে একাউন্ট ডিলিট করার একটা অপশন দিতে হবে, আপনি সুন্দর করে তখন বেস ক্লাস User class এ, শুধু একটি মেথড এড করে দিলেন deleteAccount() এ।

Delete Account in User Class

অসাধারণ, এলিগেন্ট সল্যুশন, কিন্তু রিকোয়ারমেন্ট এমন না হয়ে হল অন্য রকম, বলা হল শুধুমাত্র Moderator এবং AppUser -ই কেবল তাদের একাউন্ট ডিলিট করতে পারবে।

তাহলে কিন্তু আপনি আর আগের মত করে deleteAccount() মেথড টি বেস ক্লাস, User class এ এড করতে পারবেন না, তাহলে আপনাকে যা করতে হবে তা হল, Moderator ও AppUser ক্লাসে deleteAccount() মেথড এড করতে হবে।

ইশ, কোড ডুপ্লিকেশন করে ফেললেন!

এই ডুপ্লিকেশন আটকাইতে গেলে কি করবেন? আপনাকে আলাদা বেস / প্যারেন্ট ক্লাস তৈরি করতে হবে (এটার নাম দিলাম আমরা NonAdmin) যেটাতে ডিলিট মেথড থাকবে, NonAdmin- User কে extend করবে এবং NonAdmin ক্লাস কে আবার Moderator ও AppUser extend করবে।

Inheritance Mess with NonAdmin

এভাবে ডুপ্লিকেশন সরিয়ে ফেলেছেন, কিন্তু ক্লাস hierarchy টা জগাখিচুড়ি বানায়া ফেলেছে।

এবার মনে করেন আরেকটা ফিচার চলে আসলো যে শুধু Admin ও Moderator- AppUser কে ডিলিট করতে পারবে; এখন তাহলে কোড ডুপ্লিকেশন আটকানোর জন্য Admin ও Moderator ক্লাসের মধ্য deleteAppUser() মেথড এড না করে আমরা করলাম কি, আমরা আরেকটা ক্লাস বানালাম যেটা User কে extend করবে এবং deleteAppUser() মেথড থাকবে।

ওয়েট!! কিন্তু আমাদের Moderator ক্লাস তো অলরেডি NonAdmin ক্লাস কে extend করেছে! আর Java সহ বেশিভাগ OOP ল্যাঙ্গুয়েজ Multiple Inheritance সাপোর্ট করে না! তাহলে এখন উপাই?

এখন আপনি যা করতে পারেন সেটা হচ্ছে, class পরিবর্তে interface ব্যাবহার করে Multiple Inheritance এর ব্যাপার টা এচিভ করতে পারেন, হয়ে এটা অত্যন্ত ভালো একটি এপ্রোচ এটিকে বলা হয় "interface segregation principle"।

চলেন দেখি এখানে আমরা ক্লাস hierarchy এর যে মেস(mess) টা তৈরি করেছি এই মেস সলভ করব কিভাবে আমরা Inheritance ব্যাবহার না করে এবার Composition ব্যাবহার করব।

কেন Inheritance এড়ানো উচিত?

কোডে Inheritance না ব্যাবহার করার কিছু অত্যন্ত শক্তিশালী কিছু কারন আছে:

  1. Inheritance ক্লাস hierarchy তে ডিপেন্ডেন্সি তৈরি করে, যার ফলাফল হল অত্যন্ত টাইডলি কাপলড (tightly coupled) কোড। tightly coupled কোড অত্যন্ত রিজিড(rigid) প্রকৃতির হয়। rigid কোড হল, আপনি যদি কোথাও ছোট্ট একটা পরিবর্তন করেন তাহলে সেটা পুরো সিস্টেমে প্রভাব ফেলবে।

  2. Inheritance ব্যাবহার করে এবস্ট্রাক্ট কোড লেখা অত্যন্ত কঠিন।

  3. Inheritance ব্যাবহার করে লেখা কোডের ইউনিট টেস্ট(Unit Test) করাও প্রায় অসম্ভব

এই পয়েন্ট গুলোর মাহাত্ব আমারা বিস্তারিত পরে কোথাও শিখে নেব, এখন শুধু এটুকু বিশ্বাস করে নেন যে Inheritance খুব খারাপ জিনিস, এটাকে কোডে ব্যাবহার করা উচিত নয়। কেন সেটার আভাস একটু পরেই পাব।

Composition

এখন চলেন আগের Inheritance এর UML ডায়াগ্রাম টা আমরা Composition ব্যাবহার করে মডিফাই করি।

আমরা User Authentication এর যত প্রোপার্ট ও মেথড আছে সেগুলোকে আলাদা একটা ক্লাসে নিয়ে নিই নাম দেই Authentication; যেটাতে User ক্লাসের মেথড ও প্রোপার্টি গুলোই থাকবে।

এবার নাম User না দিয়ে Authentication দিলাম তার কারণ হল:

  • Inheritance হল "is a" রিলেশনশিপ; [Admin extends User মানে হল "Admin একধনের User" ]
  • অন্যদিকে, Composition হল "has a" রিলেশনশিপ;

তাই আমরা User নামটি চেঞ্জ করে Authentication দিলাম; কারণ Inheritance করে যদি আমরা বলি "Admin is a User"- ঠিকঠাক শোনায়; কিন্তু Composition করতে গিয়ে যদি বলি "Admin has a User" ব্যাপার টা ইনকারেক্ট শোনায়। কিন্তু "Admin has a Authentication" ঠিকঠাক শোনায়।

ডায়াগ্রাম ও কোড লেখার আগে আমরা সিম্পল কথায় Composition কি সেটা বুঝে নেই আসেন।

আমাদের Authentication এবং Admin ক্লাসে কম্পোজিশন রিলেশনশিপ আমরা এভাবে তৈরি করি:

// Authentication.java
class Authentication {
    private String email;
    private String password;
    
    public void login() { /* ... */ }
    public void logout() { /* ... */ }
}

// Admin.java
class Admin {
    private Authentication authentication;
    
    public Admin(Authentication authentication) {
        this.authentication = authentication;
    }
    
    public void login() {
        authentication.login();
    }
    
    public void logout() {
        authentication.logout();
    }
}

খেয়াল করেন, এখানে Admin ক্লাসে Authentication টাইপের একটা প্রোপার্টি আছে; অর্থাৎ Admin has a Authentication, এটাই কম্পোজিশন।

কম্পোজিশন এর জন্য UML ডায়াগ্রাম এ যে Arrow ব্যাবহার করি সেটার পয়েন্টেটার এর মাথা কিন্তু ফিলআপ করা না, ফিলআপ ছাড়া। এর দুইটার পার্থক্য দেখেন:

  • Inheritance Arrow: তিনকোনা ফিলআপ করা (▲)
  • Composition Arrow: ফিলআপ ছাড়া (→)

Inheritance vs Composition Arrow

তাহলে আসেন আমাদের UML ডায়াগ্রাম আমরা আঁকাইয়া ফেলি Composition ব্যাবহার করে।

Composition Class Diagram

আমরা একটু আগেই দেখে এসেছি যে Inheritance করার জন্য যেমন extends কিওয়ার্ড আছে, Composition করার জন্য এমন কোন কিওয়ার্ড নেই, কম্পোজিশন আমরা করি একটি ক্লাসে অন্য ক্লাসের Class Type প্রোপার্টি রেখে; তাহলে আসেন পুরো কোড টা লিখি:

// Authentication.java
class Authentication {
    private String email;
    private String password;
    
    public Authentication(String email, String password) {
        this.email = email;
        this.password = password;
    }
    
    public void login() { /* ... */ }
    public void logout() { /* ... */ }
}

// Admin.java
class Admin {
    private Authentication authentication;
    
    public Admin(Authentication authentication) {
        this.authentication = authentication;
    }
    
    public void login() {
        authentication.login();
    }
    
    public void logout() {
        authentication.logout();
    }
}

// Moderator.java
class Moderator {
    private Authentication authentication;
    
    public Moderator(Authentication authentication) {
        this.authentication = authentication;
    }
    
    public void login() {
        authentication.login();
    }
    
    public void logout() {
        authentication.logout();
    }
}

// AppUser.java
class AppUser {
    private Authentication authentication;
    
    public AppUser(Authentication authentication) {
        this.authentication = authentication;
    }
    
    public void login() {
        authentication.login();
    }
    
    public void logout() {
        authentication.logout();
    }
    
    public void signup() { /* ... */ }
}

এই কোডে দুইটি লক্ষণীয় জিনিস আছে আমাদের:

  1. ডিপেন্ডেসি ইনজেকশন (Dependency Injection)
  2. আর আপনার মনে প্রশ্ন জাগতে পারে "এটা কি হইল! প্রত্যেক টা ক্লাসেই তো login(), logout() মেথড লিখে ফেললাম, এ কেমন সরিষার মধ্য ভূত অবস্থা!"

ডিপেন্ডেসি ইনজেকশন

প্রথমে আসেন ডিপেন্ডেসি ইনজেকশন (Dependency Injection) এর ব্যাপারে। এটার নাম টাই শুধু এমন কুল আর ট্যাকনিক্যাল, ইহা আসলে খুব সিম্পল ব্যাপার।

ডিপেন্ডেসি ইনজেকশন হল, আমরা তো Moderator বা অন্য ক্লাস গুলতে Authentication টাইপের একটি প্রপার্টি রেখেছি, সেটা সাপ্লাই করতেছি কিভাবে, এই সাপ্লাইয়ের ওয়েটাই এখানে Dependency Injection।

আমরা যদি Moderator ক্লাসের একটি অবজেক্ট তৈরি করি সেটা এভাবে তৈরি করব:

// MyApp.java
public class MyApp {
    public static void main(String[] args) {
        Authentication auth = new Authentication("moderator@example.com", "password123");
        Moderator moderator = new Moderator(auth);
        
        moderator.login();
    }
}

এখানে দেখেন Moderator যেহেতু Authentication টাইপের একটা অবজেক্ট কন্সট্রাক্টর প্যারামিটার হিসেবে নেয়, সেজন্য আমরা Authentication এর একটা অবজেক্ট তৈরি করে নিয়ে Moderator ক্লাস তৈরি করার সময় constructor এ pass করে দিলাম। এটাই Dependency Injection।

দ্বিতীয় জিনিস

"প্রত্যেক টা ক্লাসেই তো login(), logout() মেথড লিখে ফেললাম, এ কেমন সরিষার মধ্য ভূত অবস্থা!"

হ্যাঁ প্রতিটা ক্লাসেই login(), logout() মেথড আছে- কিন্তু login, logout এর লজিক বা ইমপ্লিমেন্টেশন কিন্তু নেই, এরা শুধু authentication অবজেক্টের login, logout মেথড কল করে দিয়েছে, recall করেছি আর কি।

আসলে মেথড গুলোর ইমপ্লিমেন্টেশন আছে Authentication ক্লাসের মধ্য, তাই এটা কোন সমস্যা না, বরং সুবিধা, login, logout এর ইমপ্লিমেন্টেশন যদি আমাদের চেঞ্জ করতে হয় সেটা আমরা শুধু Authentication ক্লাসের মধ্য চেঞ্জ করে দিতে পারি।

[সেক্ষেত্রে Authentication authentication প্রোপার্টি private করা ছিল এটাকে public করতে হবে] যদি Admin, Moderator ও AppUser ক্লাসের মধ্য যদি আমরা login, logout মেথড তৈরি না করি তাহলে বাইরে এভাবে কোড লিখতে হবে:

moderator.authentication.login(); // যদি authentication public হয়

কিন্তু এটা ভালো practice না, তাই আমরা wrapper method তৈরি করি।

নতুন রিকোয়ারমেন্ট: Self Account Deletion

আসেন আমরা আসল টপিক কন্টিনিউ করি, এখন যদি Composition করা নতুন hierarchy তে আমাদের নতুন রিকোয়ারমেন্ট এড করতে বলে যে শুধু Moderator ও AppUser কে নিজেদের একাউন্ট ডিলিট করার ফাংশনলালিটি দিতে হয় সেটা কিভাবে করব?

SelfAccountDeleter Composition

আমরা নতুন একটি ক্লাস তৈরি করে নিলাম, নাম দিলাম SelfAccountDeleter. এর মধ্য একাউন্ট ডিলিশনের সব ফাংশনালিটি রাখলাম. এখন শুধু Moderator ও AppUser class দুটি Compose করার সময় আমরা এদের মধ্য SelfAccountDeleter টাইপের একটা প্রোপার্টি রাখব।

তাহলে SelfAccountDeleter সহ পুরো প্রোগাম টা লিখলে আমাদের কোড এমন হবে:

// Authentication.java
class Authentication {
    private String email;
    private String password;
    
    public Authentication(String email, String password) {
        this.email = email;
        this.password = password;
    }
    
    public void login() { /* ... */ }
    public void logout() { /* ... */ }
}

// SelfAccountDeleter.java
class SelfAccountDeleter {
    private String email;
    
    public SelfAccountDeleter(String email) {
        this.email = email;
    }
    
    public void deleteAccount() {
        // Delete account logic
        System.out.println("Deleting account: " + email);
    }
}

// Admin.java
class Admin {
    private Authentication authentication;
    
    public Admin(Authentication authentication) {
        this.authentication = authentication;
    }
    
    public void login() {
        authentication.login();
    }
    
    public void logout() {
        authentication.logout();
    }
}

// Moderator.java
class Moderator {
    private Authentication authentication;
    private SelfAccountDeleter accountDeleter;
    
    public Moderator(Authentication authentication, String email) {
        this.authentication = authentication;
        this.accountDeleter = new SelfAccountDeleter(email);
    }
    
    public void login() {
        authentication.login();
    }
    
    public void logout() {
        authentication.logout();
    }
    
    public void deleteAccount() {
        accountDeleter.deleteAccount();
    }
}

// AppUser.java
class AppUser {
    private Authentication authentication;
    private SelfAccountDeleter accountDeleter;
    
    public AppUser(Authentication authentication, String email) {
        this.authentication = authentication;
        this.accountDeleter = new SelfAccountDeleter(email);
    }
    
    public void login() {
        authentication.login();
    }
    
    public void logout() {
        authentication.logout();
    }
    
    public void signup() { /* ... */ }
    
    public void deleteAccount() {
        accountDeleter.deleteAccount();
    }
}

ব্যাস এই হইল আমাদের কোড, এখন এটাকে ইউস করতে চাইলে আমরা এভাবে ইউস করতে পারি:

// MyApp.java
public class MyApp {
    public static void main(String[] args) {
        Authentication auth = new Authentication("moderator@example.com", "password123");
        Moderator moderator = new Moderator(auth, "moderator@example.com");
        
        moderator.login();
        moderator.deleteAccount();
    }
}

জোস, আগলি Inheritance ছেড়ে Composition করে আমরা অত্যন্ত ফেক্সিবল ও ম্যানেজবল একটা কোডবেস তৈরি করে নিয়েছি।

আরেকটি রিকোয়ারমেন্ট: Delete Other Users

দেখেন এবার কত সহজে আমরা আগে যে দ্বিতীয় রিকোয়ারমেন্ট ইমপ্লিমেন্ট করতে গিয়ে জগা খিচুড়ি পাকিয়ে ফেলছিলাম সেই রিকোয়ারমেন্ট টা ইমপ্লিমেন্ট করার চেষ্টা করি।

রিকোয়ারমেন্ট: শুধু Admin ও Moderator কেবল AppUser এর একাউন্ট ডিলিট করতে পারবে।

চলেন প্রথমে UML ক্লাস ডায়গ্রাম আঁকাই নিই আমরা:

AppUserAccountDeleter Composition

আমরা শুধুমাত্র এখানে AppUserAccountDeleter ক্লাস তৈরি করে নিলাম, এবং Admin ও Moderator ক্লাস Compose করার সময় সাথে AppUserAccountDeleter দিয়ে Compose করব, অর্থাৎ Admin ও Moderator ক্লাসের মধ্য একটা AppUserAccountDeleter টাইপের প্রোপার্টি রাখব।

Final Composition Diagram

তাহলে চলেন একটু আগের লিখা কোড টা রিকোয়ারমেন্ট অনুযায়ী মডিফাই করি:

নতুন AppUserAccountDeleter ক্লাস টা বানিয়ে নিই:

// AppUserAccountDeleter.java
class AppUserAccountDeleter {
    public void deleteAppUser(String userEmail) {
        // Delete AppUser account logic
        System.out.println("Deleting AppUser account: " + userEmail);
    }
}

এবার Admin ও Moderator ক্লাস দুটো কে মডিফাই করি এবং এতে AppUserAccountDeleter এট্রিবিউট হিসেবে রাখি:

// Admin.java
class Admin {
    private Authentication authentication;
    private AppUserAccountDeleter userAccountDeleter;
    
    public Admin(Authentication authentication) {
        this.authentication = authentication;
        this.userAccountDeleter = new AppUserAccountDeleter();
    }
    
    public void login() {
        authentication.login();
    }
    
    public void logout() {
        authentication.logout();
    }
    
    public void deleteAppUser(String userEmail) {
        userAccountDeleter.deleteAppUser(userEmail);
    }
}

// Moderator.java
class Moderator {
    private Authentication authentication;
    private SelfAccountDeleter accountDeleter;
    private AppUserAccountDeleter userAccountDeleter;
    
    public Moderator(Authentication authentication, String email) {
        this.authentication = authentication;
        this.accountDeleter = new SelfAccountDeleter(email);
        this.userAccountDeleter = new AppUserAccountDeleter();
    }
    
    public void login() {
        authentication.login();
    }
    
    public void logout() {
        authentication.logout();
    }
    
    public void deleteAccount() {
        accountDeleter.deleteAccount();
    }
    
    public void deleteAppUser(String userEmail) {
        userAccountDeleter.deleteAppUser(userEmail);
    }
}

তাহলে MyApp.java ফাইল টা দেখতে এরকম হবে:

// MyApp.java
public class MyApp {
    public static void main(String[] args) {
        Authentication adminAuth = new Authentication("admin@example.com", "admin123");
        Admin admin = new Admin(adminAuth);
        
        Authentication modAuth = new Authentication("moderator@example.com", "mod123");
        Moderator moderator = new Moderator(modAuth, "moderator@example.com");
        
        admin.login();
        admin.deleteAppUser("user@example.com");
        
        moderator.login();
        moderator.deleteAccount();
        moderator.deleteAppUser("user2@example.com");
    }
}

এখানে ছোট্ট একটা লক্ষণীয় ব্যাপার আছে, দেখেন SelfAccountDeleter কিন্তু আমরা প্রাইভেট করে রেখেছি এবং এই অবজেক্ট এর মেথড কল Admin ও Moderator ক্লাসের ভেতর থেকে দিয়েছি, কারণ, SelfAccountDeleter এর deleteAccount মেথড কারেন্ট ইউজারের ইমেইল নেয় যেটার ভ্যালু আমাদের ক্লাসের মধ্য অলরেডি আছে, তাই এটাকে পাবলিক করে এক্সপোজ করলে অযথা বাইরে থেকে কল করার সময় কারেন্ট ইউজারের ইমেল পাস করতে হবে যা unnecessary।

সামারি

Summary Diagram

দিনশেষে আজকের সামারি হলো, Inheritance খুব খারাপ জিনিস, কেন এটা খারাপ সেটা দেখেছি আমরা আজ। তাই Inheritance এর পরিবর্তে Composition কিভাবে ব্যাবহার করব সেটা শিখেছি, এবং Composition আমাদের ক্লাস hierarchy কে অনেক বেশি ফেক্সিবল ও ম্যানেজবল করে তোলে।

মূল Takeaways:

  1. Inheritance হল "is a" রিলেশনশিপ, যা tightly coupled কোড তৈরি করে
  2. Composition হল "has a" রিলেশনশিপ, যা loosely coupled ও flexible কোড তৈরি করে
  3. Dependency Injection ব্যবহার করে আমরা dependencies সহজে inject করতে পারি
  4. Composition ব্যবহার করে আমরা নতুন features সহজে add করতে পারি without breaking existing code
  5. Composition আমাদের কোডকে testable করে তোলে

"Composition over Inheritance" - এই principle follow করে আমরা অনেক বেশি maintainable, scalable ও testable কোড লিখতে পারি।