সিঙ্গেলটন ডিজাইন প্যাটার্ন - Singleton Design Pattern

সিঙ্গেলটন ডিজাইনপ্যাটার্ন - Singleton  Design  Pattern
৩০ ডিসেম্বর, ২০১৪, মঙ্গলবার।

আমার মনে হয়, সিঙ্গেলটন ডিজাইন প্যাটার্ন সবচেয়ে বেশি পরিচিত প্যাটার্ন। আমরা অন্য কোনো ডিজাইন প্যাটার্ন না জানলেও, সিঙ্গেলটন ডিজাইন প্যাটার্ন সম্পর্কে কম-বেশি সবাই জানি। তাই, আজকের পোস্ট খুব বেশি বড় করবো না।  

প্রথম প্রশ্ন: সিঙ্গেলটন ডিজাইন প্যাটার্ন কোন সমস্যার সমাধান করে, কখনই বা  ব্যবহার করতে হয় ?
উত্তরটা সহজ: যদি কখনো কোনো ক্লাস-এর একটাই অবজেক্ট থাকার দরকার হয়, তখন এই প্যাটার্ন ব্যবহার করা হয়।  এমন অবস্থার খুব পরিচিত একটা উদাহরণ হলো: যেকোনো সিস্টেমে সাধারণত ডাটাবেস কন্ট্রোলার অথবা ফাইল ম্যানেজমেন্ট কন্ট্রোলার একটাই দরকার হয়। এদের ইমপ্লিমেন্টেশন করা হয় সিঙ্গেলটন ডিজাইন প্যাটার্ন ব্যবহার করে।  

যেহেতু যেকোনো ক্লাস-এর অবজেক্ট তৈরী হয় ওই ক্লাস-এর constructor কল করে, সেহেতু প্রথমেই Constructor-টাকে প্রাইভেট করে দিতে হবে, যেন অন্য কেউ new কীওয়ার্ড ব্যবহার করে তাকে আর কল করতে না পারে। আর দ্বিতীয়ত, একটা পাবলিক Static  মেথড দিয়ে রাখতে হবে , যেমন, getInstance(), যাতে অন্যরা এই মেথড কল করে ক্লাস-এর একমাত্র অবজেক্টটাকে ব্যবহার বা এক্সেস করতে পারে। কোড-টা অনেকটা নিচের মত হবে:

public class SingletonExample {

       private static SingletonExample ref = null;
       
       public static SingletonExample getInstance() {
             if (ref == null) {
                 ref = new SingletonExample();
             }
             return ref;
       }

       private SingletonExample() {  // … more code }
}

ব্যস, হয়ে গেল।  আপনার কোড যদি সব সময়ই সিঙ্গেল থ্রেডেড (Single Treaded) হয়, তাহলে বোধহয় আর পড়ার দরকার নেই, উপরের কোড-ই যথেষ্ট। কিন্তু আপনার কোড যদি মাল্টি-থ্রেডেড হওয়ার সম্ভাবনা থাকে, তাহলে এই কোডে কাজ নাও হতে পারে। ব্যাপারটা বুঝতে হলে আপনাকে এই পোস্টের বাকি অংশটুকুও পড়তে হবে।  

একবার ভেবে দেখুন তো, মাল্টি-থ্রেডেড কোডে দুটো থ্রেড যদি একই সাথে প্রথমবারের মত getInstance মেথড কল করে, তাহলে কী হবে?

ঠিকই ধরেছেন, দুটো থ্রেড-এর জন্যই ‘ref ‘ null  হবে, এবং দুজনেই আলাদা ভাবে “new SingletonExample ()” লাইনটি excute করবে বা চালিয়ে দেবে, ফলে পুরো সিস্টেমে SingletonExample ক্লাস-এর একটির বদলে দুইটি অবজেক্ট তৈরী হয়ে যাবে -- সিঙ্গেলটন আর থাকবে না !!

মাল্টি-থ্রেডেড প্রোগ্রামিংয়ের  এটা একটা খুবই পরিচিত সমস্যা। এর সমাধানও জাভা  প্রোগ্রামিং লাঙ্গুয়েজেই দেয়া আছে, আর তা হচ্ছে ‘synchronized’ কীওয়ার্ড ব্যবহার করা।  অর্থাৎ, আমরা যদি getInstance মেথডের আগে synchonized কীওয়ার্ড লিখে দেই তাহলে একবারে শুধুমাত্র একটি থ্রেড-ই মেথডটির এক্সেস পাবে। সেক্ষেত্রে, প্রথম থ্রেড-এর কাছেই ‘ref ‘ কেবল null  থাকবে, সে new SingletonExample()  কল করে অবজেক্ট তৈরী করে রেখে দিবে, আর পরে যেই-ই আসুক, সে আগেই তৈরী করে রাখা অবজেক্ট-এর রেফারেন্স পাবে।  ‘synchronized’ কীওয়ার্ড ব্যবহারে কোড-এর চেহারা নিচের মত হবে :

public class SingletonExample {

