Friday, July 28, 2017

ইনভার্শন অব কন্ট্রোল কী (What is Inversion of Control)?

আমরা বাস্তব জগেতে যখন কোনো কিছু তৈরি করি, তখন কাজগুলোকে ছোট ছোট বিভিন্ন উপাদানে বিভক্ত করে ফেলি। যেমন- গাড়ির ক্ষেত্রে - চাকা, স্টিয়ারিং, chassis, দরজা, ইত্যাদি। এগুলো আরও ছোট ছোট অংশে বিভক্তর করে এদেরকে আলাদা আলাদা ভাবে তৈরি করা হয় এবং পরে একসঙ্গে জুড়ে দেওয়া হয়। 

একইভাবে অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিংয়ের মূল ব্যপার হলো বাস্তব জগতের সঙ্গে সাদৃশ্য রেখে কোনো সমস্যা সমাধান করা। এতে অনেকগুলো অবজেক্ট থাকা যারা নিদির্ষ্ট কোনো কাজ করে এবং এগুলোকে একত্রে জুড়ে দিয়ে একটি বড় অবজেক্ট তৈরি করা হয়। প্রত্যেকটি অবজেক্ট একটি বাস্তব জগতের কোনো কিছুর প্রতিরূপ। Airplane অবজেক্ট একটি উড়োজাহাজকে নির্দেশ করতে পারে, সেভাবে একটি Purchase অবজেক্ট একটি বই কেনার বিভিন্ন ডেটা যেমন- দাম, বই অবজেক্টের রেফারেন্স, কত কপি ইত্যাদি নির্দেশ করতে পারে। 

একটি অবজেক্টকে সঠিকভাবে কাজ করতে হলে অনেক সময়ই অন্য এক বা একাধিক অবজেক্টের উপর নির্ভর করতে হয়। যে অবজেক্টের উপর নির্ভর করে কোনো কাজ করতে হয় তাকে অন্য অবজেক্টটির ডিপেডেন্সি বলা হয়। অর্থাৎ A কে কাজ করতে হলে যদি B এর উপর নির্ভর করতে হয়, তাহলে B, A এর ডিপেডেন্সি।

এখন মনে করুন, আপনি একটি ইমেইল অ্যাপ্লিকেশন লিখবেন। এই অ্যাপ্লিকেশনের বেশ কতগুলো অংশ থাকতে পারে।

এক- একটি টেক্সট এডিটরের প্রয়োজন হবে, যা একটি অবজেক্ট। 
দুই- আপনি যে টেক্সটটি লিখবেন, সেটিকে নির্দেশ করতে একটি অবজেক্ট প্রয়োজন হবে।
তিন- ইমেইল সেন্ড করার আগে অবশ্যই আপনি বানানগুলো ঠিক করে নিতে চান, সেক্ষেত্রে বানান শুদ্ধিকরণ একটি অবজেক্টের প্রয়োজন হবে। 
চার- আপনাকে ইমেইলটি পাঠানোর জন্য ইমেইল অ্যাড্রেস প্রয়োজন। এগুলোর জন্য একটি অ্যাড্রেসবুক অবজেক্টের প্রয়োজন হবে, যেখানে আপানার সব ইমেইল অ্যাড্রেসগুলো রয়েছে। 
পাঁচ- একটি ইমেইলার অবজেক্ট যে কিনা ইমেইলটি ইন্টারনেট ব্যবহার করে গন্তব্যে পৌঁছানোর কাজটি করবে। 

ইত্যাদি ..।

তাহলে দেখা যাচ্ছে এই ইমেইলার অবজেক্টটি টেক্সট এডিটর, একচুয়াল টেক্সট, স্পেলচেকার এবং অ্যাড্রেসবুকের উপর নির্ভর করে। এগুলো ইমেইলার অবজেক্টের ডিপেডেন্সি। 

উদাহরণের সুবিধার্থে এবং মূল বিষয়টি সংক্ষিপ্তভাবে বোঝার জন্য মনে করুন, আপনার দুটি অবজেক্ট রয়েছে, একটি ইমেইলার অবজেক্ট যাতে একটি সেন্ড মেথড রয়েছে। আপনি ইমেইল সেন্ড করার আগে বানানগুলো ঠিক করে নিতে চান, সে জন্য প্রয়োজন SpellFixer অবজেক্ট। যেমন- 

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Emailer { 
    private SpellFixer spellFixer; 

    public Emailer() { 
        this.spellFixer = new SpellFixer(); 
    } 

    public void send(String text, String emailAddress) { 
        String fixedText = spellFixer.fixSpelling(text); 

        System.out.println("sending email to: " 
+ emailAddress + " with following text: " + fixedText); 
    } 
}

public class SpellFixer { 

    public String fixSpelling(String text) { 
        //business logic 
        //bla bla 

        return text; 
    } 
} 

এখন আপনি ইমেইলার ক্লাসটির অবজেক্ট তৈরি করে ইমেইল সেন্ড করতে পারবেন। 

