paint-brush
জাভাতে সলিড প্রিন্সিপলস: একটি বিগিনারস গাইডদ্বারা@ps06756
14,465 পড়া
14,465 পড়া

জাভাতে সলিড প্রিন্সিপলস: একটি বিগিনারস গাইড

দ্বারা Pratik Singhal14m2024/03/22
Read on Terminal Reader

অতিদীর্ঘ; পড়তে

সলিড নীতি হল স্কেলযোগ্য সফ্টওয়্যারগুলি বিকাশের জন্য প্রয়োজনীয় অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিংয়ের নীতি। নীতিগুলি হল: S: একক দায়িত্ব নীতি O: খোলা/বন্ধ নীতি এল: লিসকভের প্রতিস্থাপন নীতি আমি: ইন্টারফেস সেগ্রিগেশন নীতি D: নির্ভরতা বিপরীত নীতি
featured image - জাভাতে সলিড প্রিন্সিপলস: একটি বিগিনারস গাইড
Pratik Singhal HackerNoon profile picture
0-item

সলিড ডিজাইনের নীতিগুলি হল সবচেয়ে গুরুত্বপূর্ণ ডিজাইনের নীতিগুলি যা আপনাকে পরিষ্কার কোড লিখতে জানতে হবে৷ সলিড নীতিগুলির উপর একটি শক্ত কমান্ড থাকা যে কোনও প্রোগ্রামারের জন্য একটি অপরিহার্য দক্ষতা। তারা ভিত্তি যার উপর অন্যান্য নকশা নিদর্শন বিকশিত হয়.


এই নিবন্ধে, আমরা বাস্তব জীবনের কিছু উদাহরণ ব্যবহার করে সলিড ডিজাইনের নীতিগুলি মোকাবেলা করব এবং তাদের গুরুত্ব বুঝতে পারব।


পলিমরফিজম, বিমূর্ততা এবং উত্তরাধিকারের সাথে একত্রে, উদ্দেশ্য-ভিত্তিক প্রোগ্রামিংয়ে ভাল হওয়ার জন্য সলিড নীতিগুলি সত্যিই গুরুত্বপূর্ণ।

কেন সলিড নীতিগুলি গুরুত্বপূর্ণ?

সলিড নীতিগুলি একাধিক কারণে গুরুত্বপূর্ণ:


  • সলিড নীতিগুলি আমাদেরকে পরিষ্কার এবং রক্ষণাবেক্ষণযোগ্য কোড লেখার অনুমতি দেয়: যখন আমরা একটি নতুন প্রকল্প শুরু করি, তখন প্রাথমিকভাবে কোডের মান ভাল হয় কারণ আমাদের কাছে সীমিত বৈশিষ্ট্যের সেট রয়েছে যা আমরা বাস্তবায়ন করি। যাইহোক, আমরা আরও বৈশিষ্ট্য যুক্ত করার সাথে সাথে কোডটি বিশৃঙ্খল হতে শুরু করে।


  • সলিড নীতিগুলি বিমূর্ততা, পলিমরফিজম এবং উত্তরাধিকারের ভিত্তির উপর ভিত্তি করে তৈরি করে এবং সাধারণ ব্যবহারের ক্ষেত্রে ডিজাইনের প্যাটার্ন তৈরি করে। এই ডিজাইন প্যাটার্নগুলি বোঝা প্রোগ্রামিংয়ে সাধারণ ব্যবহারের ক্ষেত্রে প্রয়োগ করতে সহায়তা করে।


  • সলিড নীতিগুলি আমাদের পরিষ্কার কোড লিখতে সাহায্য করে যা কোডের পরীক্ষাযোগ্যতা উন্নত করে। এটি কারণ কোডটি মডুলার এবং ঢিলেঢালাভাবে সংযুক্ত। প্রতিটি মডিউল স্বাধীনভাবে উন্নত এবং স্বাধীনভাবে পরীক্ষা করা যেতে পারে।


আসুন এখন বাস্তব-বিশ্বের উদাহরণ সহ প্রতিটি সলিড নীতিগুলি বিশদভাবে অন্বেষণ করি।

1. একক দায়িত্ব নীতি

SOLID নীতিতে S এর অর্থ একক দায়িত্ব নীতি। একক দায়বদ্ধতার নীতি বলে যে একটি শ্রেণীর পরিবর্তনের শুধুমাত্র একটি কারণ থাকা উচিত। এটি আমাদের প্রকল্পে অতিরিক্ত প্রয়োজনীয়তাগুলি অন্তর্ভুক্ত করার সময় আমাদের পরিবর্তন করতে হবে এমন স্থানের সংখ্যা সীমিত করে৷

প্রতিটি ক্লাস পরিবর্তন করার জন্য ঠিক একটি কারণ থাকা উচিত।

উদাহরণ স্বরূপ, ধরা যাক আমরা জাভাতে একটি ব্যাঙ্কিং অ্যাপ্লিকেশন ডিজাইন করছি যেখানে আমাদের একটি SavingsAccount ক্লাস রয়েছে যা ডেবিট, ক্রেডিট এবং sendUpdates এর মত মৌলিক ক্রিয়াকলাপগুলিকে অনুমতি দেয়৷ sendUpdate পদ্ধতিটি NotificationMedium নামে একটি enum নেয় (যেমন ইমেল, SMS, ইত্যাদি) এবং উপযুক্ত মাধ্যমে আপডেট পাঠায়। আমরা নীচে দেখানো হিসাবে এটির জন্য কোড লিখব।


 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 ক্লাসটি দেখেন, এটি একাধিক কারণে পরিবর্তন হতে পারে:


  1. SavingsAccount ক্লাসের মূল যুক্তিতে (যেমন debit , credit ইত্যাদি) কোনো পরিবর্তন হলে।


  2. যদি ব্যাঙ্ক একটি নতুন বিজ্ঞপ্তি মাধ্যম চালু করার সিদ্ধান্ত নেয় (আসুন 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 ডিজাইন প্যাটার্ন এবং নিম্ন-স্তরের ডিজাইন ইন্টারভিউ সম্পর্কে আরও জানতে আগ্রহী হন, তাহলে আমার উচ্চ রেট দেওয়া কোর্স অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং + জাভা ডিজাইন প্যাটার্নস দেখুন