সকল সফটওয়্যার অ্যাপ্লিকেশন সময়ের সাথে সাথে পরিবর্তিত হয়। সফটওয়্যারের পরিবর্তনগুলো ক্যাসকেডিং সমস্যা তৈরি করতে পারে যা অপ্রত্যাশিত। কিন্তু পরিবর্তন অনিবার্য, কারণ আমরা এমন সফটওয়্যার তৈরি করতে পারি না যা পরিবর্তন নহয়। সফটওয়্যার রিকোয়ারমেন্টস সফটওয়্যারের বৃদ্ধির সাথে সাথে পরিবর্তিত হয়ে থাকে। আমরা যা করতে পারি সেটি হলো সফটওয়্যারকে এমনভাবে ডিজাইন করা যাতে সেটি পরিবর্তনের বিরুদ্ধে প্রতিরোধী হয়। সফটওয়্যার সঠিকভাবে ডিজাইন করতে সময় ও শ্রম লাগতে পারে শুরুতে, কিন্তু দীর্ঘমেয়াদে এটি সময় ও শ্রম বাঁচায়। টাইটলি কাপলড (tightly coupled) সফটওয়্যার ভঙ্গুর, এবং পরিবর্তন瑟夫লাফ আমরা প্রিডিক্ট করতে পারি না। খারাপ ডিজাইন করা সফটওয়্যারের কিছু প্রভাব:
- এটি ইমোবিলিটি (immobility) সৃষ্টি করে।
- কোড পরিবর্তন করা ব্যয়বহুল।
- সফটওয়্যারকে সিম্পল করার চেয়ে কমপ্লেক্সিটি যোগ করা সহজ।
- কোড অনম্যানেজেবল (unmanageable)।
- একজন ডেভেলপারের জন্য এর ফাংশনালিটি বুঝতে অনেক সময় লাগে।
- সফটওয়্যারের এক অংশ পরিবর্তন করলে অন্যটি ভাঙতে পারে, এবং আমরা প্রিডিক্ট করতে পারি না পরিবর্তন কী সমস্যা আনবে।
Design Principles and Design Patterns পেপারটি নিচের সিম্পটমগুলো লিস্ট করে রটিং (rotting) সফটওয়্যারের:
- Rigidity (জড়তা): সমস্যা ছাড়া কোড পরিবর্তন করা খুব কঠিন, কারণ একটি জায়গায় পরিবর্তন আনতে অন্য জায়গায়ও পরিবর্তন আনার দরকার পড়ে।
- Fragility (ভঙ্গুরতা): কোড পরিবর্তন করলে সফটওয়্যারের আচরণ ভাঙে। এটা এমন অংশও ভাঙতে পারে যা সরাসরি পরিবর্তন سے সম্পর্কিত নয়।
- Immobility (অচলতা): সফটওয়্যার অ্যাপ্লিকেশনের কিছু অংশের আচরণ সিমিলার হলেও, আমরা কোড রিয়ুজ করতে পারি না এবং তাদের ডুপ্লিকেট করতে হয়।
- Viscosity (শ্লেষতা): সফটওয়্যার পরিবর্তন করা কঠিন হলে, আমরা সেটি ভালো করার বদলে কমপ্লেক্সিটি যোগ করতে থাকি।
সফটওয়্যারকে এমনভাবে ডিজাইন করা প্রয়োজন যাতে পরিবর্তনগুলো কন্ট্রোলযোগ্য এবং প্রিডিক্টেবল হয়।
SOLID ডিজাইন প্রিন্সিপলস এই সমস্যাগুলো সমাধান করে সফটওয়্যার প্রোগ্রামগুলোর ডিকাপলিং (decoupling) দিয়ে। রবার্ট সি. মার্টিন (Robert C. Martin) এই ধারণাগুলো তার পেপার "Design Principles and Design Patterns"-এ معرفی করেন, এবং মাইকেল ফেথার্স (Michael Feathers) এক্রোনিম (acronym) টি বুঝিয়ে দেন।
SOLID ডিজাইন প্রিন্সিপেলে এই পাঁচটি প্রিন্সিপাল অন্তর্ভুক্ত:
- Single Responsibility Principle (একক দায়িত্বের নীতি)
- Open/Closed Principle (ওপেন/ক্লোজড নীতি)
- Liskov Substitution Principle (লিসকভ সাবস্টিটিউশন নীতি)
- Interface Segregation Principle (ইন্টারফেস সেগ্রিগেশন নীতি)
- Dependency Inversion Principle (ডিপেন্ডেন্সি ইনভারশন নীতি)
আমরা রুবিতে ভালো ডিজাইন করা সফটওয়্যার তৈরি করতে এই প্রিন্সিপলগুলো কীভাবে সাহায্য করে সেটি বুঝতে প্রতিটি এক্সপ্লোর করব।
Single Responsibility Principle - SRP
মান লेउ, HR ম্যানেজমেন্ট সফটওয়্যারের জন্য, আমাদের ইউজার তৈরি করা, কর্মীর স্যালারি যোগ করা, এবং কর্মীর পেসলিপ জেনারেট করা ফাংশনালিটি দরকার। বিল্ড করার সময়, আমরা এই সব ফাংশনালিটি একটি ক্লাসে যোগ করতে পারি, কিন্তু এই পদ্ধতিটি এই ফাংশনালিটিগুলোর মধ্যে অনিচ্ছাকৃত ডিপেন্ডেন্সি তৈরি করে। শুরুতে এটি সিম্পল, কিন্তু জিনিসগুলো পরিবর্তন হলে এবং নতুন রিকোয়ারমেন্ট আসলে, আমরা প্রিডিক্ট করতে পারব না কোন ফাংশনালিটি পরিবর্তন থেকে ভাঙবে।
এক ক্লাসের এক, এবং শুধু এক কারণ থাকা উচিত পরিবর্তনের জন্য - Robert C Martin
এখানে একটি নমুনা কোড যেখানে সকল ফাংশনালিটি একটি ক্লাসে:
class User
def initialize(employee, month)
@employee = employee
@month = month
end
def generate_payslip
# ডাটাবেস থেকে পড়া,
# পেসলিপ জেনারেট
# এবং ফাইলে লেখা
self.send_email
end
def send_email
# ইমেইল পাঠানোর কোড
employee.email
month
end
end
পেসলিপ জেনারেট করে ইউজারের কাছে পাঠাতে, আমরা ক্লাস ইনিশিয়ালাইজ করে generate_payslip মেথড কল করতে পারি:
month = 11
user = User.new(employee, month)
user.generate_payslip
এখন, একটি নতুন রিকোয়ারমেন্ট। আমরা পেসলিপ জেনারেট করব কিন্তু ইমেইল পাঠাব না। আমাদের এক্সিস্টিং ফাংশনালিটি যেন থাকে এবং ইন্টারন্যাল রিপোর্টিং-এর জন্য একটি নতুন পেসলিপ জেনারেটর যোগ করতে হবে ইমেইল পাঠানো ছাড়া, কারণ এটি ইন্টারন্যাল উদ্দেশ্যে। এই ফেজে, আমরা চাই এক্সিস্টিং পেসলিপ যা কর্মীদের কাছে পাঠানো হচ্ছে সেটি ফাংশনাল রয়ে।
এই রিকোয়ারমেন্টের জন্য, আমরা এক্সিস্টিং কোড রিয়ুজ করতে পারি না। আমাদের entweder generate_payslip মেথডে একটি ফ্ল্যাগ যোগ করতে হবে যেন যদি true হয় তাহলে ইমেইল পাঠাবে না। এটি করা যায়, কিন্তু যেহেতু এটি এক্সিস্টিং কোড পরিবর্তন করছে, এটি এক্সিস্টিং ফাংশনালিটি ভাঙতে পারে।
জিনিসগুলো ভাঙতে না দেয়ার জন্য, আমাদের এই লজিকগুলো আলাদা আলাদা ক্লাসে ডিকাপল করতে হবে:
class PayslipGenerator
def initialize(employee, month)
@employee = employee
@month = month
end
def generate_payslip
# ডাটাবেস থেকে পড়া,
# পেসলিপ জেনারেট
# এবং ফাইলে লেখা
end
end
class PayslipMailer
def initialize(employee)
@employee = employee
end
def send_mail
# ইমেইল পাঠানোর কোড
employee.email
month
end
end
এরপর, আমরা এই দুটি ক্লাস ইনিশিয়ালাইজ করে তাদের মেথড কল করতে পারি:
month = 11
# জেনারেল পেসলিপ জেনারেটর
generator = PayslipGenerator.new(employee, month)
generator.generate_payslip
# ইমেইল পাঠানো
mailer = PayslipMailer.new(employee, month)
mailer.send_mail
এই পদ্ধতিটি দায়িত্বগুলো ডিকাপল করতে সাহায্য করে এবং প্রিডিক্টেবল পরিবর্তন নিশ্চিত করে। আপনি যদি শুধু মেইলার ফাংশনালিটি পরিবর্তন করতে চান, তবে পেসলিপ জেনারেটর ছাড়া সেটি করতে পারেন। এটি ফাংশনালিটিতে যেকোনো পরিবর্তন প্রিডিক্ট করতে সাহায্য করে।
মান লেউ, আমাদের ইমেইলে মাস ফিল্ডের ফরম্যাট Nov করতে হবে 11 এর বদলে। এই কেসে, আমরা PayslipMailer ক্লাস মডিফাই করব, এবং এটি নিশ্চিত করবে PayslipGenerator ফাংশনালিটিতে কিছুই পরিবর্তন বা ভাঙবে না।
প্রতিবার আপনি কোড লিখলে, পরে একটা প্রশ্ন করুন। এই ক্লাসের দায়িত্ব কী? আপনার উত্তর যদি "এবং" (and) শব্দ ধারণ করে, তবে ক্লাসকে একাধিক ক্লাসে ভাগ করুন। ছোট ক্লাসগুলো বড়, জেনেরিক ক্লাস চেয়ে সবসময় ভালো।
Open/Closed Principle - OCP
বারট্রান্ড মেয়ার (Bertrand Meyer) অপেন/ক্লোজড প্রিন্সিপেল তার বই "Object-Oriented Software Construction"-এ উদ্ভাবন করেছিলেন।
প্রিন্সিপেলটি বলে, "সফটওয়্যার এন্টিটি (ক্লাস, মডিউল, ফাংশন, ইত্যাদি) এক্সটেনশনের জন্য খোলা থাকা উচিত কিন্তু মডিফিকেশনের জন্য বন্ধ"। এর মানে হলো আমরা এন্টিটি পরিবর্তন না করেই আচরণ পরিবর্তন করতে পারব।
উপরোক্ত উদাহরণে, আমাদের কর্মীদের জন্য পেসলিপ পাঠানোর ফাংশনালিটি আছে, যা সব কর্মীর জন্য খুব জেনেরিক। কিন্তু একটি নতুন রিকোয়ারমেন্ট আসে: কর্মীর টাইপ অনুযায়ী পেসলিপ জেনারেট করা। ফুল-টাইম কর্মী এবং কন্ট্রাক্টরের জন্য আলাদা পেরোল জেনারেশন লজিক দরকার। এই কেসে, আমরা এক্সিস্টিং PayrollGenerator মডিফাই করে এই ফাংশনালিটি যোগ করতে পারি:
class PayslipGenerator
def initialize(employee, month)
@employee = employee
@month = month
end
def generate_payslip
# ডাটাবেস থেকে পড়া,
# পেসলিপ জেনারেট
if employee.contractor?
# কন্ট্রাক্টরের জন্য পেসলিপ জেনারেট
else
# নরমাল পেসলিপ জেনারেট
end
# এবং ফাইলে লেখা
end
end
তবে, এটি একটি ব্যাড প্যাটার্ন। এর মাধ্যমে, আমরা এক্সিস্টিং ক্লাস মডিফাই করছি। যদি কর্মী কন্ট্র্যাক্টের উপর ভিত্তি করে আরও জেনারেশন লজিক যোগ করতে হয়, তবে এক্সিস্টিং ক্লাস মডিফাই করতে হবে, কিন্তু এতে ওপেন/ক্লোজড প্রিন্সিপেল লঙ্ঘন হয়। ক্লাস মডিফাই করলে, আমরা অনিচ্ছাকৃত পরিবর্তন আনার রিস্ক নেই। যখন কিছু পরিবর্তন বা যোগ হবে, এটি এক্সিস্টিং কোডে অজানা সমস্যা সৃষ্টি করতে পারে। এই if-else একই ক্লাসের আরও জায়গায় থাকতে পারে। সুতরাং, যখন আমরা একটি নতুন কর্মী টাইপ যোগ করব, আমরা হয়তো সেই সব জায়গা মিস করব যেখানে এই if-else রয়েছে। সেগুলো খুঁজে বের করে মডিফাই করা রিস্কি এবং সমস্যা সৃষ্টি করতে পারে।
আমরা এই কোডটি রিফ্যাক্টর করতে পারি এমনভাবে যাতে আমরা ফাংশনালিটি এক্সটেনড করে যোগ করতে পারি কিন্তু এন্টিটি পরিবর্তন না করি। তাহলে, প্রতিটির জন্য আলাদা ক্লাস তৈরি করি এবং সবার জন্য একই generate মেথড রাখি:
class ContractorPayslipGenerator
def initialize(employee, month)
@employee = employee
@month = month
end
def generate
# ডাটাবেস থেকে পড়া,
# পেসলিপ জেনারেট
# এবং ফাইলে লেখা
end
end
class FullTimePayslipGenerator
def initialize(employee, month)
@employee = employee
@month = month
end
def generate
# ডাটাবেস থেকে পড়া,
# পেসলিপ জেনারেট
# এবং ফাইলে লেখা
end
নিশ্চিত করুন এইগুলোতে একই মেথড নাম আছে। এখন, PayslipGenerator ক্লাস পরিবর্তন করি এই ক্লাসগুলো ব্যবহার করতে:
GENERATORS = {
'full_time' => FullTimePayslipGenerator,
'contractor' => ContractorPayslipGenerator
}
class PayslipGenerator
def initialize(employee, month)
@employee = employee
@month = month
end
def generate_payslip
# ডাটাবেস থেকে পড়া,
# পেসলিপ জেনারেট
GENERATORS[employee.type].new(employee, month).generate()
# এবং ফাইলে লেখা
end
end
এখানে, আমাদের একটি GENERATORS কনস্ট্যান্ট আছে যা কর্মীর টাইপ অনুযায়ী কল করার ক্লাস ম্যাপ করে। আমরা এটা ব্যবহার করে ডিটারমাইন করতে পারি কোন ক্লাস কল করতে হবে। এখন, নতুন ফাংশনালিটি যোগ করতে হলে, আমরা সিম্পলি সেই জন্য একটি নতুন ক্লাস তৈরি করে GENERATORS কনস্ট্যান্টে যোগ করতে পারি। এটি এক্সিস্টিং লজিক ভাঙতে না দিয়ে বা সেটি সম্পর্কে চিন্তা না করেই ক্লাস এক্সটেনড করতে সাহায্য করে। আমরা সহজে যেকোনো টাইপের পেসলিপ জেনারেটর যোগ বা বাদ দিতে পারি।
Liskov Substitution Principle - LSP
Liskov substitution principle বলে, "যদি S, T-এর সাবটাইপ হয়, তবে T টাইপের অবজেক্টগুলো S টাইপের অবজেক্ট দিয়ে রিপ্লেস করা যাবে"।
এই প্রিন্সিপেল বুঝতে, প্রথমে সমস্যাটি বুঝতে হবে। ওপেন/ক্লোজড প্রিন্সিপেলের অধীনে, আমরা সফটওয়্যারকে এমনভাবে ডিজাইন করেছি যাতে সেটি এক্সটেন্ড করা যায়। আমরা একটি সাবক্লাস Payslip generator তৈরি করেছি যা একটি স্পেসিফিক কাজ করে। কলারের জন্য, তারা যে ক্লাস কল করছে সেটি অজানা। এই ক্লাসগুলোকে একই আচরণ রাখতে হবে যাতে কলার পার্থক্য বুঝতে না পারে। আচরণ বলতে আমরা মানে মেথডগুলো কনসিস্টেন্ট থাকতে হবে। এই ক্লাসের মেথডগুলোর নিচের বৈশিষ্ট্যগুলো থাকতে হবে:
- একই নাম থাকবে
- একই সংখ্যক আর্গুমেন্ট নেবে একই ডেটা টাইপের সাথে
- একই ডেটা টাইপ রিটার্ন করবে
পেসলিপ জেনারেটর উদাহরণটি দেখি। আমাদের দুটি জেনারেটর আছে, একটি ফুল-টাইম কর্মীর জন্য এবং অন্যটি কন্ট্রাক্টরের জন্য। এখন, এই পেসলিপগুলোর কনসিস্টেন্ট আচরণ নিশ্চিত করতে, আমাদের সেগুলোকে একটি বেস ক্লাস থেকে ইনহেরিট করতে হবে। একটি বেস ক্লাস User ডিফাইন করি।
class User
def generate
end
end
ওপেন/ক্লোজড প্রিন্সিপেলের উদাহরণে তৈরি সাবক্লাসগুলোর বেস ক্লাস ছিল না। এটিকে বেস ক্লাস User দিয়ে মডিফাই করি:
class ContractorPayslipGenerator < User
def generate
# পেসলিপ জেনারেট করার কোড
end
end
class FullTimePayslipGenerator < User
def generate
# পেসলিপ জেনারেট করার কোড
end
end
এরপর, আমরা একটি সেট মেথড ডিফাইন করি যেগুলো User ক্লাস ইনহেরিট করা যেকোনো সাবক্লাসের জন্য আবশ্যক। আমরা এই মেথডগুলো বেস ক্লাসে ডিফাইন করি। আমাদের কেসে, শুধু একটি মেথড দরকার, যেটি generate নামে।
class User
def generate
raise "NotImplemented"
end
end
এখানে, আমরা generate মেথড ডিফাইন করেছি যেটিতে একটি raise স্টেটমেন্ট আছে। সুতরাং, User বেস ক্লাস ইনহেরিট করা যেকোনো সাবক্লাসের generate মেথড থাকতে হবে। যদি না থাকে, তবে এটি এরর থ্রো করবে যে মেথডটি ইমপ্লিমেন্টেড নয়। এইভাবে, আমরা নিশ্চিত করতে পারি যে সাবক্লাস কনসিস্টেন্ট। এর মাধ্যমে, কলার সবসময় নিশ্চিত থাকতে পারবে যে generate মেথড উপস্থিত।
এই প্রিন্সিপেল যেকোনো সাবক্লাস সহজে সাবস্টিটিউট করতে সাহায্য করে জিনিস ভাঙতে না দিয়ে এবং অনেক পরিবর্তন না করে।
Interface Segregation Principle - ISP
Interface segregation principle স্ট্যাটিক ল্যাঙ্গুয়েজের জন্য প্রযোজ্য, আর যেহেতু রুবি একটি ডাইনামিক ল্যাঙ্গুয়েজ, এখানে ইন্টারফেসের কোনো কনসেপ্ট নেই। ইন্টারফেস ক্লাসগুলোর মাঝপথে অ্যাবস্ট্রাকশন রুল ডিফাইন করে।
প্রিন্সিপেলটি বলে,
ক্লায়েন্টদের এমন ইন্টারফেসের ওপর নির্ভর করতে বাধ্য করা উচিত না যা তারা ব্যবহার করে না। - Robert C. Martin
এর মানে হলো, একটি জেনেরালাইজড ইন্টারফেস যা যেকোনো ক্লাস ব্যবহার করতে পারে, তার চেয়ে অনেক ইন্টারফেস থাকা ভালো। যদি আমরা একটি জেনেরালাইজড ইন্টারফেস ডিফাইন করি, তবে ক্লাসকে একটি সেটি ডিফিনিশনের ওপর নির্ভর করতে হয় যা সেটি ব্যবহার করে না।
রুবিতে ইন্টারফেস নেই, কিন্তু ক্লাস এবং সাবক্লাস কনসেপ্ট দিয়ে কিছু সিমিলার বানাতে পারি।
Liskov substitution principle-র জন্য ব্যবহৃত উদাহরণে, আমরা দেখেছিলাম সাবক্লাস FullTimePayslipGenerator জেনেরিক ক্লাস User থেকে ইনহেরিট করেছিল। কিন্তু User খুব জেনেরিক ক্লাস এবং অন্যান্য মেথডও থাকতে পারে। যদি আমাদের অন্য ফাংশনালিটি দরকার হয়, যেমন Leave, সেটিক User-এর সাবক্লাস হতে হবে। Leave-এ generate মেথড দরকার নেই, কিন্তু সেটি এই মেথডের ওপর ডিপেন্ডেন্ট হবে। তাই, জেনেরিক ক্লাস রাখার বদলে, আমরা bunun জন্য একটি স্পেসিফিক ক্লাস रखতে পারি:
class Generator
def generate
raise "NotImplemented"
end
end
class ContractorPayslipGenerator < Generator
def generate
# পেসলিপ জেনারেট করার কোড
end
end
class FullTimePayslipGenerator < Generator
def generate
# পেসলিপ জেনারেট করার কোড
end
end
এই জেনারেটর পেসলিপ জেনারেশনের জন্য স্পেসিফিক, এবং সাবক্লাসকে জেনেরিক User ক্লাসের ওপর নির্ভর করতে হয় না।
Dependency Inversion Principle - DIP
Dependency inversion সফটওয়্যার মডিউল ডিকাপল করার একটি প্রিন্সিপেল।
একটি হাই-লেভেল মডিউল লো-লেভেল মডিউলের ওপর নির্ভর করা উচিত না; উভয়ই অ্যাবস্ট্রাকশনের ওপর নির্ভর করা উচিত।
উপরোক্ত প্রিন্সিপেলগুলো ব্যবহার করে ডিজাইন আমাকে ডিপেন্ডেন্সি ইনভারশন প্রিন্সিপেলের দিকে নিয়ে যায়। যেকোনো ক্লাস যার সিঙ্গেল রেসপন্সিবিলিটি আছে, সেটি কাজ করার জন্য অন্য ক্লাসগুলো থেকে কিছু জিনিস দরকার। পেরোল জেনারেট করতে, আমাদের ডাটাবেস এক্সেস দরকার, এবং রিপোর্ট জেনারেট হলে ফাইলে লিখতে হবে। সিঙ্গেল রেসপন্সিবিলিটি প্রিন্সিপেলে, আমরা একটি ক্লাসের জন্য শুধু এক কাজ রাখতে চাই। কিন্তু, ডাটাবেস থেকে পড়া এবং ফাইলে লেখা একই ক্লাসের ভিতরে করতে হয়।
এই ডিপেন্ডেন্সিগুলো রিমুভ করা এবং মেইন বিজনেস লজিক ডিকাপল করা গুরুত্বপূর্ণ। এটি কোডকে পরিবর্তনের সময় ফ্লুইড রাখবে, এবং পরিবর্তন প্রিডিক্টেবল হবে। ডিপেন্ডেন্সি ইনভার্ট করা দরকার, এবং মডিউলের কলার ডিপেন্ডেন্সির ওপর কন্ট্রোল রাখা উচিত। আমাদের পেসলিপ জেনারেটরে, ডিপেন্ডেন্সি হলো রিপোর্টের জন্য ডেটা সোর্স; এই কোডকে এমনভাবে অর্গানাইজ করা দরকার যাতে কলার সোর্স স্পেসিফাই করতে পারে। ডিপেন্ডেন্সির কন্ট্রোল ইনভার্ট করতে হবে এবং কলার সহজে মডিফাই করতে পারবে।