1
2
Emailer emailer = new Emailer(); 
emailer.send("bla bla bla", "contact@bazlur.com");


উপরের এই প্রক্রিয়ায় কোনো সমস্যা নেই। এটি একটি সঠিক উপায়। 

তবে আমরা সব সময় যে ইংরেজিতে ইমেল লিখবো, এমনটি হওয়ার কোনো কারণ নেই। কখনো বাংলা বা অন্য কোনো ভাষাতে লিখতে পারি। সেক্ষেত্রে আমাদের এই উপরের সমাধানটি কাজ করবে না। ধরা যাক আমরা বাংলাতে একটি ইমেইল লিখতে চাই এবং এক্ষেত্রে একটি বাংলা স্পেলফিক্সার তৈরি করতে হবে। এক্ষেত্রে - 


1
2
Emailer emailer = new Emailer(); 
emailer.send("bla bla bla", "contact@bazlur.com");

তবে এটি সরাসরি আমাদের ইমেলারে প্লাগ করার কোনো উপায় নেই। যদিও বা আমরা করতে চাই, সেক্ষেত্রে আমাদের নতুন করে আরেকটি ইমেইলার তৈরি করতে হবে -

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class BanglaEmailer { 
    private BanglaSpellFixer spellFixer; 

    public BanglaEmailer() { 
        this.spellFixer = new BanglaSpellFixer(); 
    } 

    public void send(String text, String emailAddress) { 
        String fixedText = spellFixer.fixSpelling(text); 

        System.out.println("sending email to: " + emailAddress + " with following text: " + fixedText); 
    } 

    public static void main(String[] args) { 
        BanglaEmailer banglaEmailer = new BanglaEmailer(); 
        banglaEmailer.send("bla bla bla", "contact@bazlur.com"); 
    } 
} 

যদিও আমাদের ইমেইলার একইরকম, শুধুমাত্র বানান শুদ্ধ করার পক্রিয়া আলাদা হওয়ায় আমাদের দুটি অবজেক্ট তৈরি করতে হচ্ছে। এভাবে যদি আমাদের আরও অন্যান্য ভাষার জন্য লিখতে হয়, তাহলে পক্রিয়াটি আরও কঠিন হয়ে যায়। এছাড়াও এই সমাধানে বেশ কতগুলো সমস্যা রয়েছে -

১. এতে ইমেলারের সাথে স্পেল চেকার খুব টাইটলি কাপলড। এরা আলাদা আলাদাভাবে কাজ করতে পারে না। বাস্তব জগতের প্রত্যেকটি বস্তুই স্বাধীন। সুতরাং বাস্তব জগতের সঙ্গে সাদৃশ্য রইল না। 
২. আলাদা আলাদা স্পেল ফিক্সারের জন্য আলাদা ইমেইলারের প্রয়োজন হচ্ছে,এবং আলাদাভাবে কোড লিখতে হচ্ছে যাতে কোড ডুপ্লিকেশন হচ্ছে। 
৩. আমরা যদি দুটি অবজেক্টকে আলদা আলাদাভাবে টেস্ট করতে চাই, তা সম্ভব হয়ে উঠছে না। এখানে যদিও স্পেল চেকারকে আলাদা করে টেস্ট করা গেলেও, ইমেইলার টেস্ট করতে হলে স্পেল চেকারের অবজেক্ট প্রয়োজন। 
৪. এখানে দুইজন ডেভেলপার বা প্রোগ্রামার আলাদা আলাদাভাবে দুটি ক্লাস নিয়ে কাজ করতে পারবে না। 

এই উপরের সমস্যাগুলো সমাধান করার উপায় রয়েছে- 

নিচের কোডগুলো খেয়াল করুন- 

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public interface SpellFixer { 

    String fixSpelling(String text); 
} 

public class EnglishSpellChecker implements SpellFixer { 
    @Override 
    public String fixSpelling(String text) { 
        // business logic for english spell checker 

        return text; 
    } 
} 

public class BanglaSpellFixer implements SpellFixer { 
    public String fixSpelling(String text) { 
        // business logic for bangla spell fixer 

        return text; 
    } 
} 


public class Emailer { 
    private SpellFixer spellFixer; 

    public void setSpellFixer(SpellFixer spellFixer) { 
        this.spellFixer = spellFixer; 
    } 

    public void send(String text, String emailAddress) { 
        String fixedText = spellFixer.fixSpelling(text); 

        System.out.println("sending email to: " + emailAddress + " with following text: " + fixedText); 
    }  
} 

এখানে প্রথমে একটি ইন্টারেফস তৈরি করা হয়ছে। তারপর দুটি আলাদা ইমপ্লিমেন্টেশন লেখা হয়েছে।

