SOLID — অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিংয়ের পাঁচটি নীতি, যা Robert C. Martin (Uncle Bob) 2000-এর দশকের শুরুর দিকে প্রণয়ন করেছিলেন। DigitalOcean, 2024 অনুসারে, SOLID বলতে বোঝায় Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation এবং Dependency Inversion। এই নীতিগুলি Clean Architecture-এর ভিত্তি তৈরি করে এবং Android ডেভেলপমেন্ট (MVP, MVVM, Clean Architecture) এবং iOS (VIPER, TCA)-তে প্রয়োগ করা হয়।
মূল বিষয়
SOLID — একটি স্মৃতিসংক্ষেপ যা অবজেক্ট-ওরিয়েন্টেড ডিজাইনের পাঁচটি নীতি নির্দেশ করে। শব্দটি Robert C. Martin «Design Principles and Design Patterns» (2000) নিবন্ধে প্রবর্তন করেছিলেন এবং পরে «Agile Software Development: Principles, Patterns, and Practices» (2002) বইয়ে জনপ্রিয় করেছিলেন। SOLID কোনো ফ্রেমওয়ার্ক বা লাইব্রেরি নয় — এটি অনুশীলনের একটি সেট যা কোডকে কম সংযুক্ত, বেশি পরীক্ষাযোগ্য এবং পরিবর্তন করা সহজ করে তোলে।
Clean Coder Blog, 2014 অনুসারে, প্রতিটি SOLID নীতি একটি নির্দিষ্ট ডিজাইন সমস্যার সমাধান করে: SRP God শ্রেণীর বিরুদ্ধে লড়াই করে, OCP ক্যাসকেডিং পরিবর্তন প্রতিরোধ করে, LSP ভুল উত্তরাধিকার থেকে রক্ষা করে, ISP বড় ইন্টারফেস এড়ায় এবং DIP দৃঢ় সংযুক্তি হ্রাস করে। একসাথে তারা Clean Architecture-এর ভিত্তি তৈরি করে, যা Android প্রকল্পে MVP, MVVM এবং MVI-এর সাথে ব্যবহৃত হয়।
Single Responsibility Principle (SRP) — একক দায়িত্বের নীতি। সূত্র: «একটি শ্রেণীর পরিবর্তনের শুধুমাত্র একটি কারণ থাকা উচিত»। এর অর্থ হল প্রতিটি মডিউল বা শ্রেণী ঠিক একটি কার্যকারিতা বা একটি ডোমেন সত্তার জন্য দায়ী। যদি একটি শ্রেণী ব্যবহারকারী এবং ইমেল পাঠানো উভয়ই পরিচালনা করে — তবে তার পরিবর্তনের দুটি কারণ রয়েছে, যা SRP লঙ্ঘন করে।
Robert C. Martin, 2002 অনুসারে, SRP সবচেয়ে গুরুত্বপূর্ণ এবং একই সাথে সবচেয়ে বেশি লঙ্ঘিত নীতি। মোবাইল ডেভেলপমেন্টে, SRP প্রায়শই Activity/Fragment-এ UI লজিক, নেভিগেশন, নেটওয়ার্কিং এবং ব্যবসায়িক লজিক একত্রিত করে লঙ্ঘিত হয়। সমাধান হল প্রতিটি স্তরকে আলাদা শ্রেণীতে বিভক্ত করা: UI লজিকের জন্য ViewModel, ডেটার জন্য Repository, নেভিগেশনের জন্য NavController।
UserManager শ্রেণীটি বিবেচনা করুন, যা প্রোফাইল লোড করে, সেটিংস সংরক্ষণ করে এবং ইমেল পাঠায়। এগুলি তিনটি ভিন্ন দায়িত্ব, যার প্রতিটি একটি পৃথক শ্রেণীতে বিভক্ত করা উচিত: UserProfileRepository (লোডিং), UserSettingsStorage (সংরক্ষণ) এবং EmailService (পাঠানো)। ক্লায়েন্ট কোড (ViewModel) Dependency Injection-এর মাধ্যমে তিনটি ব্যবহার করে এবং প্রতিটি শ্রেণী পৃথকভাবে সহজেই পরীক্ষাযোগ্য এবং অন্যদের প্রভাবিত না করে পরিবর্তনযোগ্য।
// ❌ SRP লঙ্ঘন: Activity নেটওয়ার্ক, DB এবং UI সম্পর্কে জানে
class ProfileActivity : AppCompatActivity() {
fun loadProfile() {
api.getUser() // নেটওয়ার্ক কল
db.saveUser() // DB অপারেশন
updateUI() // UI আপডেট
}
}
// ✅ SRP অনুসৃত: স্তরগুলি পৃথক করা হয়েছে
class ProfileViewModel : ViewModel() {
private val repo = UserRepository()
fun loadProfile() { repo.getUser() }
}
SRP লঙ্ঘনের লক্ষণ: একটি শ্রেণী ২০০ লাইনের বেশি, বিভিন্ন ডোমেনের পদ্ধতি, বিভিন্ন কারণে ঘন ঘন পরিবর্তন। Android ডেভেলপমেন্টের জন্য নিয়মটি সহজ: Activity শুধুমাত্র স্ক্রিন লাইফসাইকেল পরিচালনা করে, ViewModel UI অবস্থা পরিচালনা করে, Repository ডেটা উৎস পরিচালনা করে।
SRP নীতিটি কেবল শ্রেণীর ক্ষেত্রেই নয়, বরং সার্ভিস-স্তরের আর্কিটেকচারের ক্ষেত্রেও প্রযোজ্য। প্রতিটি মাইক্রোসার্ভিস একটি ডোমেন সত্তা পরিচালনা করে: UserService — শুধুমাত্র ব্যবহারকারী, PaymentService — শুধুমাত্র পেমেন্ট, NotificationService — শুধুমাত্র বিজ্ঞপ্তি। এটি সার্ভিসগুলির স্বাধীন স্কেলিং, ডিপ্লয়মেন্ট এবং পরীক্ষা সক্ষম করে। মোবাইল অ্যাপ্লিকেশনে, মাইক্রোসার্ভিস স্তরে SRP ডোমেন অনুযায়ী API ক্লায়েন্টকে পৃথক করার মাধ্যমে প্রকাশিত হয়।
Open-Closed Principle (OCP) — শ্রেণীগুলি সম্প্রসারণের জন্য উন্মুক্ত হওয়া উচিত (নতুন আচরণ যোগ করা যেতে পারে) এবং পরিবর্তনের জন্য বন্ধ (বিদ্যমান কোড পরিবর্তন করা হয় না)। এটি বহুরূপতা, অ্যাবস্ট্রাক্ট শ্রেণী এবং ইন্টারফেসের মাধ্যমে অর্জিত হয়। বিদ্যমান পদ্ধতিতে if-else যোগ করার পরিবর্তে, একটি নতুন ইন্টারফেস বাস্তবায়ন তৈরি করা হয়।
Clean Coder Blog, 2014 অনুসারে, OCP Strategy প্যাটার্নের সাথে সবচেয়ে ভাল কাজ করে। উদাহরণস্বরূপ, যদি একটি অ্যাপ বিভিন্ন পেমেন্ট পদ্ধতি (Google Pay, Apple Pay, PayPal) সমর্থন করে, তবে পেমেন্ট প্রসেসরে switch-case যোগ করার প্রয়োজন নেই। প্রতিটি পেমেন্ট পদ্ধতি একটি সাধারণ PaymentGateway ইন্টারফেস বাস্তবায়ন করে এবং বিদ্যমান কোড পরিবর্তন না করেই একটি নতুন পেমেন্ট সিস্টেম একটি নতুন শ্রেণী হিসেবে যোগ করা হয়।
// ✅ OCP: সম্প্রসারণের জন্য উন্মুক্ত, পরিবর্তনের জন্য বন্ধ
interface PaymentGateway {
fun processPayment(amount: Double): Boolean
}
class GooglePayGateway : PaymentGateway {
override fun processPayment(amount: Double) = true
}
// নতুন পেমেন্ট সিস্টেম — বিদ্যমান কোড পরিবর্তন না করে
class ApplePayGateway : PaymentGateway {
override fun processPayment(amount: Double) = true
}
Liskov Substitution Principle (LSP) — Barbara Liskov-এর প্রতিস্থাপন নীতি। যদি S, T-এর একটি উপপ্রকার হয়, তাহলে T টাইপের অবজেক্টগুলিকে S টাইপের অবজেক্ট দিয়ে প্রোগ্রামের বৈশিষ্ট্য পরিবর্তন না করে প্রতিস্থাপন করা যেতে পারে। আনুষ্ঠানিকভাবে: একটি ফাংশন যা ভিত্তি শ্রেণী ব্যবহার করে, তার যেকোনো উপশ্রেণীর সাথে সঠিকভাবে কাজ করা উচিত। যদি কোনো উপশ্রেণী সেখানে ব্যতিক্রম নিক্ষেপ করে যেখানে ভিত্তি শ্রেণী নিক্ষেপ করে না — LSP লঙ্ঘিত হয়েছে।
Robert C. Martin, 2002 অনুসারে, LSP SOLID নীতিগুলির মধ্যে সবচেয়ে কঠিন। লঙ্ঘনের সর্বোত্তম উদাহরণ হল Square শ্রেণী যা Rectangle থেকে উত্তরাধিকার সূত্রে প্রাপ্ত। যদি Square-এ setWidth প্রস্থ এবং উচ্চতা উভয়ই সেট করে, তাহলে Rectangle আচরণ প্রত্যাশী ক্লায়েন্ট কোড অপ্রত্যাশিত ফলাফল পায়। মোবাইল ডেভেলপমেন্টে, ViewModel-এর উত্তরাধিকারের সময় LSP প্রায়শই লঙ্ঘিত হয় — যখন একটি চাইল্ড ViewModel বাধ্যতামূলক নির্ভরতা যোগ করে।
// ❌ LSP লঙ্ঘন: Square Rectangle-এর আচরণ ভেঙে দেয়
open class Rectangle(open var width: Int, open var height: Int)
class Square(side: Int) : Rectangle(side, side) {
override var width
get() = super.width
set(value) { super.setBoth(value, value) }
}
Interface Segregation Principle (ISP) — ক্লায়েন্টরা যেসব ইন্টারফেস ব্যবহার করে না তার উপর নির্ভর করা উচিত নয়। একটি «মোটা» ইন্টারফেসের পরিবর্তে, বেশ কয়েকটি সংকীর্ণ বিশেষায়িত ইন্টারফেস তৈরি করুন। যদি একটি শ্রেণী একটি ইন্টারফেস বাস্তবায়ন করে কিন্তু কিছু পদ্ধতি UnsupportedOperationException নিক্ষেপ করে বা খালি থাকে — এটি ISP লঙ্ঘনের স্পষ্ট লক্ষণ।
DigitalOcean, 2024 অনুসারে, ViewModel এবং Repository ডিজাইন করার সময় মোবাইল ডেভেলপমেন্টে ISP বিশেষভাবে প্রাসঙ্গিক। সমস্ত CRUD পদ্ধতি সহ একটি একক UserRepository ইন্টারফেসের পরিবর্তে, QueryUserRepository (শুধুমাত্র পড়া) এবং CommandUserRepository (লেখা) তৈরি করা ভাল। তাহলে শুধুমাত্র পড়া ক্লায়েন্ট (UI উপাদান) শুধুমাত্র Query ইন্টারফেসের উপর নির্ভর করে এবং লেখার পদ্ধতি সম্পর্কে কিছুই জানে না।
// ❌ মোটা ইন্টারফেস — ক্লায়েন্ট অপ্রয়োজনীয় পদ্ধতি বাস্তবায়নে বাধ্য
interface UserOperations {
fun getUser(id: String): User
fun saveUser(user: User)
fun deleteUser(id: String)
fun exportUsers(): File
}
// ✅ ISP: পৃথক ইন্টারফেস
interface UserReader { fun getUser(id: String): User }
interface UserWriter { fun saveUser(user: User) }
interface UserDeleter { fun deleteUser(id: String) }
Dependency Inversion Principle (DIP) — উচ্চ-স্তরের মডিউল নিম্ন-স্তরের মডিউলের উপর নির্ভর করা উচিত নয়। উভয়েরই অ্যাবস্ট্রাকশনের (ইন্টারফেস) উপর নির্ভর করা উচিত। অ্যাবস্ট্রাকশনগুলি বিস্তারিত বিবরণের উপর নির্ভর করা উচিত নয় — বিস্তারিত বিবরণ অ্যাবস্ট্রাকশনের উপর নির্ভর করা উচিত। এটি «Dependency Injection» (DI) নয়, যদিও DI হল DIP বাস্তবায়নের একটি সাধারণ উপায়।
Robert C. Martin, 2019 অনুসারে, DIP Clean Architecture-এর ভিত্তি। ViewModel (উচ্চ-স্তরের) সরাসরি RetrofitApi (বিস্তারিত) ইনস্ট্যান্স তৈরি করা উচিত নয়। পরিবর্তে, ViewModel UserRepository ইন্টারফেসের উপর নির্ভর করে এবং কনক্রিট UserRepositoryImpl Retrofit-সহ কনস্ট্রাক্টরের মাধ্যমে পাস করা হয়। Android-এ, DIP Hilt/Dagger বা Koin-এর মাধ্যমে বাস্তবায়িত হয়: সমস্ত নির্ভরতা DI কন্টেইনের মাধ্যমে সরবরাহ করা হয়।
// ✅ DIP: Module অ্যাবস্ট্রাকশনের উপর নির্ভর করে, বিস্তারিত বিবরণের উপর নয়
class UserRepositoryImpl(
private val api: UserApi, // ইন্টারফেসের উপর নির্ভরশীল
private val db: UserDao // ইন্টারফেসের উপর নির্ভরশীল
) : UserRepository {
override suspend fun getUser(id: String): User {
return api.fetchUser(id)
}
}
// Hilt DI: বিস্তারিত DI মডিউলের মাধ্যমে সংযুক্ত
@Module
object NetworkModule {
@Provides
fun provideUserApi(retrofit: Retrofit): UserApi =
retrofit.create(UserApi::class.java)
}
মোবাইল ডেভেলপমেন্টে SOLID সমস্ত স্তরে প্রয়োগ করা হয়: অ্যাপ্লিকেশন আর্কিটেকচার থেকে পৃথক শ্রেণী পর্যন্ত। Android প্রকল্পে, Clean Architecture কোডকে তিনটি স্তরে বিভক্ত করে: domain (ব্যবসায়িক লজিক — ফ্রেমওয়ার্ক থেকে স্বাধীন), data (রিপোজিটরি, API, DB) এবং presentation (UI, ViewModel)। Domain স্তর SOLID নীতি ব্যবহার করে: ইউজ কেস (SRP), রিপোজিটরি ইন্টারফেস (DIP), এন্টিটি শ্রেণী (OCP + LSP)।
Android Developers Guide, 2025 অনুসারে, Android-এ SRP ViewModel, Repository এবং Mapper-কে পৃথক করার মাধ্যমে প্রকাশিত হয়। OCP — DataSource ইন্টারফেসের মাধ্যমে নতুন ডেটা উৎস যোগ করার সময়। LSP — বিভিন্ন রিপোজিটরি জুড়ে সমজাতীয় Result হ্যান্ডলিং-এ। ISP — CQRS পদ্ধতিতে (Read/Write রিপোজিটরি পৃথক করা)। DIP — নির্ভরতা ইনজেকশনের জন্য Hilt/Koin-এর মাধ্যমে।
| নীতি | ছাড়া সমস্যা | মোবাইল প্রকল্পে সমাধান |
|---|---|---|
| SRP | ১০০০+ লাইনের Activity | ViewModel + UseCase + Repository |
| OCP | পেমেন্ট টাইপ অনুযায়ী switch-case | Strategy: PaymentGateway ইন্টারফেস |
| LSP | BaseViewModel প্রতিস্থাপনে বাগ | উপশ্রেণী চুক্তি পরীক্ষা |
| ISP | UnsupportedOperationException | Reader / Writer পৃথকীকরণ |
| DIP | ViewModel ম্যানুয়ালি Retrofit তৈরি করে | Hilt / Koin DI কন্টেইনার |
SOLID ভুলগুলি প্রায়শই কোডের অত্যধিক জটিলতার সাথে সম্পর্কিত। প্রথম — প্রসঙ্গ বিবেচনা না করে আক্ষরিকভাবে নীতিগুলি অনুসরণ করা। শুধুমাত্র «পরিষ্কার» ISP-র জন্য একটি UserService শ্রেণীকে ১০টি ইন্টারফেস এবং ১৫টি শ্রেণীতে বিভক্ত করা ওভারইঞ্জিনিয়ারিং। SOLID একটি হাতিয়ার, লক্ষ্য নয়। দ্বিতীয় ভুল — SRP-কে «এক পদ্ধতি = এক দায়িত্ব» হিসেবে ভুল বোঝা। একটি শ্রেণীর একাধিক পদ্ধতি থাকতে পারে যদি সেগুলি সব একই দায়িত্বের এলাকার অন্তর্গত হয়।
Simple Thread, 2024 অনুসারে, তৃতীয় ভুল — Android-এ ViewModel-এর উত্তরাধিকারে LSP উপেক্ষা করা। যদি ভিত্তি ViewModel LiveData আশা করে কিন্তু চাইল্ড StateFlow ব্যবহার করে — LiveData-তে সাবস্ক্রাইব করা ক্লায়েন্ট কোড আপডেট পাবে না। চতুর্থ — পরীক্ষার জন্য DIP লঙ্ঘন: RepositoryImpl সরাসরি OkHttpClient ইনস্ট্যান্স তৈরি করে, যা ইউনিট পরীক্ষা অসম্ভব করে তোলে।
সুবর্ণ নিয়ম: SOLID প্রয়োগ করুন যখন এটি একটি বাস্তব সমস্যার সমাধান করে (ঘন ঘন পরিবর্তন, পরীক্ষার অসুবিধা, পুনরাবৃত্তি)। সাধারণ CRUD স্ক্রিনের জন্য, পাঁচটি নীতির কঠোরভাবে অনুসরণ করা অতিরিক্ত। ব্যবসায়িক লজিক, আর্থিক গণনা এবং API ইন্টারঅ্যাকশনের জন্য, SOLID অপরিহার্য।
Clean Architecture (Robert C. Martin, 2012) — অ্যাপ্লিকেশন স্তরে SOLID-এর সরাসরি প্রয়োগ। SRP ইউজ কেস সীমানা নির্ধারণ করে (প্রত্যেক ইউজ কেস — একটি শ্রেণী)। OCP রিপোজিটরি ইন্টারফেসের মাধ্যমে বাস্তবায়িত হয় (Data Layer Domain পরিবর্তন না করেই পরিবর্তন হতে পারে)। ISP ইউজ কেসকে ইনপুট/আউটপুট সীমানায় বিভাজন প্রদান করে। DIP — নির্ভরতার দিক Domain স্তরের ভিতরের দিকে। LSP গ্যারান্টি দেয় যে কোনো রিপোজিটরি বাস্তবায়ন ইউজ কেস ভাঙা ছাড়াই প্রতিস্থাপনযোগ্য।
সচরাচর জিজ্ঞাসা
SOLID — কোড লেখার পাঁচটি নিয়ম যাতে এটি পরিবর্তন, পরীক্ষা এবং বোঝা সহজ হয়। প্রতিটি অক্ষর একটি নীতি: বড় শ্রেণী লিখবেন না (SRP), বিদ্যমান কোড পরিবর্তন করবেন না — নতুন যোগ করুন (OCP), উপশ্রেণীর আচরণ ভাঙবেন না (LSP) এবং অন্যান্য।
SRP (Single Responsibility) সবচেয়ে গুরুত্বপূর্ণ বলে বিবেচিত কারণ এর লঙ্ঘন God শ্রেণীর দিকে নিয়ে যায় — বিশাল শ্রেণী যা পরীক্ষা এবং পরিবর্তন করা কঠিন। তবে, DIP (Dependency Inversion) ছাড়া কোড দৃঢ়ভাবে সংযুক্ত থাকে, যা也很 গুরুত্বপূর্ণ।
বাধ্যতামূলক নয়, তবে দীর্ঘ জীবনচক্রের বাণিজ্যিক প্রকল্পের জন্য অত্যন্ত সুপারিশ করা হয়। সাধারণ অ্যাপের জন্য (এক স্ক্রিন, কোনো ব্যবসায়িক লজিক নেই), SOLID অত্যধিক হতে পারে। ৫০+ স্ক্রিন এবং ৩+ ডেভেলপারবিশিষ্ট প্রকল্পের জন্য, SOLID ন্যূনতম প্রয়োজন।
পরিণতি: শ্রেণীগুলি «মোটা» হয়ে যায় (১০০০+ লাইন), এক জায়গায় পরিবর্তন অন্যগুলি ভেঙে দেয়, ইউনিট পরীক্ষা লেখা অসম্ভব হয়ে পড়ে, নতুন বৈশিষ্ট্য যোগ করতে দিনের পরিবর্তে সপ্তাহ লাগে। সময়ের সাথে সাথে, কোড «Big Ball of Mud» — জট ও ভঙ্গুর — এ পরিণত হয়।
অনুসরণের লক্ষণ: প্রতিটি শ্রেণী ২০০ লাইনের কম, একটি বৈশিষ্ট্য পরিবর্তন ৫+ ফাইলকে প্রভাবিত করে না, ১০টি নির্ভরতা মক না করেই পরীক্ষা লেখা যায়, একজন নতুন ডেভেলপার এক দিনে কাঠামো বুঝতে পারে। SonarQube এবং detekt-এর মতো সরঞ্জাম SRP এবং DIP লঙ্ঘন সনাক্ত করতে সহায়তা করে।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন