সলিড ডিজাইনের নীতিগুলি হল সবচেয়ে গুরুত্বপূর্ণ ডিজাইনের নীতিগুলি যা আপনাকে পরিষ্কার কোড লিখতে জানতে হবে৷ সলিড নীতিগুলির উপর একটি শক্ত কমান্ড থাকা যে কোনও প্রোগ্রামারের জন্য একটি অপরিহার্য দক্ষতা। তারা ভিত্তি যার উপর অন্যান্য নকশা নিদর্শন বিকশিত হয়. এই নিবন্ধে, আমরা বাস্তব জীবনের কিছু উদাহরণ ব্যবহার করে সলিড ডিজাইনের নীতিগুলি মোকাবেলা করব এবং তাদের গুরুত্ব বুঝতে পারব। পলিমরফিজম, বিমূর্ততা এবং উত্তরাধিকারের সাথে একত্রে, উদ্দেশ্য-ভিত্তিক প্রোগ্রামিংয়ে ভাল হওয়ার জন্য সলিড নীতিগুলি সত্যিই গুরুত্বপূর্ণ। কেন সলিড নীতিগুলি গুরুত্বপূর্ণ? সলিড নীতিগুলি একাধিক কারণে গুরুত্বপূর্ণ: সলিড নীতিগুলি আমাদেরকে পরিষ্কার এবং রক্ষণাবেক্ষণযোগ্য কোড লেখার অনুমতি দেয়: যখন আমরা একটি নতুন প্রকল্প শুরু করি, তখন প্রাথমিকভাবে কোডের মান ভাল হয় কারণ আমাদের কাছে সীমিত বৈশিষ্ট্যের সেট রয়েছে যা আমরা বাস্তবায়ন করি। যাইহোক, আমরা আরও বৈশিষ্ট্য যুক্ত করার সাথে সাথে কোডটি বিশৃঙ্খল হতে শুরু করে। সলিড নীতিগুলি বিমূর্ততা, পলিমরফিজম এবং উত্তরাধিকারের ভিত্তির উপর ভিত্তি করে তৈরি করে এবং সাধারণ ব্যবহারের ক্ষেত্রে ডিজাইনের প্যাটার্ন তৈরি করে। এই ডিজাইন প্যাটার্নগুলি বোঝা প্রোগ্রামিংয়ে সাধারণ ব্যবহারের ক্ষেত্রে প্রয়োগ করতে সহায়তা করে। সলিড নীতিগুলি আমাদের পরিষ্কার কোড লিখতে সাহায্য করে যা কোডের পরীক্ষাযোগ্যতা উন্নত করে। এটি কারণ কোডটি মডুলার এবং ঢিলেঢালাভাবে সংযুক্ত। প্রতিটি মডিউল স্বাধীনভাবে উন্নত এবং স্বাধীনভাবে পরীক্ষা করা যেতে পারে। আসুন এখন বাস্তব-বিশ্বের উদাহরণ সহ প্রতিটি সলিড নীতিগুলি বিশদভাবে অন্বেষণ করি। 1. একক দায়িত্ব নীতি SOLID নীতিতে S এর অর্থ একক দায়িত্ব নীতি। একক দায়বদ্ধতার নীতি বলে যে একটি শ্রেণীর পরিবর্তনের শুধুমাত্র একটি কারণ থাকা উচিত। এটি আমাদের প্রকল্পে অতিরিক্ত প্রয়োজনীয়তাগুলি অন্তর্ভুক্ত করার সময় আমাদের পরিবর্তন করতে হবে এমন স্থানের সংখ্যা সীমিত করে৷ প্রতিটি ক্লাস পরিবর্তন করার জন্য ঠিক একটি কারণ থাকা উচিত। উদাহরণ স্বরূপ, ধরা যাক আমরা জাভাতে একটি ব্যাঙ্কিং অ্যাপ্লিকেশন ডিজাইন করছি যেখানে আমাদের একটি ক্লাস রয়েছে যা ডেবিট, ক্রেডিট এবং এর মত মৌলিক ক্রিয়াকলাপগুলিকে অনুমতি দেয়৷ পদ্ধতিটি নামে একটি enum নেয় (যেমন ইমেল, SMS, ইত্যাদি) এবং উপযুক্ত মাধ্যমে আপডেট পাঠায়। আমরা নীচে দেখানো হিসাবে এটির জন্য কোড লিখব। SavingsAccount sendUpdates sendUpdate NotificationMedium public class SavingsAccount { public int balance; public String name; public SavingsAccount(int initialBalance, String name) { this.balance = initialBalance; this.name = name; System.out.println("Created a savings account with balance = " + initialBalance); } public void debit(int amountToDebit) { // debit business logic } public void credit(int amountToCredit) { // credit business logic } public void sendNotification(NotificationMedium medium) { if (medium == NotificationMedium.SMS) { // Send SMS here } else if (medium == NotificationMedium.EMAIL) { // Send Email here } } } public enum NotificationMedium { SMS, EMAIL } এখন, আপনি যদি উপরের ক্লাসটি দেখেন, এটি একাধিক কারণে পরিবর্তন হতে পারে: SavingsAccount ক্লাসের মূল যুক্তিতে (যেমন , ইত্যাদি) কোনো পরিবর্তন হলে। SavingsAccount debit credit যদি ব্যাঙ্ক একটি নতুন বিজ্ঞপ্তি মাধ্যম চালু করার সিদ্ধান্ত নেয় (আসুন WhatsApp বলি)। এটি সলিড নীতিতে একক দায়িত্ব নীতির লঙ্ঘন। এটি ঠিক করতে, আমরা একটি পৃথক ক্লাস করব যা বিজ্ঞপ্তি পাঠাবে। চলুন উপরের কোডটিকে সলিড নীতি অনুসারে রিফ্যাক্টর করি public class SavingsAccount { public int balance; public String name; public SavingsAccount(int initialBalance, String name) { this.balance = initialBalance; this.name = name; System.out.println("Created a savings account with balance = " + initialBalance); } public void debit(int amountToDebit) { // debit business logic } public void credit(int amountToCredit) { // credit business logic } public void printBalance() { System.out.println("Name: " + name+ " Account Balance: " + balance); } public void sendNotification(Medium medium) { Sender.sendNotification(medium, this); } } public enum NotificationMedium { SMS, EMAIL } public class Sender { public static void sendNotification(NotificationMedium medium, SavingsAccount account) { // extract account data from the account object if (medium == NotificationMedium.SMS) { //logic to send SMS here } else if (medium == NotificationMedium.EMAIL) { // logic to send Email here } } } এখন, যেহেতু আমরা কোডটি রিফ্যাক্টর করেছি, যদি বা ফরম্যাটে কোনো পরিবর্তন হয়, আমরা ক্লাস পরিবর্তন করব। যাইহোক, এর মূল যুক্তিতে যদি পরিবর্তন হয়, তাহলে ক্লাসে পরিবর্তন হবে। NotificationMedium Sender SavingsAccount SavingsAccount এটি লঙ্ঘনটি ঠিক করে যা আমরা প্রথম উদাহরণে লক্ষ্য করেছি। 2. খোলা/বন্ধ নীতি ওপেন ক্লোজ নীতি বলে যে আমাদের ক্লাসগুলি এমনভাবে ডিজাইন করা উচিত যাতে সেগুলি এক্সটেনশনের জন্য উন্মুক্ত থাকে (অতিরিক্ত বৈশিষ্ট্য যুক্ত করার ক্ষেত্রে) কিন্তু পরিবর্তনের জন্য বন্ধ থাকে। পরিবর্তনের জন্য বন্ধ থাকা আমাদের দুটি উপায়ে সাহায্য করে: অনেক সময়, মূল শ্রেণী উৎস এমনকি উপলব্ধ নাও হতে পারে. এটি আপনার প্রকল্প দ্বারা গ্রাস একটি নির্ভরতা হতে পারে। মূল ক্লাস অপরিবর্তিত রাখলে বাগ হওয়ার সম্ভাবনা কমে যায়। যেহেতু আমরা পরিবর্তন করতে চাই এমন ক্লাসের উপর নির্ভরশীল অন্যান্য ক্লাস থাকতে পারে। ওপেন ক্লোজ নীতির একটি উদাহরণ দেখতে, আসুন একটি শপিং কার্ট (যেমন ই-কমার্স ওয়েবসাইটগুলিতে প্রয়োগ করা হয়) এর উদাহরণটি দেখুন। আমি নামে একটি ক্লাস তৈরি করতে যাচ্ছি যাতে (গুলি) এর একটি তালিকা থাকবে যা আপনি এতে যোগ করতে পারেন। আইটেমের প্রকারের উপর নির্ভর করে এবং এর উপর ট্যাক্সেশন আমরা একটি পদ্ধতি তৈরি করতে চাই যা শ্রেণীর ভিতরে মোট কার্টের মান গণনা করে। Cart Item Cart ক্লাস এক্সটেনশনের জন্য খোলা এবং পরিবর্তনের জন্য বন্ধ করা উচিত import java.util.ArrayList; import java.util.List; public class Cart { private List<Item> items; public Cart() { this.items = new ArrayList<>(); } public void addToCart(Item item) { items.add(item); } public double calculateCartValue() { double value = 0.0; for(Item item: items) { if (item.getItemType() == GIFT) { // 8% tax on gift + 2% gift wrap cost value += (item.getValue()*1.08) + item.getValue()*0.02 } else if (item.getItemType() == ItemType.ELECTRONIC_ITEM) { value += (item.getValue()*1.11); } else { value += item.getValue()*1.10; } } return value; } } @Getter @Setter public abstract class Item { protected double price; private ItemType itemType; public double getValue() { return price; } } public enum ItemType { ELECTRONIC, GIFT } উপরের উদাহরণে, পদ্ধতি কার্টের ভিতরে থাকা সমস্ত আইটেমের উপর পুনরাবৃত্তি করে এবং আইটেমের প্রকারের উপর ভিত্তি করে যুক্তি প্রয়োগ করে কার্টের মান গণনা করে। calculateCartValue যদিও এই কোডটি সঠিক দেখায়, এটি সলিড নীতিগুলি লঙ্ঘন করে৷ ধরা যাক কার্ট মান গণনা করার সময় আমাদের একটি ভিন্ন ধরনের আইটেমের জন্য একটি নতুন নিয়ম যোগ করতে হবে (মুদিখানা বলুন)। সেক্ষেত্রে, আমাদের মূল ক্লাস সংশোধন করতে হবে এবং এর ভিতরে শর্ত অন্য একটি লিখতে হবে যা টাইপের আইটেমগুলির জন্য পরীক্ষা করে। Cart else if Grocery যাইহোক, সামান্য রিফ্যাক্টরিংয়ের মাধ্যমে, আমরা কোডটিকে খোলা/বন্ধ নীতি মেনে চলতে পারি। দেখা যাক কিভাবে। প্রথমে, আমরা ক্লাসটিকে বিমূর্ত করব এবং নীচের মত বিভিন্ন ধরণের (গুলি) এর জন্য কংক্রিট ক্লাস করব। Item Item public abstract class Item { protected double price; public double getValue() { return price; } } public class ElectronicItem extends Item { public ElectronicItem(double price) { super.price = price; } @Override public double getValue() { return super.getValue()*1.11; } } public class GiftItem extends Item { public GiftItem(double price) { super.price = price; } @Override public double getValue() { return super.getValue()*1.08 + super.getValue()*0.02; } } public class GroceryItem extends Item { public GroceryItem(double price) { super.price = price; } @Override public double getValue() { return super.getValue()*1.03; } } , , এবং এর মতো প্রতিটি কংক্রিট আইটেম ক্লাসের ভিতরে পদ্ধতি প্রয়োগ করে যা ট্যাক্সেশন এবং মান গণনার জন্য ব্যবসায়িক যুক্তি ধারণ করে। GroceryItem GiftItem ElectronicItem getValue() এখন, আমরা ক্লাসকে অ্যাবস্ট্রাক্ট ক্লাস উপর নির্ভর করব এবং নীচে দেখানো প্রতিটি আইটেমের জন্য পদ্ধতি চালু করব। Cart Item getValue() import java.util.ArrayList; import java.util.List; public class Cart { private List<Item> items; public Cart(Payment paymentOption) { this.items = new ArrayList<>(); } public void addToCart(Item item) { items.add(item); } public double calculateCartValue() { double value = 0.0; for(Item item: items) { value += item.getValue(); } return value; } } এখন, এই রিফ্যাক্টর কোডে, এমনকি যদি নতুন ধরনের (গুলি) চালু করা হয়, শ্রেণী অপরিবর্তিত থাকে। পলিমরফিজমের কারণে, এর ভিতরে আসল ধরন যাই হোক না কেন, সেই ক্লাসের পদ্ধতিটি চালু করা হবে। Item Cart items ArrayList Item getValue() 3. লিসকভের প্রতিস্থাপন নীতি লিসকভের প্রতিস্থাপন নীতি বলে যে একটি প্রদত্ত কোডে, এমনকি যদি আমরা একটি সুপারক্লাসের অবজেক্টকে চাইল্ড ক্লাসের একটি বস্তু দিয়ে প্রতিস্থাপন করি, কোডটি ভাঙা উচিত নয়। অন্য কথায়, যখন একটি সাবক্লাস একটি সুপারক্লাসের উত্তরাধিকারী হয় এবং তার পদ্ধতিগুলিকে ওভাররাইড করে, তখন এটি সুপার ক্লাসে পদ্ধতির আচরণের সাথে সামঞ্জস্য বজায় রাখা উচিত। উদাহরণস্বরূপ, যদি আমরা নিম্নলিখিত শ্রেণীগুলিকে এবং দুটি শ্রেণীর এবং শ্রেণী করি। এখন, ধরা যাক আমরা গাড়ির ক্লাসের মধ্যে নামে একটি পদ্ধতি তৈরি করি, এটি ক্লাসে ওভাররাইড করা যেতে পারে, তবে এটি ক্লাসে অসমর্থিত হবে কারণ একটি ইঞ্জিন নেই (নীচে কোড নমুনা দেখুন) Vehicle Car Bicycle startEngine() Car Bicycle Bicycle মেথড ওভাররাইড করার সময় সাবক্লাসের সুপারক্লাসের আচরণের সাথে সামঞ্জস্য বজায় রাখা উচিত। class Vehicle { public void startEngine() { // start engine of the vehicle } } class Car extends Vehicle { @Override public void startEngine() { // Start Engine } } class Bicycle extends Vehicle { @Override public void startEngine(){ throw new UnsupportedOperationException("Bicycle doesn't have engine"); } } এখন, ধরা যাক এমন কিছু কোড আছে যা টাইপ গাড়ির একটি বস্তুর প্রত্যাশা করে এবং পদ্ধতির উপর নির্ভর করে। যদি, কোডের টুকরোটিকে কল করার সময় টাইপের একটি বস্তুকে পাস করার পরিবর্তে আমরা একটি অবজেক্টকে পাস করি, এটি কোডে সমস্যা সৃষ্টি করবে। যেহেতু মেথড কল করা হলে (গুলি) ক্লাসের পদ্ধতি একটি ব্যতিক্রম ছুঁড়ে দেবে। এটি সলিড নীতির লঙ্ঘন হবে (লিসকভের প্রতিস্থাপন নীতি) startEngine() Vehicle Bicycle startEngine() Bicycle এই সমস্যাটি সমাধান করার জন্য, আমরা এবং দুটি ক্লাস তৈরি করতে পারি এবং ক্লাস থেকে ইনহেরিট এবং থেকে ইনহেরিট পেতে পারি। MotorizedVehicle NonMotorizedVehicle MotorizedVehicle Car NonMotorizedVehicle Bicycle class Vehicle { } class MotorizedVehicle extends Vehicle { public void startEngine() { // start engine here } } class Car extends MotorizedVehicle { @Override public void startEngine() { // Start Engine } } class NonMotorizedVehicle extends Vehicle { public void startRiding() { // Start without engine } } class Bicycle extends NonMotorizedVehicle { @Override public void startRiding(){ // Start riding without the engine. } } 4. ইন্টারফেস সেগ্রিগেশন নীতি সলিড প্রিন্সিপলে "I" মানে ইন্টারফেস সেগ্রিগেশন প্রিন্সিপল। ইন্টারফেস সেগ্রিগেশন নীতিটি বলে যে বড় ইন্টারফেস থাকার পরিবর্তে যা প্রয়োগকারী ক্লাসগুলিকে অব্যবহৃত পদ্ধতিগুলি প্রয়োগ করতে বাধ্য করে, আমাদের ছোট ইন্টারফেস থাকা উচিত এবং ক্লাসগুলি প্রয়োগ করা উচিত। এইভাবে, ক্লাসগুলি শুধুমাত্র প্রাসঙ্গিক পদ্ধতি প্রয়োগ করে এবং পরিষ্কার থাকে। আপনার ইন্টারফেসগুলিকে একটি বড় ইন্টারফেসের পরিবর্তে একাধিক ছোট ইন্টারফেসে ভাগ করুন। উদাহরণস্বরূপ, জাভাতে বিল্ট-ইন কালেকশন ফ্রেমওয়ার্ক দেখি। অন্যান্য ডেটা স্ট্রাকচারের মধ্যে, Java এবং ডেটা স্ট্রাকচারও প্রদান করে, LinkedList ArrayList ক্লাস নিম্নলিখিত ইন্টারফেস (গুলি) প্রয়োগ করে: , , , , এবং । ArrayList Serializable Cloneable Iterable Collection List RandomAccess ক্লাস , , , , , এবং প্রয়োগ করে। LinkedList Serializable Cloneable Iterable Collection Deque List Queue যে ইন্টারফেস বেশ অনেক! এতগুলি ইন্টারফেস থাকার পরিবর্তে, জাভা বিকাশকারীরা , , , , , এবং একটি ইন্টারফেসে একত্রিত করতে পারত, ধরা যাক ইন্টারফেস। এখন, এবং উভয় ক্লাসই এই নতুন ইন্টারফেসটি বাস্তবায়ন করতে পারত। Serializable Cloneable Iterable Collecton List RandomAccess IList ArrayList LinkedList IList যাইহোক, যেহেতু এলোমেলো অ্যাক্সেস সমর্থন করে না, এটি ইন্টারফেসে পদ্ধতিগুলি প্রয়োগ করতে পারে এবং কেউ এটিকে কল করার চেষ্টা করলে ফেলে দিতে পারে। LinkedList RandomAccess UnsupportedOperationException যাইহোক, এটি সলিড প্রিন্সিপলে ইন্টারফেস সেগ্রিগেশন নীতির লঙ্ঘন হবে কারণ এটি লিঙ্কডলিস্ট ক্লাসকে ইন্টারফেসের অভ্যন্তরে পদ্ধতিগুলি প্রয়োগ করতে "বাধ্য" করবে যদিও প্রয়োজন নেই৷ RandomAccess অতএব, সাধারণ আচরণের উপর ভিত্তি করে ইন্টারফেসটিকে বিভক্ত করা এবং প্রতিটি ক্লাসকে একটি বড় ইন্টারফেসের পরিবর্তে অনেকগুলি ইন্টারফেস প্রয়োগ করতে দেওয়া ভাল। নির্ভরতা বিপরীত নীতি ডিপেনডেন্সি ইনভার্সন প্রিন্সিপল বলে যে উপরের স্তরের ক্লাসগুলি সরাসরি নীচের স্তরের ক্লাসের উপর নির্ভর করবে না। এটি দুটি স্তরের বি/ডব্লিউ একটি টাইট কাপলিং ঘটায়। এর পরিবর্তে, নিম্ন শ্রেণীর একটি ইন্টারফেস প্রদান করা উচিত যার উপর উচ্চ-স্তরের শ্রেণীগুলি নির্ভর করবে। ক্লাসের চেয়ে ইন্টারফেসের উপর নির্ভর করুন উদাহরণ স্বরূপ, চলুন আমরা উপরে উদাহরণটি দেখেছি তা চালিয়ে যাই এবং কিছু অর্থপ্রদানের বিকল্প যোগ করতে এটিকে উন্নত করি। ধরা যাক আমাদের কাছে এবং দুই ধরনের পেমেন্ট অপশন আছে। এখন, ক্লাসে, আমরা একটি পদ্ধতি যোগ করতে চাই যা কার্টের মান গণনা করবে এবং সরবরাহকৃত অর্থপ্রদানের ভিত্তিতে অর্থপ্রদান শুরু করবে। পদ্ধতি Cart DebitCard Paypal Cart placeOrder এটি করার জন্য, আমরা ক্লাসের ভিতরে ক্ষেত্র হিসাবে দুটি অর্থপ্রদানের বিকল্প যোগ করে উপরে উদাহরণে নির্ভরতা যোগ করতে পারতাম। যাইহোক, এটি এবং ক্লাসের সাথে ক্লাসকে শক্তভাবে সংযুক্ত করবে। Cart Cart DebitCard Paypal Cart এর পরিবর্তে, আমরা একটি ইন্টারফেস তৈরি করব এবং এবং উভয় ক্লাসেই ইন্টারফেসগুলি বাস্তবায়ন করব। এখন, শ্রেণী ইন্টারফেসের উপর নির্ভর করবে, ব্যক্তিগত অর্থপ্রদানের প্রকারের উপর নয়। এটি ক্লাসগুলিকে ঢিলেঢালাভাবে সংযুক্ত রাখে। Payment DebitCard Paypal Payment Cart Payment নীচের কোড দেখুন. public interface Payment { void doPayment(double amount); } public class PaypalPayment implements Payment { @Override public void doPayment(double amount) { // logic to initiate paypal payment } } public class DebitCardPayment implements Payment { @Override public void doPayment(double amount) { // logic to initiate payment via debit card } } import java.util.ArrayList; import java.util.List; public class Cart { private List<Item> items; private Payment paymentOption; public Cart(Payment paymentOption) { this.items = new ArrayList<>(); this.paymentOption = paymentOption; } public void addToCart(Item item) { items.add(item); } public double calculateCartValue() { double value = 0.0; for(Item item: items) { value += item.getValue(); } return value; } public void placeOrder() { this.paymentOption.doPayment(calculateCartValue()); } } আপনি যদি অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং, GoF ডিজাইন প্যাটার্ন এবং নিম্ন-স্তরের ডিজাইন ইন্টারভিউ সম্পর্কে আরও জানতে আগ্রহী হন, তাহলে আমার উচ্চ রেট দেওয়া কোর্স দেখুন অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং + জাভা ডিজাইন প্যাটার্নস