       private static SingletonExample ref = null;
       
       public static synchronized SingletonExample getInstance() {
             if (ref == null) {
                 ref = new SingletonExample();
             }
             return ref;
       }

       private SingletonExample() {  // … more code }
}

এখানে বলে রাখি, উপরের কোডের এই ধরনকে বলা হয় ‘Lazy Initialization‘ (বাংলায় কী একে ‘অলস সুচনা’ বলবো ;-) ?) - শুরুতেই অবজেক্ট তৈরী না করে, যখন দরকার তখন তৈরী করা আর কী !! খেয়াল করে দেখুন, কেউ getInstance মেথড কল না করা পর্যন্ত কিন্তু অবজেক্ট তৈরী হবে না।  

আমাদের শেষ কোডে এমনিতে কোনো ভুল নেই, কিন্তু পারফরমেন্স-এর বিচারে এই কোড ভালো না।  কারণ, synchronized কীওয়ার্ড ব্যবহার করা  বা সিন্ক্রনাইজেশন একটা ব্যয়-বহুল পদ্ধতি বা এক্ষ্পেন্সিভ প্রসেস (Expensive process)। আর খেয়াল করে দেখুন, আমাদের এই  সিন্ক্রনাইজেশন কিন্তু শুধুমাত্র একবারই দরকার (এক্কেবারে শুরুতে) - এর পর আর দরকার নেই।  কিন্তু আমাদের কোড -এ প্রতিবার  getInstance মেথড কল করা হলেই (আমরা না চাইলেও)  সিন্ক্রনাইজেশন ব্যবহার হবে।  

এই সমস্যা সমাধানের উপায়?

উপায় হচ্ছে, Eager Initialization - বা আগে থেকেই অবজেক্ট তৈরী করে রাখা। যেমন:

public class SingletonExample {
       private static SingletonExample ref = new SingletonExample();
       public static SingletonExample getInstance() { return ref; };
       private SingletonExample() { // … more code };
}

উপরের কোডে প্রথমেই ref -এ নতুন SingletonExample  ক্লাস-এর অবজেক্ট তৈরী করে রেখে দেয়া হয়েছে। এভাবে স্ট্যাটিক ইনিশিয়ালাইজেশনের কারণে ক্লাস লোডের সময় প্রথমেই অবজেক্ট তৈরী হয়ে যাবে। এই পদ্ধতি থ্রেড-সেফ, অর্থাৎ দুটো থ্রেড একসাথে এই কোড execute  করতে বা চালাতে পারবে না।  জাভা স্পেসিফিকেশন -এর ১২.৪ চ্যাপ্টার-এ এ ব্যাপারে বলা আছে।

ভুল করে আমাদের ক্লাস-এর অন্য কোথাও যেন আবার ‘ref’ কে null  বানিয়ে দেয়া না হয় (যেহেতু তা private, আমাদের নিজেদের ক্লাস-এর অন্য মেথড কিন্তু ref কে চাইলেই এক্সেস করতে পারবে, পরিবর্তনও করতে পারবে), তা নিশ্চিত করতে উপরের কোডে final কীওয়ার্ড ব্যবহার করা হয় :

public class SingletonExample {
       private static final SingletonExample ref = new SingletonExample();
       public static SingletonExample getInstance() { return ref; };
       private SingletonExample() { // … more code };
}

কোনো ভ্যারিয়েবল-এর আগে final  কীওয়ার্ড ব্যবহার করা মানেই হচ্ছে কম্পাইলার-কে বলে দেয়া যে, “একে এর মান দিয়ে দিচ্ছি, ভুলেও কিন্তু আর যেন কেউ তা পরিবর্তন করতে না পারে, খেয়াল থাকে যেন !”- এর পর অন্য কেউ এর মান, ইচ্ছা-অনিচ্ছাকৃত ভাবে পরিবর্তন করতে চাইলেই ‘ধরা’ মানে কম্পাইলার এরর খাবে !

শেষ কথা:
জাভা তে “Reflection Attack” করে রানটাইম-এ কোনো অবজেক্ট-এর প্রাইভেট  মেথড কল করা যায় !! কাজেই কেউ চাইলে প্রাইভেট Constructor কে কল করেও ফেলতে পারে। এতে করে আরেকটা অবজেক্ট তৈরী হয়ে যাবে! এর থেকে মুক্তি পেতে আমাদের প্রাইভেট Constructor -এ একটা চেক বসিয়ে দেয়া যায় :

// ….
private SingletonExample() {
       if (ref != null ) throw new IllegalStateException();
       //  … more code
}
//

কেউ যদি আমাদের প্রাইভেট constructor  কল করে তাহলে এক্সেপশন খাবে। কারণ আগে থেকেই আমাদের অবজেক্ট তৈরী করা থাকায়  ref ভ্যারিয়েবল-এ আর  null থাকবে না, তাই না?

আমাদের সর্বশেষ কোড-কে আরেকটু ছোট করা যায়, getInstance মেথড পুরোটাই আসলে গায়েব করে দেয়া যায় :

public class SingletonExample {
       public  static final SingletonExample ref = new SingletonExample();
       private SingletonExample() {
             if (ref != null ) throw new IllegalStateException();
             //  … more code
        }
}

দুটোই একই কাজ করে।  একটায় আলাদা মেথড getInstance আছে, আরেকটায় নেই।  

সর্বশেষ (শোনা) কথা !! - আমি এই ব্যপারে খুব একটা জানি না, তাই বাকিটুকু নিজ দায়িত্বে পড়ে নেবেন :

জাভা ১.৫ থেকে enum -এর ব্যবহার শুরু হয়েছে। enum ব্যবহার করে খুবই ছোট করে পুরো সিঙ্গেলটন ডিজাইন প্যাটার্ন লিখে ফেলা যায়।  নিচের কোড-এর বাইটকোড (bytecode) অনেকটাই  আমাদের  উপরের লেখা ছোট কোড-এর মত হবে! - যা কিনা সিঙ্গেলটন ডিজাইন প্যাটার্ন।  

public enum SingletonExample {
      ref;
}

এই enum  ব্যবহার করলে নাকি জাভা রিফ্লেকশন এট্যাক সহ অবজেক্ট সিরিয়ালাইজেশন -ডিসিরিয়ালাইজেশন (Serialization - deserialization) সমস্যারও সমাধান হয়।   রেফারেন্স হিসাবে জাভা লাঙ্গুয়েজ স্পেসিফিকেশন-এর চ্যাপ্টার ৮.৯ আর যথারীতি জশুয়া ব্লক-এর বইয়ের আইটেম ৩ ও ৭৭  দ্রষ্টব্য। আর এই ব্লগটিও আমার কাছে বেশ ভালো মনে হয়েছে। আপনার যদি আরো ভালো কোনো রেফারেন্স জানা থাকে, তাহলে নিচে কমেন্টে লিখে দেবেন দয়া করে।  

[আপডেট: ২০১৬ তে এসে আরেকটা আর্টিকেল পড়ে ব্যাপারটা আমার অনেকটুকুই পরিষ্কার হয়েছে। Enum -এর উপর ঐ লেখা পড়ে আমি আরেকটা ব্লগ পোস্ট লিখেছি। এই লিংকে গিয়ে পোস্টটা পড়ে নিতে পারেন।]

ক্রেডিট দিয়ে শেষ করি :
কোড সহ অনেক ব্যাখ্যাই আমি আমার সুপারভাইসর  ড. ক্রিস্টফ চালনার -এর স্লাইড থেকে নিয়েছি।
ধন্যবাদ। -- ইশতিয়াক হোসেন, সিএসই , ডিইউ , সপ্তম ব্যাচ।

POM or TAP design pattern for test automation using Selenium WebDriver

POM or TAP design pattern for test automation using Selenium WebDriver August 21, Monday, 2017 I've written the same topic in Bangl...