সফটওয়্যার ডিজাইন প্রিন্সিপাল-১
ডিজাইন বলতে আমরা বুঝি কোন কিছু আকানো বা কোন একটি জিনিষ দেখতে কেমন হবে, এর ফাংশনালিটি কেমন হবে আগে থেকে তার পরিকল্পনা করা। সফটওয়ার ডিজাইন বলতেও এরকম পরিকল্পনা পদ্ধতি বোঝায় যেখানে কোন একটি সফটওয়্যারের রিকোয়ারমেন্ট কে এনালাইসিস করে সলিউশন ডেভেলপ করার সময় সুনির্দিষ্ট কিছু নিয়মনীতি মেনে দাড় করানো।
সফটওয়্যার ডিজাউন প্রিন্সিপাল হল কোন একটি সিস্টেম ডেভেলপ করার সময় সফটওয়্যার ডেভেলপারের জন্য কিছু নির্দিষ্ট নিয়ম-নীতির সমষ্টি। যেন সিস্টেম টা একটি ‘গুড সিস্টেম’ এ পরিনত হয়। সফটওয়্যার ডিজাইন প্রিন্সিপালগুলোর মধ্যে বেশ কতকগুলো প্রিন্সিপাল রয়েছে, যেমন –
- সলিড (SOLID)
- ড্রাই(DRY)
- কিস (KISS)
- ইয়াগনি (YAGNI) ইত্যাদি
তবে এদের মধ্যে সবচেয়ে গুরুত্বপূর্ন ৫টা প্রিন্সিপাল নিয়ে একটি নেমোনিক টার্ম তৈরি হয় যার নাম সলিড (SOLID) প্রিন্সিপাল।
সলিড (SOLID) প্রিন্সিপাল
অবজেক্ট অরিয়েন্টেড ক্লাস ডিজাইন করার সময় কিছু নির্দিষ্ট নিয়মনীতি এবং বেস্ট প্র্যাক্টিসগুলোর মধ্যে ৫টি নিয়ম নিয়ে হল সলিড প্রিন্সিপাল। সফওয়্যার আর্কিটেকচার এবং ডিজাইন প্যাটার্ন বুঝার জন্য এই পাচটা প্রিন্সিপাল আমাদের সাহায্য করে। সুতরাং প্রত্যেকটা ডেভেলপারের অন্তত সলিড(SOLID) নিয়ে সলিড বুঝ থাকা উচিত।
SOLID এর ৫টি প্রিন্সিপাল হল –
- Single Responsibility Principle
- Open-Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
ইতিহাস
রবার্ট জে মার্টিন(Robert J.Martin) কে অনেকেই চিনে থাকবেন। প্রখ্যাত এই কম্পিউটার বিজ্ঞানি ‘আংকেল বব’ নামেও বহুল পরিচিত। সারা দুনিয়াতে উনার বেস্ট সেলিং দুইটা বই হল – Clean Code এবং Clean Architecture । উনি Agile Alliance এরও একজন অংশগ্রহনকারী।
তো এই বব চাচ্চু ২০০০ সালে তার একটি পেপারে প্রথম সলিডের পাচটি প্রিন্সিপালকে পরিচয় করান। কিন্তু তিনি কোন নাম দেননি। পরবর্তিতে মিকাইল ফিদারস(Micheal Feathers) প্রিন্সিপালগুলোকে একসাথে সমন্বয় করে SOLID নাম দেন।
বব চাচ্চুর বইতে বলেছেন ক্লিন কোড, ক্লিন আর্কিটেকচার এবং ডিজাইন প্রিন্সিপাল একটা আরেকটার সাথে সম্পর্কিত। কারন এই সবগুলো টার্মের একটাই উদ্দেশ্য।
এমন কোড লেখা যাতে অনেক ডেভেলপার একসাথে কাজ করতে পারে এবং কোড হয় সহজে পড়ার যোগ্য, সহজে বুঝার যোগ্য এবং সহজে টেস্ট করার যোগ্য।
সিঙ্গেল রেসপন্সিবিলিটি প্রিন্সিপাল(Single Responsibility Principle)
একটা অবজেক্ট অরিয়েন্টেড ক্লাসে কেবলমাত্র একটা কাজই করা হবে এবং যদি কখনো পরিবর্তন করার দরকার হয় তাহলে এই একটা কারনেই করা হবে।
A class should do one thing therefore it should have only a single reason to change.
টেকনিক্যালি বলতে গেলে, মনে করি একটি ক্লাস হল একটি ডাটা কন্টেইনার। সেটা একটি Book ক্লাস অথবা একটি Author ক্লাস হতে পারে। এবং প্রত্যেকটি ক্লাসের ঐ ক্লাসের সাথে রিলেটেড এনটিটি আছে। যখন কোন ডাটা মডেল পরিবর্তন হবে তখনই শুধু এই ক্লাস পরিবর্তন হবে।
সিঙ্গেল রেসপন্সিবিলিটি প্রিন্সিপাল কেন এত গুরুত্বপূর্ন?
প্রথমত, একটা বড় প্রোজেক্টে অনেক ডেভেলপার একসাথে কাজ করে বা অনেক ডেভেলপমেন্ট টিম কাজ করে। প্রত্যেকটা টিম আলাদা আলাদা এনটিটি নিয়ে কাজ করে। এখন সবারই তো ক্লাস মডিফাই করার দরকার হতে পারে নিজেদের প্রয়োজনে। সবাই যখন একই ক্লাসে নিজেদের প্রয়োজনমাফিক পরিবর্তন করতে যাবে তখন ডেফিনেটলি একে অপরের সাথে ইনকমপ্যাটিবিলিটি ইস্যু তৈরি করবে। সিঙ্গেল রেসপন্সিবিলিটি প্রিন্সিপাল মেনে যখন ক্লাস ডিজাইন করা হবে তখন সবার আলাদা আলাদা ক্লাস থাকবে। যার যখন প্রয়োজন নিজের ক্লাসে মডিফাই করে নেবে। সিম্পল !
দ্বিতীয়ত, ভার্সন কন্ট্রোলিং সহজ হয়। মনে করি আমাদের একটি InvoiceStore ক্লাস আছে যেখানে ডাটাবেজ অপারেশন গুলো হ্যান্ডল করা হয়। এখন এই ক্লাসে যদি কোন মডিফাই করে গিট কমিট করা হয়, তাহলে SRP এর কারনে বোঝা সহজ হয় যে কোডের কোথায় পরিবর্তন তা হইছে।
একই সাথে গিট মার্জ কনফ্লিক্ট ও এড়ানো সম্ভব হয় SRP এর কারনে। একাধিক ব্যাক্তি যখন একই ফাইলে মডিফাই করে গিট পুশ করেন সেটা মার্জ কনফ্লিক্টের একটা বড় কারন। সুতরাং SRP মেনে সবাই আলাদা আলাদা ফাইলে কমিট পুশ করলে মার্জ কনফ্লিক্টের সম্ভাবনাই নেই।
এখন আমরা কিছু হ্যান্ডস অন উদাহরনে দেখব সিঙ্গেল রেসপন্সিবিলিটি প্রিন্সিপাল অমান্য করে কোড করলে কি সমস্যা হয় এবং পরবর্তিতে সেই কোডকে প্রিন্সিপাল মেনে রিফ্যাক্টর করলে কি সুবিধা হয়।
ধরুন, একটা সাধারন বইয়ের দোকানের বই বিক্রয়ের ইনভয়েসিং সফটওয়্যার বানাচ্ছি আমরা। ধরে নিলাম আমাদের সফওয়্যারে শুধু বই বিক্রয় করা ছাড়া আর কোনো ফিচার নেই।
তো এখানে একটা এনটিটি হল Book । সুতরাং Book ক্লাসটাকে তৈরি করে ফেলি প্রথমে –
class Book{
public string $name;
public string $author;
public string $publisher;
public int $price;
public function __construct(string $name, string $author, string $author, int $price){
$this->name = $name;
$this->author = $author;
$this->publisher = $publisher;
$this->price = $price;
}
}
এখন Invoice আরেকটি এনটিটি। তাহলে সেটাও লিখে ফেলা যাক –
class Invoice{
private Book $book;
private int $quantity;
private double $discountRate;
private double $taxRate;
private double $total;
public function __construct(Book $book, int $quantity, double $discountRate, double $taxRate){
$this->book = $book;
$this->quantity = $quantity;
$this->docuntRate = $discountRate;
$this->taxtRate = $taxRate;
$this->total = $this->calculateTotal();
}
public function calculateTotal(): double {
double $price = (($book->$price - $book.$price * $discountRate) * $this->quantity);
double $priceWithTaxes = $price * (1 + $this->taxRate);
return $priceWithTaxes;
}
public void printInvoice() {
// printing logic
}
}
উপরের ক্লাসটাতে আমরা দেখতে পাচ্ছি কতকগুলো ক্লাস প্রোপার্টি এবং তিনটা ক্লাস মেথড ডিফাইন করা আছে।
- invoice() – মেথডে ইনভয়েস টা তৈরি হবে।
- calculateTotal() – মেথডে ইনভয়েসের টোটাল টাকা হিসাব করা হবে।
- printInvoice() – মেথডে ইনভয়েসটা প্রিন্ট করা হবে।
একটু চিন্তা করি, উপরের ক্লাসটাতে কি ভুল আছে?
আসল ভুলটা হল এখানে সিঙ্গেল রেসপন্সিবিলিটি প্রিন্সিপাল অমান্য করা হচ্ছে। একই ক্লাসে ভিতর আমরা ইনভয়েস তৈরি করা, টোটাল হিসাব করা এবং প্রিন্ট করার লজিক লিখছি। এখন আমাদের তো প্রিন্ট করার ফরম্যাট পরিবর্তন হতে পারে। যেমন এখন প্রিন্ট করব পিডিএফ ফরম্যাটে, কিছুদিন পরে প্রয়োজন হতে পারে ডক ফরম্যাটে প্রিন্ট করার। তখন আমরা কি করব? সোজা গিয়ে ক্লাসের মধ্যে নতুন আরেকটা মেথড ডিফাইন করে দেব অথবা এক্সিস্টিং মেথড মডিফাই করব? না। এটা ঠিক হবেনা। ক্লাসটাকে তৈরি করা হইছে ইনভয়েস এর বিজনেস লজিক লেখার জন্য। সুতরাং প্রিন্ট করার জন্য আমরা সেপারেট একটা ক্লাস নেব।
আবার ইনভয়েস টাকে স্টোর করার জন্য নতুন আরেকটা মেথড প্রয়োজন। যেখানে স্টোর করার জন্যও একাধিক পদ্ধতি থাকবে। যেমন, ডাটাবেজে স্টোর করা, ফাইলে স্টোর করা। আবার ডাটাবেজের ক্ষেত্রে একাধিক ডাটাবেজে স্টোর করার অপশন ও থাকতে পারে। এ কাজের জন্য আমরা কি করব? ঐ ক্লাস নতুন মেথড এড করে দেব? না। নতুন আরেকটি ক্লাস নেব যেখানে শুধু ডাটা স্টোর করা রিলেটেড লজিকগুলো থাকবে।
লিখে ফেলা যাক ক্লাস গুলো –
class InvoicePrinter {
private Invoice $invoice;
public function __construct(Invoice $invoice): void {
$this->invoice = $invoice;
}
public function printToPDF() {
// PDF print login
}
public function printToDOC(){
// print to doc logic
}
}
class InvoiceStore {
Invoice $invoice;
public function __construct(Invoice $invoice): void {
$this->invoice = $invoice;
}
public function saveToDB(Database $database): void {
// Store Data to database
}
}
এখন আমাদের ক্লাসগুলো সিঙ্গেল রেসপন্সিবিলিটি প্রিন্সিপাল মেনে ডিজাইন করা হয়েছে। ভবিষ্যতে কোন ক্লাস মডিফাই করার দরকার পড়লে সব ক্লাসে হাত দিতে হবেনা, জাস্ট যে রিলেটেড লজিক মডিফাই করতে চাই সেই রিলেটেড ক্লাসটাতে মডিফাই করলেই যথেষ্ট।