উপরোক্ত উদাহরণে, ContractorPayslipGenerator মডিউল ডিপেন্ডেন্সি কন্ট্রোল করে, কারণ ডেটা কোথা থেকে পড়া হবে এবং আউটপুট কীভাবে সংরক্ষণ হবে সেটি ক্লাস দ্বারা কন্ট্রোল হয়। এটাকে উল্টো দিতে, চলুন একটি UserReader ক্লাস বানাই যা ইউজার ডেটা পড়ে:
class UserReader
def get
raise "NotImplemented"
end
end
এখন, মান লেউ আমরা পোস্টগ্রেস (Postgres) থেকে ডেটা পড়তে চাই। এজন্য UserReader-এর একটি সাবক্লাস তৈরি করি:
class PostgresUserReader < UserReader
def get
# পোস্টগ্রেস থেকে ডেটা পড়ার কোড
end
সিমিলারলি, আমাদের FileUserReader, InMemoryUserReader, বা যে কোনো টাইপের রিডার থাকতে পারে। এখন FullTimePayslipGenerator ক্লাস মডিফাই করি যাতে সেটি PostgresUserReader ডিপেন্ডেন্সি হিসেবে ব্যবহার করে।
class FullTimePayslipGenerator < Generator
def initialize(datasource)
@datasource = datasource
end
def generate
# পেসলিপ জেনারেট করার কোড
data = datasource.get()
end
end
কলার এখন PostgresUserReader ডিপেন্ডেন্সি হিসেবে পাস করতে পারে:
datasource = PostgresUserReader.new()
FullTimePayslipGenerator.new(datasource)
কলার এখন ডিপেন্ডেন্সির ওপর পুরো কন্ট্রোল রাখে এবং দরকার হলে সহজে সোর্স পরিবর্তন করতে পারে।
ডিপেন্ডেন্সি ইনভার্ট করা শুধু ক্লাসের জন্য নয়। কনফিগারেশনগুলোও ইনভার্ট করতে হয়। উদাহরণস্বরূপ, পোস্টগ্রেস সার্ভারের সাথে কানেক্ট করার সময়, আমাদের স্পেসifik কনফিগারেশন দরকার, যেমন DBURL, ইউজারনেম, এবং পাসওয়ার্ড। এই কনফিগারেশনগুলো ক্লাসে হার্ডকোড করার বদলে, আমাদের কলার থেকে পাস ডাউন করতে হবে।
class PostgresUserReader < UserReader
def initialize(config)
@config = config
end
def get
# কনফিগ দিয়ে DB ইনিশিয়ালাইজ
self.config
# পোস্টগ্রেস থেকে ডেটা পড়ার কোড
end
end
কলার কনফিগ প্রদান করবে:
config = { url: "url", user: "user" }
datasource = PostgresUserReader.new(config)
FullTimePayslipGenerator.new(datasource)
কলার এখন ডিপেন্ডেন্সির ওপর সম্পূর্ণ কন্ট্রোল রাখে, এবং পরিবর্তন ম্যানেজমেন্ট সহজ এবং কম পেইনফুল।
উপসংহার
SOLID ডিজাইন কোড ডিকাপল করতে সাহায্য করে এবং পরিবর্তন কম পেইনফুল করে। প্রোগ্রামগুলোকে এমনভাবে ডিজাইন করা গুরুত্বপূর্ণ যাতে সেগুলো ডিকাপলড, রিয়ুজেবল, এবং পরিবর্তনের প্রতি রেসপন্সিভ হয়। পাঁচটি SOLID প্রিন্সিপেল একে অপরের পূরক এবং একসাথে কো-এগزیস্ট করা উচিত। একটি ভালো ডিজাইন করা কোডবেস ফ্লেক্সিবল, পরিবর্তন করা সহজ, এবং কাজ করার জন্য মজাদার। যেকোনো নতুন ডেভেলপার সহজে জয়েন করে কোড বুঝতে পারে।
SOLID কী ধরণের সমস্যা সমাধান করে এবং আমরা কেন এটা করছি সেটি বুঝতে পারা খুব গুরুত্বপূর্ণ। সমস্যাটি বুঝতে পারলে আপনি ডিজাইন প্রিন্সিপেলগুলো গ্রহণ করতে পারবেন এবং আরও ভালো সফটওয়্যার ডিজাইন করতে পারবেন।