source: https://www.udemy.com/course/solid-principles/

SOLID Principle (Part-2)

--

SOLID হচ্ছে Object-Oriented Programming এর গুরুত্বপূর্ণ ৫টি individual design pattern এর কম্বিনেশন, যা Robert Cecil Martin (যিনি uncle bob নামে পরিচিত) ২০০০ সালে তার Design Principles and Design Patterns নামক পেপারে উপস্থাপন করেন।

SOLID নিয়ে এটা আমার ২য় আর্টিকেল। প্রথম আর্টিকেল এখানে পাবেন।

এই পর্বে আমরা SOLID এর শেষ ৩ টি প্রিন্সিপাল নিয়ে আলোচনা করবো।

Liskov Substitution Principle (LSP)

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

Subclass/derived class should be substitutable for their base class.

বারবারা লিসকভের (Barbara Liskov) নামানুসারে এর নামকরণ করা হয়েছে। তিনি 1987 সালে এটি উপস্থাপন করেছিলেন। এখানে Substitution বলতে বুঝায়- প্রতিস্থাপন /কোনো কিছুর সাথে রিপ্লেস করা।

Liskov Substitution Principle বলে যে, কোনো object এর ইনস্ট্যান্স কে ওই object এর base/parent class এর ইনস্ট্যান্স দিয়ে রিপ্লেস করা যাবে এবং এজন্য existing code এর correctness বা বিশুদ্ধতা পরিবর্তন করতে হবেনা।

নিচের উদাহরণটি দেখুনঃ

এই কোডে দেখা যাচ্ছে, পেমেন্ট স্টেটাস জানার জন্য একটা abstract class আছে paymentStatusService নামে এবং এই ক্লাসে getStatus নাম একটা abstract মেথড আছে। বিভিন্ন পেমেন্ট গেটওয়ের জন্য আলাদা আলাদা class করা হয়েছে যারা paymentStatusService কে extend করে। যেহেতু abstract ক্লাসকে এক্সটেন্ড করলে তার abstract মেথডকে ইমপ্লিমেন্ট করতে হবে, সেহেতু সকল ক্লাস getStatus মেথড ডিফাইন করেছে। সবশেষে আমরা CreditCardPaymentStatus class এর instance তৈরী করেছি এবং getStatus মেথড কল করেছি credit card পেমেন্ট এর স্টেটাস জানার জন্য।

আর LSP বলতে চায়, আমরা CreditCardPaymentStatus class এর instance তৈরী করেছি, সেটা যেনো SonaliPaymentStatus এর instance দিয়ে রিপ্লেস করা যায়। কারণ যেহেতু CreditCardPaymentStatus এবং SonaliPaymentStatus দুইটি ক্লাসই একই base/parent class এর child.

কিন্তু আমাদের কোডে দেখা যাচ্ছে, আমরা এই রিপ্লেসমেন্ট করতে পারবোনা। কারণ, CreditCardPaymentStatus এর getStatus মেথড এর রিটার্ন টাইপ আর SonaliPaymentStatus এর getStatus মেথড এর রিটার্ন টাইপ এক নয়। যেহেতু এই রিটার্ন টাইপ নিয়ে পরবর্তীতে কোনো প্রসেসিং থাকতে পারে, সেহেতু এখানে শুধুমাত্র object এর instance রিপ্লেস করলেই হবেনা। এটাই LSP violation.

উল্লেখ্য, এখানে আমরা base ক্লাস দিয়ে উদাহরণ দিয়েছি, তবে interface এর implementation এর ক্ষেত্রেও একই বিষয় প্রযোজ্য। একটা interface কে ইমপ্লিমেন্ট করা সকল ক্লাস ও পস্পরকে প্রতিস্থাপনযোগ্য হতে হবে।

নিম্নোক্ত শর্তগুলো অনুসরণ করলে আমরা LSP violation এড়িয়ে চলতে পারবোঃ

  • sub-class গুলোতে base-class এর মেথড ব্যবহার করলে সেক্ষেত্রে মেথড এর input parameter type এবং সংখ্যা অবশ্যই base-class এর মেথড এর সমান হতে হবে।
  • মেথড এর রিটার্ন টাইপ একই ধরণের হতে হবে।
  • Exception টাইপ ও একই ধরণের হতে হবে।

Interface-Segregation Principle (ISP)

A client should not be forced to implement an interface that it doesn’t use.

এটা খুবই সহজ একটা principle. এর মানে হচ্ছে যে, কোনো class কে এমন কোনো interface ইমপ্লিমেন্ট করতে বাধ্য না করা, যে interface সে ব্যবহার করেনা অথবা যে interface এর মেথডগুলো সে ব্যবহার করেনা।

ISP বলতে চায় যে, আমাদের তৈরিকৃত interface হবে একদম client-specific এবং খুব বেশি generalized না। অর্থাৎ, interface হবে ছোটো এবং client-specific.

একটা উদাহরণ দেখা যাকঃ

এখানে একটা PrinterInterface তৈরী করেছি যেটায় ৩টি মেথড আছে, print, photocopy, এবং scan. তারপর ৩টি printer এর জন্য ৩টি class ডিজাইন করা হয়েছে যারা সবাই PrinterInterface কে ইমপ্লিমেন্ট করেছে। যেহেতু interface ইমপ্লিমেন্ট করলে তার সবগুলো মেথড ইমপ্লিমেন্ট করতে হয়, সেহেতু printer ক্লাসগুলোও সবগুলো মেথড ইমপ্লিমেন্ট করেছে।