পরবর্তীতে ইমেলার ক্লাসটিতে একটি সেটার মেথড দিয়ে স্পেল চেকারটি ইনজেক্ট করা হয়েছে। অর্থাৎ আমরা কোন স্পেল ফিক্সারটি ব্যবহার করবো তা নির্ভর করছে ইমেলার ইনস্ট্যানসিয়েট করার পর। 


1
2
3
4
5
6
7
8
        SpellFixer spellFixer = new EnglishSpellChecker();
        Emailer emailer = new Emailer();
        emailer.setSpellFixer(spellFixer);
        emailer.send("bla bla bla", "contact@bazlur.com");

        BanglaSpellFixer banglaSpellFixer = new BanglaSpellFixer();
        emailer.setSpellFixer(banglaSpellFixer);
        emailer.send("আমি বাংলার গান গাই", "contact@bazlur.com");

এতে করে অনেকগুলো সুবিধা হলো - 

১. স্পেল ফিক্সার একাধিক থাকলেও ইমেইলারে অবজেক্ট একই থাকছে। 
২. এখানে কোনো কোড ডুপ্লিকেশন নেই।
৩. আমি যখন ইমেইলার টেস্ট করবো তখন যদি কোনো স্পেল চেকার না থাকে,তাহলে একটি মক (Mock) স্পেল চেকার দিয়ে আমার কাজ করে নিতে পারবো। এতে টেস্টিং প্রক্রিয়া অনেক সহজ হয়ে যায়। 
৪. আলদা আলদাভাবে স্পেল চেকার ও ইমেলার তৈরি করা সম্ভব হয়ে যাচ্ছে। 

নিচে একটি টেস্টকোড দেওয়া হলো -

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class EmailerTest { 

    @Test 
    public void testSend() throws Exception { 
        SpellFixer spellFixer = Mockito.mock(SpellFixer.class); 

        String text = "hello world"; 

        when(spellFixer.fixSpelling(text)) 
                .thenReturn(text); 

        Emailer emailer = new Emailer(); 
        emailer.setSpellFixer(spellFixer); 

        emailer.send(text, "contact@bazlur.com"); 
    } 
}

এখানে টেস্টিং ফ্রেমওয়ার্ক JUnit ও Mockito ব্যবহার করা হয়েছে। উপরের টেস্ট কোডটি যদিও কোনো অর্থপূর্ণ টেস্ট করে না, কিন্তু বোঝার জন্য দেওয়া হয়েছে। এখানে SpellFixer ইন্টারফেস ব্যবহার না করে বরং একে মক কর হয়েছে। অর্থাৎ এই পদ্ধতি ব্যবহার করলে যেকোনো ক্লাসকে স্বাধীনভাবে টেস্ট করা যায় এবং টেস্টেবল কোড লেখা যায়। 

উপরের কোডটিতে সেটার ব্যবহার করে অবজেক্ট ইনজেক্ট করা হয়েছে। তবে যদি আমরা কোনো বিশেষ কন্টেইনার যেমন- Spring, Google Guice, picoContainer ইত্যাদি ব্যবহার করে প্রক্রিয়াটি আরও সহজ করে ফেলা যায়। 

1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Emailer { 
    @Autowired 
    private SpellFixer spellFixer; 

    public void send(String text, String emailAddress) { 
        String fixedText = spellFixer.fixSpelling(text); 

        System.out.println("sending email to: " + emailAddress + " with following text: " + fixedText); 
    } 
}


উপরের কোডটিতে শুধু একটি অ্যানোটেশন ব্যবহার করা হয়েছে। এই ইমেইলার ব্যবহার করতে হলে SpellFixer এর ইমপ্লিমেন্টেশনের কোনো ইনস্ট্যান্স নিউ অপারেটর ব্যবহার করে তৈরি করার প্রয়োজন নেই। এখানে স্প্রিং কন্টেইনারের ইনস্ট্যান্স তৈরি করে ইনজেক্ট করে দেয় যখন প্রয়োজন হয়। এতে করে কোডটি আগের থেকে অনেক সংক্ষিপ্ত হয়। 

এই প্রক্রিয়াকে ইনভার্শন অব কন্ট্রোল(Inversion of Control) বলা হয়। অর্থাৎ এটি ব্যবহার করে অবজেক্ট ইনজেক্ট করার কন্ট্রোলটি আমরা একটি কন্টেইনারক দিয়ে দেওয়া হয়। 

এখানে দুটি বিষয় লক্ষ্য করা যায় - 

১. কী করতে চাই
২. কখন করতে চাই

কী করতে চাইকে কখন করতে চাই থেকে আলাদা করে ফেলে আমরা একটি কন্টেইনারকে সেই দায়িত্ব দিয়ে দিতে পারি। কন্টেইনার সিন্ধান্ত নেবে কখন কোন অবজেক্ট ইনস্ট্যানসিয়েট করতে হবে । এই কন্ট্রোলকে আমাদের হাত থেকে কন্টেইনারকে দিয়ে দেওয়ার প্রক্রিয়াকে ইনভারর্সন অব কন্ট্র্রোল বলা হয়।