এখানে কোথায় ISP violation হয়েছে? দেখুন, আমাদের একটা printer আছে OldPrinter যেটাতে scan এবং photocopy এর ফিচার নেই কিন্তু তবুও OldPrinter class এই মেথডগুলো ইমপ্লিমেন্ট করতে বাধ্য। তেমনিভাবে, ModernPrinter ও বাধ্য হয়েছে scan মেথড ইমপ্লিমেন্ট করতে। যদিও এইসব মেথড ওদের প্রয়োজন নেই, কিন্তু interface এর rules এর কারণে তারা বাধ্য হয়েছে। এটাই Interface-Segregation Principle কে violate করেছে।

কিভাবে এর সমাধান করা যায়ঃ

এখানে আমরা আগের interface কে segregate করে small & client-specific interface হিসেবে ৩ ভাগে ভাগ করেছি। এর ফলে যে client এর যে interface প্রয়োজন সে তাকেই ইমপ্লিমেন্ট করবে। এবং কোনো client কেই অপ্রয়োজনীয় মেথড ইউজ করতে বাধ্য করা হবে না।

Dependency Inversion Principle (DIP)

High level modules should not depend on low-level modules, both should depend on abstractions.

Abstract should not depend on details. Details should depend on abstractions.

সহজে বলা যায় যে, সিস্টেম এর high-level code, low-level code এর উপর ডিপেন্ড করবে না। বরং দুইটাই ডিপেন্ড করবে abstraction এর উপর।

এখানে কিছু টার্মস বুঝার চেষ্টা করিঃ

Dependency: Class A যদি class B এর উপর বা কোনো ভ্যালুর উপর নির্ভর করে তবে class B অথবা ওই ভ্যালুই হলো class A এর ডিপেন্ডেন্সি।

High-level code, Low-level code: High-level code হচ্ছে সেসব যেগুলো আমরা সরাসরি কল করি এবং যেগুলো core business logic ধারণ করে। আর এসব High-level code কে যেসব code সাহায্য করে সেগুলো Low-level code।

Abstraction: Abstraction হলো কোনো concrete class অথবা method এর indirect representation. Abstraction বলতে সাধারণত Abstraction class এবং interface কে বুঝায়।

একটা উদাহরণ দেখা যাকঃ

এখানে আমরা login এর জন্য userLogin ক্লাস ডিজাইন করেছি এবং সেখানে login মেথড এর মাধ্যমে GoogleAuthenticationService দিয়ে ইউজারকে authenticate করছি। এক্ষেত্রে আমাদের high-level code হলো userLogin ক্লাস এবং low-level code হলো GoogleAuthenticationService এবং high-level code, low-level code এর উপর ডিপেন্ডেন্ট হয়ে আছে। এটাই DIP violation.

কিছুদিন পর যদি আমাদের অন্যকোনো athentication সার্ভিস প্রয়োজন হয় তাহলে কি করতে হবে? তখন আমরা আলাদা athentication সার্ভিস তৈরী করে userLogin ক্লাস এর login মেথডে সেই সার্ভিস ইউজ করবো, আর এটা কিন্তু SOLID এর OCP কেও violate করছে।

চলুন দেখি কিভাবে এ সমস্যার সমাধান করতে পারিঃ

যেহেতু DIP এর শর্ত হলো, high-level code এবং low-level code দুইটাই Abstraction এর উপর নির্ভর করবে, সেহেতু এখানে আমাদেরকে Abstraction (class, interface) ইমপ্লিমেন্ট করতে হবে। আমরা interface ব্যবহার করেছি।AuthenticationService নামে একটি ইন্টারফেস তৈরী করেছি যেখানে authenticate মেথড আছে। প্রতিটা login সার্ভিস এই ইন্টারফেস কে ইমপ্লিমেন্ট করবে এবং authenticate মেথডে তাদের functionality ডিফাইন করবে। তারমানে, আমাদের low-level code এখন Abstraction এর উপর ডিপেন্ডেন্ট হলো।

তারপর, আমাদের userLogin ক্লাস এর login মেথডে কিছু পরিবর্তন করেছি। এখানে AuthenticationService ইন্টারফেস কে ইঞ্জেক্ট করেছি। অর্থাৎ এই মেথডে আসার সময়েই প্যারামিটার হিসেবে AuthenticationService interface কে ইমপ্লিমেন্ট করেছে এমন কোনো class এর instance আসবে আর সেই instance এ authenticate মেথড ডিফাইন করা থাকবে।
তারমানে দেখা যাচ্ছে, আমাদের high-level code অর্থাৎ userLogin ক্লাস এখন কোনো concrete ক্লাস এর উপর নয় বরং একটা Abstraction (AuthenticationService) এর উপর ডিপেন্ডেন্ট।

নোটঃ

SOLID নিয়ে কাজ করার সময় আমাদের মাথায় রাখতে হবে যে, সলিড শুধুমাত্র কিছু practice, এগুলো কোনো আবশ্যক নিয়ম নয়। সবসময় বা সবজায়গায় সলিড apply করতে আমরা বাধ্য না। অনেক সময় দুই-একটা principle apply করলে আরো দুই-একটা automatic apply হয়ে যায়। SOLID এর Principle গুলো পরস্পরের সাথে সম্পর্কযুক্ত।

SOLID এর মূল উদ্দেশ্য হলো reusable এবং maintainableসফটওয়্যার ডিজাইন সেহেতু যেসব জায়গায় SOLID implement করলে আমাদের উদ্দেশ্য সফল হবে সেসব জায়গায় SOLID implement করা উচিত।

আশা করি SOLID এর প্রতিটি principle সম্পর্কে ব্যাসিক কিছু ধারণা দিতে পেরেছি। আমি মনে করি, আমার জানায় এবং লেখায় অনেক ভুল আছে। আপনাদের চো‍খে কোন ভুল ধরা পড়লে অবশ্যই অবশ্যই আমাকে জানাতে ভুলবেন না।

--

--