Monday, December 3, 2018

Interface কেন ব্যবহার করা হয়?


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

জাভা নিয়ে আমরা যারা কাজ করি তারা প্রায়ই শুনে থাকি বা দেখে থাকি যে কোন একটা কাজ করতে গেলে কোন একটা class কে নির্দিষ্ট কোন একটা interface কে implement করতে হয়। এমনটা কেন করতে হয়? যেমন ধরুন multi-threaded সিস্টেম নিয়ে কাজ করতে গেলে আমাদের আলাদা আলদা Thread কে অবশ্যই Runnable ইন্টারফেরটিকে implement করতে হয় বা Thread ক্লাসকে extend করতে হয়। আসলে Thread ক্লাস নিজেই Runnable ইন্টারফেসের implement করে তাই আপনি যখন Thread ক্লাসকে extend করছেন তখন Runnable কেও implement করে ফেলছেন। কিন্তু কথা হল আপনাকে Thread তৈরি করতে হলে কেনই বা Runnable ইন্টারফেসকে ইমপ্লিমেন্ট করতে হবে? সেটা না করলে কি কোনভাবেই চলবে না? এই বিষয়টি নিয়েই আমরা পাগলামি করব। তবে চলুন শুরু করা যাক।

ধরুন আপনি একটা বই কিনবেন, তো বই কিনতে গেলে আপনি অবশ্যই বইয়ের দোকানে যাবেন, মুদি দোকানে যাবেন না। কারন আপনি খুব ভালভাবেই জানেন যে মুদি দোকানে বই পাওয়া যায়না। বইয়ের দোকানে যে বইটি খুজছেন সেটি পাওয়া যাক বা না যাক সেখানে বই পাওয়া যায় সুতরাং আপনার কাঙ্খিত বইটি পাওয়ার সম্ভাবনা রয়েছে। ঠিক এই কাজটিই করে interface.
Interface কি এবং সেটি কিভাবে তৈরি করতে হয় সেটি আপনারা জানেন এই ধারনা করেই সামনে এগোব। 
 
ধরলাম আমরা চাই এমন একটা ক্লাস তৈরি করতে যে ক্লাস আমাদের বলে দিবে কোন একটি প্রানীর height এবং weight এর অনুপাত সঠিক আছে কি না। তো আমরা একটা কাজ করি। জাভা নিয়ে কাজ করতে গেলে তো আমরা কথায় কথায় ক্লাস বানাই। এখানেও একটা ক্লাস বানিয়ে ফেলি।

কোডঃ

public class Human {
    private double height;

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }
}

খেয়াল করুন এখানে Human ক্লাসে height নামেএ একটি ফিল্ড রয়েছে যেখানে আমরা ডিফাইন করে দিতে পারি কোন একটা মানুষের উচ্চতা কত। কিন্তু হাইট এবং ওয়েটের অনুপাত ঠিক আছে সেটা জানতে হলে তো আমাদের ওয়েটও লাগবে! সেটা তো নাই। ওকে এবার তাহলে কোডে কিছুটা মডিফিকেশন আনি।

কোডঃ

public class Human {
    private double height;
    private double weight;

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }
}

ওয়েল এবার আমাদের ক্লাসের মাঝে weight ফিল্ডটিও আছে এবং সেটার getter মেথডও আছে যার মাধ্যমে আমরা ওয়েটের ভ্যালু রিড করতে সক্ষম। তবে চলুন এবার আমরা সেই ক্লাসটি তৈরি করে ফেলি যে ক্লাসটি আমাদের এই Human এর হাইট এবং ওয়েটের অনুপাত ঠিক আছে কি না সেটা বলে দিবে।

কোডঃ

public class WeightMeasurement {
    public String getMeasurement(Human human) {
        if (human.getHeight() <= 0D || human.getWeight() <= 0D) {
            return null;
        }
        if (human.getWeight() / human.getHeight() == 11) {
            return "perfect";
        } else if (human.getWeight() / human.getHeight() < 11) {
            return "under-weight";
        } else {
            return "over-weight";
        }
    }
}

তাহলে আমরা এমন একটি ক্লাস তৈরি করে করলাম যে ক্লাসে getMeasurement নামে একটা মেথড আছে যে Human ক্লাসের অবজেক্ট নেয় প্যারামিটার হিসাবে এবং String টাইপের তিনটি ভ্যালু রিটার্ন করে।পুরো কোডটি নিচের মত।

কোডঃ

public class Main {
    public static void main(String[] args) {
        Human rahim = new Human();
        rahim.setHeight(5.5);
        rahim.setWeight(67);
        Human karim = new Human();
        karim.setHeight(5.6);
        karim.setWeight(59);
        WeightMeasurement measurement = new WeightMeasurement();
        System.out.println("Rahim -> "+measurement.getMeasurement(rahim));
        System.out.println("Karim -> "+measurement.getMeasurement(karim));
    }
}

প্রোগ্রামটিকে রান করলে আমরা নিচের মত আউটপুট দেখতে পাবঃ
Rahim -> over-weight
Karim -> under-weight

এখন কথা হল আমরা যে কেবল Human বা মানুষের ওয়েট মাপামাপি করব তেমটা তো না। এমন তো হতে পারে আমরা অন্য প্রানীর ক্ষেত্রেও এমন কাজ করতে চাই। তাহলে প্রতিটা ক্লাসের জন্য আলাদা আলাদা টাইপ দিয়ে কি আমরা WeightMeasurement ক্লাসে মেথড ওভারলোড করব? উহু, এখানেই আসবে interface এর কেরামতি। আমরা এমন একটি interface বা টাইপ বানাব যার মাঝে প্রয়োজনীয় সেইসব মেথড থাকবে যেগুলা আমাদের WeightMeasurement ক্লাসে প্রয়োজন হবে কিন্তু সেগুলার কোন ইমপ্লিমেন্টেশন থাকবে না ওই ইন্টারফেসে। যে ক্লাসই তাকে implement করবে সে ওইসব মেথড override করবে। ওকে তাহলে আমরা তেমন একটা ইন্টারফেস তৈরি করে ফেলি।

কোডঃ

 public interface Animal {
    public double getHeight();
    public double getWeight();
}

অর্থাৎ Animal একটি ইন্টাফেস যাকে যে ক্লাসই ইমপ্লিমেন্ট করুক না কেন উক্ত ক্লাসকে  getHeight এবং getWeight ক্লাসকে ইমপ্লিমেন্ট করতেই হবে। যদি ক্লাস abstract হয় তবে করতে(ই) হবে না। এখন আমরা যদি WeightMeasurement ক্লাসে এমন মডিফিকেশন করি যেঃ

কোডঃ

public class WeightMeasurement {
    public String getMeasurement(Animal animal) {
        if (animal.getHeight() <= 0D || animal.getWeight() <= 0D) {
            return null;
        }
        if (animal.getWeight() / animal.getHeight() == 11) {
            return "perfect";
        } else if (animal.getWeight() / animal.getHeight() < 11) {
            return "under-weight";
        } else {
            return "over-weight";
        }
    }
}

উহু, তবু একটু খটকা থেকে গেল। মানুষ ৫ ফুট ৬ ইঞ্চি হলে যদি ৬০ কেজি ওজন হয় তবে ৫ ফুট ৬ ইঞ্জি উচ্চতার একটা গরুর ওজন তো কয়েক মন ছাড়িয়ে যাওয়া কথা। ৫ ফুট ৬ ইঞ্জি উচ্চতার গরুর যদি ৬০ কেজি ওজন হয় তবে সেটা আর দেখতে হবে না কেবল হাড় আর চামড়াই থাকবে। তাহলে কি করা যায়? আলাদা আলাদা প্রানীর আলাদা আলদা রেশিও থাকবে। ওকে তাহলে আমরা সেটা আমাদের Animal ইন্টারফেসে উল্লেখ করে দেই।

কোডঃ

public interface Animal {
    public double getHeight();
    public double getWeight();
    public double getRatio();
}


Animal ইন্টারফেসের কাজ তো নয়হ হল কিন্তু  WeightMeasurement তে যে ছোট্ট একটা পরিবর্তন করতে হয়। দেখি সেটা কিভাবে করা যায়।

কোডঃ


class WeightMeasurement {
    public String getMeasurement(Animal animal) {
        if (animal.getHeight() <= 0D || animal.getWeight() <= 0D) {
            return null;
        }
        if (animal.getWeight() / animal.getHeight() == animal.getRatio()) {
            return "perfect";
        } else if (animal.getWeight() / animal.getHeight() < animal.getRatio()) {
            return "under-weight";
        } else {
            return "over-weight";
        }
    }
}

এখানে আমরা আলাদা আলাদা Animal এর জন্য উক্ত Animal এর ওয়েট রেশিও নিচ্ছি। এবার আর গরু আর মানুষের রেশিও এক হবে না যদি না আমরা পাগলামি করে বসি।

সব তো হল কিন্তু Human ক্লাসের কি দশা দেখি সেটা একবার দেখে আসি। Human ক্লাসকে আমরা চাচ্ছি Animal ক্লাসের চাইল্ড বানাতে মানে Human ইমপ্লিমেন্ট করবে Animal কে। দেখি সেই কোডটুকু কেমন।

কোডঃ

lass Human implements Animal{
    private double height;
    private double weight;

    @Override
    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    @Override
    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    @Override
    public double getRatio() {
        return 11D;
    }
}

এবার যদি আমরা আমাদের আগের কোডটুকু রান করি তবে অনায়াসেই রান করবে কাঙ্খিত ফলাফল দিবে। কিন্তু গরুর ক্ষেত্রেও যে কাজ করছে সেটা একটু পরখ করে দেখি।

কোডঃ

public class Cow {
    private double height;
    private double weight;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }
}

খেয়াল করে দেখুন Cow ক্লাসের হাইট এবং ওয়েট দুটিই আছে। সাথে একটি নতুন ফিল্ড অ্যাড হয়েছে name । name ফিল্ডে আমরা আমাদের প্রিয় গরুর নাম রেখে দিতে পারি, যেমন লালি, কালু যার গরুর যে প্রিয় নাম সেটা। ওকে চলুন তবে আমরা আমাদের কোডটি রান করি।

কোডঃ

public class Main {
    public static void main(String[] args) {
        Cow lali = new Cow();
        lali.setName("Lali");
        lali.setHeight(5.5);
        lali.setWeight(200);
        WeightMeasurement measurement = new WeightMeasurement();
        System.out.println(lali.getName()+" -> "+measurement.getMeasurement(lali));
    }
}

ওমা একি! কম্পাইলার একটি Exception থ্রো করেছে! "Unresolved compilation problem" নামে একটি এক্সেপশন। মানে হল আমরা getMeasurement মেথডে ইনপুট প্যারামিটারের টাইপ বলেছিলাম Animal টাইপ কিন্তু আমরা পাঠিয়েছি Cow টাইপ যেটা কম্পাইলার কম্পাইল টাইমে গুলিয়ে ফেলেছে। ওকে তাহলে আমরা একটা কাজ করি, টাইপ কাস্ট করে দেই দেখি কি হয়।

কোডঃ

public class Main {
    public static void main(String[] args) {
        Cow lali = new Cow();
        lali.setName("Lali");
        lali.setHeight(5.5);
        lali.setWeight(200);
        WeightMeasurement measurement = new WeightMeasurement();
        System.out.println(lali.getName()+" -> "+measurement.getMeasurement((Animal)lali));
    }
}

উহু, তবুও কাজ হলনা। এবার থ্রো করে "java.lang.ClassCastException"। ঘটনা কি! আসলে আমরা getMeasurement ডেফিনেটলি বলে দিয়েছিলাম সেটার টাইপ হবে Animal কিন্তু আমরা পাঠাচ্ছি Cow যেটা Animal নয় এবং যার মাঝে getRatio() মেথডটিও নাই। বা থাকলেও সে নিজে Animal তো নয়। তাই গরুটিকে এবার আমাদের প্রানী তথা Animal এর বাচ্চা বানাতে হবে। দেখি কোডটি কেমন।

কেডঃ

public class Cow implements Animal{
    private double height;
    private double weight;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    @Override
    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }
   
    @Override
    public double getRatio() {
        return 27D;
    }
}

এবার আমরা আমাদের গরুটিকে একটা প্রানী তথা Animal বলতে পারি। এবার যদি আমরা আগের কোডটিকে রান করি তবে দেখি কি আউটপুট আসেঃ
Lali -> over-weight

মানে কাজ করছে। আসলে আমরা কোন একটা ইন্টারফেস implement করি কোন একটা ক্লাসের type interference এর জন্য। আমরা নির্দিষ্ট করে বলে দিতে চাই যে কোন একটি ইন্টারফেস ইমপ্লিমেন্ট করতে গেলে যে ক্লাসে সেটা করা হবে সেখানে নির্দিষ্ট কিছু বিহেবিয়্যর বা মেথড অবশ্যই থাকবে যার উপর ভিত্তি করে আমরা জেনেরিক কোন একটি কাজ করতে পারব। যেমনটি আমি আগেই বলেছি Runnable ইন্টারফেসের ক্ষেত্রে। আপনারা যদি Runnable এর সোর্স কোড দেখি তবে সাদামাটা চেহারার public abstract void run(); মেথডটি দেখতে পারব। আসলে আমরা যখন কোন একটি থ্রেড রান করি তখন JVM খুজে বের করে কোথায় এই run মেথডটি ইমপ্লিমেন্ট করা হয়েছে এবং এই run মেথডটিকে সে কল করে। এজন্য আমাদের কোন একটি থ্রেড তৈরি করতে হলে run মেথডটি ওভাররাইড করতে হয়।

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

বহু ভুলত্রুটি থাকার পরেপ পাবলিশ করছি কারন আমি তো পাগল, পাগলে কি না বলে। আর কারো কোন কথায় পাগলের কি কোনদিন কিছু গায়ে লেগেছে? তবে কোন প্রশ্ন থাকলে অবশ্যই করতে পারেন।

Monday, May 22, 2017

এনক্যাপসুলেশন নিয়ে যত পাগলামী ॥ পাগলের প্রলাপ

অবজেক্ট অরিয়েন্টেড ল্যাঙ্গুয়েজের যে কয়েকটি স্বকীয় বৈশিষ্ট্য রয়েছে তার মধ্যে অন্যতম গুরুত্বপূর্ন এবং সবচেয়ে সহজবোধ্য বৈশিষ্ট্য হল এনক্যাপসুলেশন । আমিতো পাগল, আপনারা যেহেতু এটা পড়ছেন কিছুক্ষন পর আপনারাও পাগল হয়ে গেলে আমার কিছুই করার থাকবে না । তো চলুন আর কথা না বলে পাগলামী শুরু করি ।

আশাকরি জীবনে একবার হলেও অসুস্থ হয়েছেন । না হলে তো খুবই খুশির কথা । আর সেটা না হলেও ঔষধ হিসাবে খাওয়া হয় যে ক্যাপসুল সেটা নিশ্চয় দেখেছেন । তো এখন কিছুটা ক্যাপসুল নিয়ে কথা বলি ।

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

অবজেক্ট অরিয়েন্টেড প্রোগ্রামিং এর অন্যতম একটি প্রধান বৈশিষ্ট্য কিন্তু ঠিক এই কনসেপ্ট যেটি একটু আগেই ব্যাখ্যা করলাম । আমরা সবাই জানি অবজেক্ট অরিয়েন্টেড প্রোগ্রামিং এর কনসেপ্টে দুনিয়ার তাবৎ সবকিছুই অবজেক্ট । আপনি, আমি, লতা-পাতা, গাছ, পাখি, ফুল যত যা কিছু আমরা দেখতে পাই, দেখা যায়না, অনুভব করতে পারি, বা কল্পনা করতে পারি সবই অবজেক্ট । একটি অবজেক্টের (জাভার রেসপেক্টে) ৪ টি প্রধান কম্পোনেন্ট থাকে, (i) ব্লক (ii) কনস্ট্রাক্টর (iii) অ্যাট্রিবিউট (স্টেট) (iv) মেথড (বিহ্যেবিয়র) ।
(i) & (ii) এই দুইটি কম্পোনেন্ট আপাতত আমাদের দরকার হবেনা, যখন হবে তখন জেনে নেওয়া যাবে ।  তবে আমি আশাকরব যেহেতু আপনারা এই আর্টিকেলটি পড়তে এসেছেন তাই এদের কাজ হালকা পাতলা হলেও কিছুটা জানেন ।
(iii) অ্যাট্রিবিউটঃ অ্যাট্রিবিউট মানে হল ভ্যারিয়েবল । এখানে যে ভ্যারিয়েবলের কথা বলা হচ্ছে সেটিকে ফিল্ডও বলা হয় কারন এটি সরাসরি কনটেইনার ক্লাসের আন্ডারে ডিক্লেয়ার করা হয় । কোন মেথডের মাঝে ডিক্লেয়ার করা ভেরিয়েবলকে লোকাল ভ্যারিয়েবল বলা হবে এবং ওটা ভোলাটাইল । অ্যাট্রিবিউটকে অবজেক্টের স্টেটও বলা হয় । কারন একটি অবজেক্টের ইন্সট্যান্ট কন্ডিশন বা স্টেট ধারন করে বিধায় এর এরকম নামকরন ।
(iv) মেথডঃ যারা প্রোসিডিউরাল প্রোগ্রামিং এর সাথে পরিচিত তারা এটাকে ফাংশন হিসাবে চিনে থাকবেন হয়ত । মেথডের মাঝে বিভিন্ন অ্যাট্রিবিউট নিয়ে নাড়াচাড়া করা হয় । একই অ্যাট্রিবিউটের উপর আলাদা আলাদা মেথডের প্রভাব আলাদা আলাদা হয়ে থাকে । মানে একই অ্যাট্রিবিউটকে যদি ২ টি আলাদা মেথডের মাঝে প্যারামিটার হিসাবে পাস করা হয় তবে সেই মেথড ২ টি উক্ত অ্যাট্রিবিউটের সাথে একই রকম ভদ্র ব্যাবহার নাও করতে পারে, বেশিরভাগ ক্ষেত্রেই করেনা । এজন্য মেথডকে একটি অবজেক্টের বিহ্যেবিয়রও বলা হয় ।

পরিচিতি পর্ব শেষ , এবার পাগলের প্রলাপ শুরু করা যাক । আমরা সবাই জানি যে একটি অবজেক্টের ব্লুপ্রিন্ট হল ক্লাস । তার মানে একটি অবজেক্টের মাঝে কি কি স্টেট থাকবে এবং উক্ত স্টেট গুলার জন্য কি কি ধরনের বিহ্যেব (আচরন) করতে পারে অবজেক্টটি সেগুলা সব ডেসক্রাইব করা থাকে তার ক্লাসে ।

এখন কথা হল একটি ক্লাস লেখার সময় আমরা ৪ ধরনের অ্যাট্রিবিউট ডিক্লেয়ার করতে পারি তাদের অ্যাক্সেসিবিলিটির উপর নির্ভর করে । তার মানে উক্ত অ্যাট্রিবিউটটিকে কে কে বা কোন কোন জায়গা থেকে অ্যাক্সেস এবং মডিফাই করা সম্ভব । অ্যাক্সেসিবিলিটি কনট্রোল করার এই ৪ প্রকার মডিফায়ারকে বলা হয় অ্যাক্সেস মডিফায়ার । এদের নাম এবং কাজ সম্পর্কে নিচের টেবিলটি দেখলে বুঝতে পারব ।
এখানে দেখতে পারছি public মডিফায়ার ব্যাবহার করলে সেই অ্যট্রিবিউটটিকে যেকোন স্থান থেকে সরাসরি অ্যাক্সেস করা সম্ভব । আর private মডিফায়ার ব্যাবহার করলে কেবল উক্ত ক্লাসের মেফডগুলাতে সেটির অ্যাক্সেস পাওয়া সম্ভব । ছোট্ট একটা উদাহরন দেখি ।

কোডঃ
public class EncapTest {

    public int x;
    private int y;

    public void showX() {

        System.out.println("In EncapTest -> " + x);
    }

    public void showY() {

        System.out.println("In EncapTest -> " + y);
    }
}

public class Main {

    public static void main(String[] args) {

        EncapTest et = new EncapTest();
        et.x = 10;
//        et.y = 5; //This field is not accessible here outside from EncapTest class
        et.showX();
        et.showY();
        System.out.println("Outside from EncapTest -> " + et.x);
//        System.out.println("Outside from EncapTest -> " + et.y); // Not valid
    }
}

আউটপুটঃ
In EncapTest -> 10
In EncapTest -> 0
Outside from EncapTest -> 10

উপরোক্ত কোডটিতে আমরা 'EncapTest' ক্লাসে ২ টি অ্যাট্রিবিউট ডিক্লেয়ার করেছি, 'x' যেটি পাবলিক এবং 'y' যেটি প্রাইভেট । অর্থাৎ 'x' কে উক্ত ক্লাস এবং উক্ত ক্লাসের বাইরে যেকোন ক্লাস থেকে এবং যেকোন মেথড থেকে অ্যাক্সেস করা সম্ভব কিন্তু 'y' কে কেবল মাত্র 'EncapTest' ক্লাসের অবস্থিত মেথড ব্যাতীত অন্য কোন স্থান থেকে অ্যাক্সেস বা মডিফাই করা সম্ভব নয় । অর্থাৎ 'y' অ্যাট্রিবিউটটিকে আমরা 'EncapTest' ক্লাসে এনক্যাপসুলেট করে ফেলেছি ।

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

কিন্তু একটা অ্যাট্রিবিউটকে যদি আমরা প্রয়োজনে মডিফাই নাই করতে পারলাম বা অ্যাক্সেসই না করতে পারলাম তাহলে সেটা রেখে লাভ কি ! ওকে, সেটার ব্যাবস্থাও আছে । এটার জন্য আমরা যে কাজটি করি সেটা হল পাবলিক একটা ইন্টারফেস (যে ইন্টারফেস ইমপ্লিমেন্ট করা হয় সেটা না) রাখি যে মাধ্যমে আমরা উক্ত অ্যাট্রিবিউটকে অ্যাক্সেস করতে পারি । এধরনের পাবলিক ইন্টারফেসকে বলা হয় গেটার (getter) এবং সেটার (setter) মেথড । এই মেথডগুলার অ্যাক্সেস মডিফায়ার সাধারনত (অসাধারনও হতে পারে) দেওয়া থাকে পাবলিক এবং যেকোন স্থান থেকে তাদের অ্যাক্সেস করা সম্ভব । গেটার মেথডের মাধ্যমে কোন একটি অ্যাট্রিবিউটের ভ্যালু রিড করা হয় এবং সেটার মেথডের মাধ্যমে কোন একটা অ্যাট্রিবিউটের ভ্যালু সেট করা হয় বা সহজ কথায় ভ্যালু ওভাররাইড করা হয় । চলুন একটা উদাহরন দেখে নেওয়া যাক ।

কোডঃ
public class EncapTest {

    private int x;
    private int y;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

public class Main {

    public static void main(String[] args) {

        EncapTest et = new EncapTest();
        et.setX(5);
        et.setY(7);
        System.out.println("Value of x: " + et.getX()); //x is accessible through getter
//        System.out.println(et.x); //x is not directly accessible
        System.out.println("Value of y: " + et.getY());
    }
}

আউটপুটঃ
Value of x: 5
Value of y: 7

উপরোক্ত ছোট্ট প্রোগ্রামটিতে আমরা দেখতে পারছি যে 'EncapTest' ক্লাসের মাঝে ডিক্লেয়ার কৃত ২ টি অ্যাট্রিবিউট 'x' & 'y' প্রাইভেট তাই মেইন মেথডের মাঝ থেকে সরাসরি তাদের কোনভাবেই অ্যাক্সেস করা সম্ভব নয় । কিন্তু তাদের গেটার এবং সেটারের মাধ্যমে অ্যাক্সেস করা সম্ভব ।

মূল লক্ষনীয় বিষয় এবার । খেয়াল করে দেখুনতো একটা অ্যাট্রিবিউটকে প্রাইভেটলি ডিক্লেয়ার করে সেটার গেটার এবং সেটারকে যদি পাবলিক ডিক্লেয়ার করা হয় তবে উক্ত অ্যাট্রিবিউটটিকে বরং পাবলিক ডিক্লেয়ার করার মাঝে কোন তফাৎ থাকে কি না । নাহ কোন তফাতই থাকবে না, কারন প্রাইভেট প্রোপার্টি হওয়া সত্তেও উক্ত অ্যাট্রিবিউটকে যেকোন স্থান থেকে মেনুপুলেট করা সম্ভব । তাহলে আর এনক্যাপসুলেট করলাম কিভাবে !

অ্যাক্সেস লেয়ার অনুযায়ী এনক্যাপসুলেশন আসলে আমাদের করতে হবে গেটার এবং সেটার মেথডে । অর্থাৎ একটি অ্যাট্রিবিউট (অবশ্যই সেটা যদি পাবলিক টাইপ স্ট্যাটিক বা ফাইনাল না হয় তবে) ডিক্লেয়ার করার সময় প্রাইভেটলি ডিক্লেয়ার করতে হবে এবং মডিফিকেশন স্কোপ সেট করে দিতে হবে মেথড লেয়ারে । ওকে একটা উদাহরন দেখে ফেলি ।

কোডঃ
public class EncapTest {

    private int x;
    private int y;

    public int getX() {
        return x;
    }

    void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    void setY(int y) {
        this.y = y;
    }
}

public class Main {

    public static void main(String[] args) {

        EncapTest et = new EncapTest();
        et.setX(5);
        et.setY(7);
        System.out.println("Value of x: " + et.getX()); //x is accessible through getter
//        System.out.println(et.x); //x is not directly accessible
        System.out.println("Value of y: " + et.getY());
    }
}

আউটপুটঃ
Value of x: 5
Value of y: 7

উপরোক্ত প্রোগ্রামটিতে খুব ছোট্ট কিন্তু অনেক গুরুত্বপূর্ন যে পরিবর্তনটুকু করা হয়েছে সেটা হল আমরা 'x' & 'y' এর সেটারকে এনক্যাপসুলেট করে দিয়েছি প্যাকেজ স্কোপে । অর্থাৎ, যেখান থেকেই 'EncapTest' এর অবজেক্ট ক্রিয়েট করা হোক না কেন সেটার গেটার দুটি ঠিকই অ্যাক্সেস পাবে কিন্তু সেটার কেবল মাত্র উক্ত প্যাকেজের বাইরে অ্যাক্সেস পাবে না । মানে 'EncapTest' ক্লাসটি যে প্যাকেজে থাকবে উক্ত প্যাকেজে অবস্থিত যেকোন ক্লাসে যদি 'EncapTest' এর অবজেক্ট ক্রিয়েট করা হয় তবে সেসব ক্লাসে উক্ত অবজেক্টগুলার সেটার মেথডের মাধ্যমে 'x' এবং 'y' এর ভ্যালু মডিফাই করা সম্ভব কিন্তু প্যাকেজ স্কোপের বাইরে সেটা করা সম্ভব নয় । গেটার মেথড পাবলিক হওয়ায় যেকোন স্থান থেকে 'x' এবং 'y' ভ্যালু রিড করা সম্ভব । এখানে 'x' এবং 'y' এর মডিফিকেশন এনক্যাপসুলেট করে দেওয়া হয়েছে প্যাকেজ স্কোপে এবং ডাটা রিডের ক্ষেত্রে কোন প্রকার এনক্যাপসুলেশন আরোপ করা হয়নি ।

ওকে, এবার একটা জিনিস দেখি । এতক্ষন আমরা 'x' এবং 'y' নিয়ে কাজ করলাম যে দুটিই প্রিমিটিভ ভ্যালু । যদি এমন হয় যে আমরা অ্যাট্রিবিউট হিসাবে অন্য কোন ক্লাসের অবজেক্ট নিয়ে কাজ করছি সেক্ষেত্রে কি হবে ? এক্ষেত্রে আমাদের জানতে হবে অবজেক্ট রেফারেন্স কিভাবে কাজ করে । সেটা জানার জন্য আপনারা এই আর্টিকেলটি পড়ে ফেলতে পারেন । ওকে তাহলে আমরা জানি যে রেফারেন্স টাইপের ক্ষেত্রে গেটার মেথড আমাদের উক্ত অবজেক্টের রেফারেন্স রিটার্ন করবে । মানে আমরা যদি কোন প্রকার সেটার মেথড ব্যাবহার নাও করি তার পরেও আমরা উক্ত অবজেক্টের ম্যানুপুলেশন করতে পারব । ছোট্ট একটা উদাহরন দেখে ফেলি ।

কোডঃ
public class EncapObject {

    private int id;

    public int getId() {
        return id;
    }

    void setId(int id) {
        this.id = id;
    }
}

public class EncapTest {

    private EncapObject encapObject;

    public EncapTest(EncapObject encapObject) {
        this.encapObject = encapObject;
    }

    public EncapObject getEncapObject() {
        return encapObject;
    }
}

public class Main {

    public static void main(String[] args) {

        EncapObject encapObject = new EncapObject();
        encapObject.setId(5);
        EncapTest et = new EncapTest(encapObject);
        et.getEncapObject().setId(7);
        System.out.println(et.getEncapObject().getId());
    }
}

আউটপুটঃ
7

উক্ত ছোট্ট প্রোগ্রামটিতে আমরা দেখতে পারছি যে 'EncapTest' ক্লাসের মাঝে 'EncapObject' ক্লাসের যে অবজেক্ট 'encapObject' রয়েছে তার অ্যাক্সেস মডিফায়ার প্রাইভেট । মানে বাইরে থেকে অ্যাক্সেস পাওয়া সম্ভব নয় । উক্ত অবজেক্সেট কোন সেটার মেথডও তৈরি করা হয়নি কিন্তু একটি গেটার মেথড রয়েছে যেটি পাবলিক এবং গেটার মেথড উক্ত অবজেক্টের রেফারেন্সই রিটার্ন করে ভ্যালু নয় যেজন্য উক্ত অবজেক্টকে খুব সহয়ে ম্যানুপুলেট করা সম্ভব যদিও কোন সেটার মেথড প্রেজেন্ট নেই । এজন্য রেফারেন্স টাইপ অ্যাট্রিবিউটের ক্ষেত্রে আমাদের অবশ্যই এনক্যাপসুলেশন স্কোপ এমন ভাবে সেট করতে হবে যেন সেটার পল্যুশন না হয় ।

আশাকরি আপনারা সবাই বুঝতে পেরেছেন এনক্যাপসুলেশন কি এবং কেন সেটা এতটা গুরুত্বপূর্ন । বড় এবং সফিসিস্টিকেটেড অ্যাপ্লিকেশন যেখানে সিকিউরিটি খুবই গুরুত্বপূর্ন সেসব ক্ষেত্রে এনক্যাপসুলেশনের গুরুত্ব অপরিসীম ।

সবশেষে একটি প্রশ্ন, এনক্যাপসুলেশন আসলে কি ? কোন একটা অ্যাট্রিবিউটকে প্রাইভেট ডিক্লেয়ার করে তার পাবলিক গেটার এবং সেটার তৈরি করাই কি এনক্যাপসুলেশন ?

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

জাভা পাস-বাই-রেফারেন্স নাকি পাস-বাই-ভ্যালু ॥ পাগলের প্রলাপ

সবার প্রথমে একটা গল্প বলি । মূল গল্পটি কাহলিল জিবরানের , আমি সংক্ষিপ্ত এবং নিজের মত করেই লিখছি ।
গল্পঃ কোন এক কালে ছোট্ট কোন এক দেশে সবাই অনেক সুখে শান্তিতে বসবাস করত । সেই দেশে একটা কুপ ছিল যার পানি ছিল সুশীতল এবং সুমিষ্ট । রাজ্যের রাজা-প্রজা সবাই সেই কুপের পানি পান করত । এক রাত্রে এক ডাইনি এসে ওই কুপে কয়েক ফোটা পানি মিশিয়ে মনে মনে কিছু মন্ত্র পাঠ করে বিড়বিড় করে বলল যে আগামিকাল সকালে এই কুপের পানি যে যে পান করবে সে সে উন্মাদ হয়ে যাবে । পরের দিন রাজ্যের সবাই সেই কুপ থেকে পানি পান করল কেবল রাজা আর প্রধান উজির ব্যাতীত । কিছুক্ষন পর দেখা গেল রাজ্যের সব প্রজা নিজেদের কাজ বাদ দিয়ে কানা-ঘুষা শুরু করল যে, "আমাদের রাজা আর উজির পাগল হয়ে গিয়েছে, রাজ্যে নতুন রাজা নির্বাচিত করতে হবে নাহলে রাজ্য অচল হয়ে যাবে" । তো এমন অবস্থা দেখে রাজামশাই চিন্তিত হয়ে উজিরকে ডেকে বললেন যে যাও ওই কুপ থেকে ২ গ্লাস পানি নিয়ে এসো রাজ পেয়ালায় করে । তারপর রাজা আর উজির সেই পানি পান করলেন । পরদিন সকালে দেখা গেল সব স্বাভাবিক এবং সব প্রজা বলা বলি করতে লাগল, যাক আমাদের রাজামশাইয়ের তন্দ্রা ফিরে এসেছে, এবার রাজ্য চলবে । যথারীতি সুখে শান্তিতে সবাই জীবন যাপন করতে লাগল ।

গল্পটি বলা এজন্যই যে আপাতত এই আর্টিকেল পড়ার সময় ধরে নিন আমি একজন পাগল । আর পাগলের কোন কথা বিশ্বাস করতে হয়না ।

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



এখন প্রশ্ন হল জাভাতে এই দুই রকম বিহেভিয়রই প্রেজেন্ট । তাহলে জাভা কি পাস বাই ভ্যালু নাকি পাস বাই রেফারেন্স , নাকি দুটিই, নাকি কোনটিই না ? এগুলা আমরা দেখব তার আগে আমরা দুটি উদাহরন দেখি যে কিভাবে জাভাতে দুটি বিহেভিয়রই প্রেজেন্ট ।

পাস বাই ভ্যালুঃ
public class ReferenceTest {

    private int x = 5;

    private void increment(int x) {
        x++;
        System.out.println("Incremented Value: " + x);
    }

    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        rt.increment(rt.x);
        System.out.println("Original Value: " + rt.x);
    }
}

আউটপুটঃ
Incremented Value: 6
Original Value: 5


ছোট্ট এই প্রোগ্রামটি রান করলে আমরা দেখতে পারব যে জাভা আসলে পাস বাই ভ্যালু । increment মেথডটির মাঝে ভেরিয়েবলের ভ্যালু পাস হয়েছে তবে সেটা কেবল ভ্যালুটুকুই । সেটার সাথে অরিজিনাল ভেরিয়েবলের সম্পর্ক ছিন্ন করে কেবল ভ্যালুটুকুই ।

পাস বাই রেফারেন্সঃ
class ReferredObject {

    private int id;

    public ReferredObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    private void increment(ReferredObject ro) {

        ro.setId(ro.getId() + 1);
        System.out.println("Incremented Value: " + ro.getId());
    }

    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        ReferredObject ro = new ReferredObject(5);
        rt.increment(ro);
        System.out.println("Original Value: " + ro.getId());
    }
}

আউটপুটঃ
Incremented Value: 6
Original Value: 6


উপরোক্ত ছোট্ট প্রোগ্রামটিতে দেখা যাচ্ছে যে আমরা ReferredObject ক্লাসের একটি অবজেক্টকে increment মেথডের মাঝে পাস করে উক্ত অবজেক্টের ভ্যালু চেঞ্জ করলে সেটা যেখান থেকে কল করা হয়েছিল সেখানেও চেঞ্জ হয়ে যাচ্ছে । তার মানে এটা পাস বাই রেফারেন্স । (পাগলে কি না বলে!!!)

এখন তো মহা সমস্যা । জাভা কি তাহলে পাস বাই রেফারেন্স , পাস বাই ভ্যালু, দুটিই একাসাথে নাকি কোনটিই না !

আসলে জাভা প্রিমিটিভের ক্ষেত্রে পাস বাই ভ্যালু এবং অবজেক্টের ক্ষেত্রে [জানিনা!!!] বিহেভিয়ার শো করে । মানে টা কি ! ফাজলামি নাকি !!

ওকে, সব কিছুর আগে আমাদের একটি বহুল ব্যাবহৃত কিওয়ার্ডের (new) কাজ সম্পর্কে ক্লিয়ার হয়ে নিতে হবে । 'new' কিওয়ার্ডের কাজ সম্পর্কে ওরাকলের টিউটোরিয়াল সিরিজে খুব সুন্দর চিত্র সহকারে ব্যাখ্যা করে বুঝিয়ে বলা হয়েছে । সংক্ষেপে বললে 'new' অপারেটর ছাড়া কোনভাবেই মেমোরিতে নতুন কোন স্পেস অ্যালোকেট করা সম্ভব নয় । কেবল মাত্র প্রিমিটিভ ভ্যারিয়েবল ছাড়া যেকোন অবজেক্ট এমনি অ্যারের ক্ষেত্রেও 'new' অপারেটর ব্যাবহার করতেই হবে মেমোরিতে নতুন স্পেস অ্যালোকেট করতে তথা নতুন অবজেক্ট ক্রিয়েট করতে । অন্য সব ক্ষেত্রে কেবল মাত্র একই অবজেক্টের লোকেশন পয়েন্টিং করা হবে আলাদা আলাদা নামে ।


কোডঃ
class ReferredObject {

    private int id;

    public ReferredObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    public static void main(String[] args) {

        ReferredObject ro1 = new ReferredObject(5);
        ReferredObject ro2 = ro1;
        ro2.setId(7);
        System.out.println("Value of ro2: " + ro2.getId());
        System.out.println("Value of ro1: " + ro1.getId());
    }
}

আউটপুটঃ
Value of ro2: 7
Value of ro1: 7

এই ছোট্ট প্রোগ্রামটিতে আমরা দেখতে পারছি যে 'ro1' অবজেক্টটি মেমোরিতে ক্রিয়েট হয়েছে কারন সেটাকে ক্রিয়েট করা হয়েছে 'new' অপারেটর এবং উক্ত ক্লাসের কনস্ট্রাক্টর ব্যাবহার করে, কিন্তু 'ro2' অবজেক্টরি মেমোরিতে নতুন কোন স্পেস অ্যালোকেট করে নি বরং 'ro1' যে মেমোরি স্পেসে রেফার করে ছিল ঠিক সেটাতেই রেফার করে আছে । এজন্য 'ro2' তে কোন প্রকার চেঞ্জ করা হলে সেটা ঠিক সেই মেমোরিকে ওভাররাইড করছে যে মেমোরি 'ro1' রেফার করে রয়েছে । ওকে , এবার তবে আমরা আরেকটা কোডে চলে যাই ।

কোডঃ
class ReferredObject {

    private int id;

    public ReferredObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    private void increment(ReferredObject ro, int newValue){
       
        ro.setId(newValue);
    }
   
    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        ReferredObject ro1 = new ReferredObject(5);
        ReferredObject ro2 = ro1;
        rt.increment(ro2, 11);
        System.out.println("Value of ro2: " + ro2.getId());
        System.out.println("Value of ro1: " + ro1.getId());
    }
}

আউটপুটঃ
Value of ro2: 11
Value of ro1: 11

উক্ত ছোট্ট প্রোগ্রামটি সব কিছু আগের মতই তবে এখানে ReferenceTest ক্লাসের মাঝে একটা মেথড increment অ্যাড করা হয়েছে যেটি ReferredObject টাইপের একটি প্যারামিটার এবং একটি ইন্টিজার ভ্যালু নেয় । যেহেতু increment মেথডের মাঝে পাস করার সময় আমরা ReferredObject কে নিউ দিয়ে ক্রিয়েট করিনি সেহেতু সে মেমোরিতে নতুন কোন স্পেস অ্যালোকেট করবে না বরং সেই মেমোরিকেই রেফার করবে যেটি 'ro1', 'ro2' করছে । অর্থাৎ 'ro1', 'ro2' এবং increment মেথডের 'ro' একই মেমোরি সেগমেন্টকে রেফার করে রয়েছে । যেকোন একটির মিউটেশন হলে সবগুলারই মিউটেশন হবে । এবার একটু ভিন্ন ধরনের একটা কোড দেখব আমরা ।

কোডঃ
class ReferredObject {

    private int id;

    public ReferredObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    private ReferredObject increment(ReferredObject ro, int newValue) {

        ro.setId(newValue);
        ro = new ReferredObject(newValue);
        ro.setId(ro.getId() + newValue);
        return ro;
    }

    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        ReferredObject ro1 = new ReferredObject(5);
        ReferredObject ro2 = ro1;
        ReferredObject ro3 = rt.increment(ro2, 11);
        System.out.println("Value of ro3: " + ro3.getId());
        System.out.println("Value of ro2: " + ro2.getId());
        System.out.println("Value of ro1: " + ro1.getId());
    }
}

আউটপুটঃ
Value of ro3: 22
Value of ro2: 11
Value of ro1: 11

উক্ত কোডটিও আগের মতই তবে এখানে কিছুটা পরিবর্তন করা হয়েছে । এখানে increment মেথডের মাঝে 'ro' এর মিউটেশন ততক্ষন 'ro1' এবং 'ro2' এর ক্ষেত্রেও প্রোযোজ্য হবে যতক্ষন পর্যন্ত তারা সেম রেফারেন্স পয়েন্ট করে থাকবে । যখনই 'ro' কে 'new' অপারেটর দিয়ে নতুন করে কনস্ট্রাক্ট করা হচ্ছে তখনই সেটা 'ro1' এবং 'ro2' থেকে ডিকাপলড হয়ে নতুন মেমোরি লোকেশনে রেফার করছে । এবং সেটার যেকোন মিউটেশন আর কোনভাবেই 'ro1' এবং 'ro2' তে প্রভাব বিস্তার করতে পারবে না ।

এবার অবজেক্টের মাঝে অবজেক্টের রেফারেন্স নিয়ে হালকা পাতলা কিছু কথা বলব ।
কোডঃ
class University {

    private String universityName;

    public University(String universityName) {
        this.universityName = universityName;
    }

    public String getUniversityName() {
        return universityName;
    }

    public void setUniversityName(String universityName) {
        this.universityName = universityName;
    }
}

class ReferredObject {

    private int id;
    private University university;

    public ReferredObject(int id, University university) {
        this.id = id;
        this.university = university;
    }

    public University getUniversity() {
        return university;
    }

    public void setUniversity(University university) {
        this.university = university;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    private ReferredObject increment(ReferredObject ro, int newValue) {

        ro.setId(newValue);
        ro = new ReferredObject(newValue, ro.getUniversity());
        ro.setId(ro.getId() + newValue);
        ro.getUniversity().setUniversityName("Changed University Name");
        return ro;
    }

    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        University university = new University("Universal University");
        ReferredObject ro1 = new ReferredObject(5, university);
        ReferredObject ro2 = ro1;
        ReferredObject ro3 = rt.increment(ro2, 11);
        System.out.println("Value of ro3: " + ro3.getId() + ", University Name: " + ro3.getUniversity().getUniversityName());
        System.out.println("Value of ro2: " + ro2.getId() + ", University Name: " + ro2.getUniversity().getUniversityName());
        System.out.println("Value of ro1: " + ro1.getId() + ", University Name: " + ro1.getUniversity().getUniversityName());
    }
}

আউটপুটঃ
Value of ro3: 22, University Name: Changed University Name
Value of ro2: 11, University Name: Changed University Name
Value of ro1: 11, University Name: Changed University Name

উপরোক্ত ছোট্ট উদাহরনটিতে আমরা দেখতে পারছি যে increment মেথডের মাঝে 'ro' কে নতুন করে কনস্ট্রাক্ট করা হয়েছে । তাই 'ro' , 'ro1' এবং 'ro2' থেকে ডিকাপলড হয়ে গিয়েছে । কিন্তু 'ro' এর মাঝে আমরা university এর সেই রেফারেন্সটিই রেফার করছি যেটি 'ro1' এবং 'ro2' তে রেফার হয়ে রয়েছে । সুতরাং 'ro' তে university এর যেকোন মিউটেশন 'ro1' এবং 'ro2' এর ক্ষেত্রেও প্রোযোজ্য হবে যদিনা আমরা 'ro' তে university কে 'new' কিওয়ার্ড দিয়ে নতুন করে কনস্ট্রাক্ট না করি ।

এমনকি থ্রেডের মাঝেও রেফারেন্স টাইপ একই বিহেভিয়ার শো করবে যেমনটা হওয়া কথা । মানে অবজেক্ট রেফারেন্সের একই যায়গায় ভ্যালু ওভাররাইড করবে । বোঝা না গেলে তার জন্য নিচের ছোট্ট একটু কোড দেখে ফেলুন ।

কোডঃ
class ReferredObject {

    private int id;

    public ReferredObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

class RefInThread implements Runnable {

    private final ReferredObject reffObject;
    private final long sleepingTime;
    private final int iteration;
    private final int threadId;

    public RefInThread(ReferredObject reffObject, long sleepingTime, int iteration, int id) {
        this.reffObject = reffObject;
        this.sleepingTime = sleepingTime;
        this.iteration = iteration;
        this.threadId = id;
    }

    @Override
    public void run() {

        try {

            for (int i = 0; i < this.iteration; i++) {

                Thread.sleep(this.sleepingTime);
                this.reffObject.setId(this.reffObject.getId() + 1);
                System.out.println("Changing in : " + this.threadId + ", Value: " + this.reffObject.getId());
            }
        } catch (InterruptedException e) {

            System.err.println(e.toString());
        }
    }
}

public class ReferenceTest {

    public static void main(String[] args) {

        ReferredObject ro = new ReferredObject(5);
        new Thread(new RefInThread(ro, 500, 10, 1)).start();
        new Thread(new RefInThread(ro, 800, 5, 2)).start();
    }
}

আউটপুটঃ
Changing in : 1, Value: 6
Changing in : 2, Value: 7
Changing in : 1, Value: 8
Changing in : 1, Value: 9
Changing in : 2, Value: 10
Changing in : 1, Value: 11
Changing in : 2, Value: 12
Changing in : 1, Value: 13
Changing in : 1, Value: 14
Changing in : 2, Value: 15
Changing in : 1, Value: 16
Changing in : 2, Value: 17
Changing in : 1, Value: 18
Changing in : 1, Value: 19
Changing in : 1, Value: 20

উপরোক্ত প্রোগ্রামটিতে থ্রেডের দুটি আলাদা আলাদা অবজেক্টের মাঝে প্যারামিটার হিসাবে একই রেফারেন্স পাস করা হয়েছে তাই তারা ওই রেফারেন্স পয়েন্টেই ভ্যালু ম্যানুপুলেট করবে , অর্থাৎ দুটি থ্রেডই আসলে একই অবজেক্ট নিয়ে কাজ করছে ।


এতক্ষন আপনার দেখলাম যতসব অবজেক্ট নিয়ে কথা বার্তা । প্রিমিটিভ ভ্যাল্যুর ক্ষেত্রে রেফারেন্সের উপায় তবে কি ? সেটার উত্তরের জন্য আবারো স্ট্যাকওভারফ্লোর এই লিংকটি ধরিয়ে দেই।

এবার কাজের কথায় আসি । জাভা কি পাস বাই রেফারেন্স নাকি পাস বাই ভ্যালু ? এতক্ষন আমরা যে উদাহরন গুলা দেখলাম সেটা যদি পাস বাই ভ্যালুর উদাহরন মনে হয় তবে জাভা পাস বাই ভ্যালু। আর যদি সেটা মনে না হয় তবে জাভা পাস বাই রেফারেন্স। এখন কথা হল জাভা যখন কোন একটি মেথডের প্যারামিটার হিসাবে কোন অবজেক্টকে রেফার করে তবে সেটা হয় কিভাবে । আমরা জানি প্রতিটি অবজেক্ট মেমোরিতে ক্রিয়েট হওয়ার সময় সেটা নির্দিষ্ট একটি মেমোরি লোকেশনে অ্যালোকেট হয় । JVM এই মেমোরি লোকেশন গুলাকে ম্যাপ করার জন্য হ্যাশ ভ্যালু ব্যাবহার করে । প্রতিটি অবজেক্ট যাদের 'new' অপারেটর দিয়ে ইন্সট্যান্সিয়েট করা হয় তার জন্য JVM একটি ইউনিক হ্যাশ কোড জেনারেট করে । ক্ষেত্র বিশেষে কিছু এক্সেপশনাল কন্ডিশন আসতে পারে যেটা অন্য কোন একটি আর্টিকেলে আলোচনা করা হবে । String ক্লাসও এই ধরনের ব্যাতীক্রম একটা ক্লাস । এগুলা নিয়ে অন্য কোন একদিন আলোচনা করা যাবে । এবার আমরা দেখি হ্যাশ কোড আসলে কিভাবে কাজ করছে ।

কোডঃ

class University {

    private String universityName;

    public University(String universityName) {
        this.universityName = universityName;
    }

    public String getUniversityName() {
        return universityName;
    }

    public void setUniversityName(String universityName) {
        this.universityName = universityName;
    }
}

class ReferredObject {

    private int id;
    private University university;

    public ReferredObject(int id, University university) {
        this.id = id;
        this.university = university;
    }

    public University getUniversity() {
        return university;
    }

    public void setUniversity(University university) {
        this.university = university;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    private ReferredObject increment(ReferredObject ro, int newValue) {

        ro.setId(newValue);
        ro = new ReferredObject(newValue, ro.getUniversity());
        ro.setId(ro.getId() + newValue);
        ro.getUniversity().setUniversityName("Changed University Name");
        return ro;
    }

    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        University university = new University("Universal University");
        ReferredObject ro1 = new ReferredObject(5, university);
        ReferredObject ro2 = ro1;
        ReferredObject ro3 = rt.increment(ro2, 11);
        System.out.println("Value of ro3: " + ro3.getId() + ", University Name: " + ro3.getUniversity().getUniversityName());
        System.out.println("Hash of ro3: " + ro3.hashCode() + ", Hash of university: " + university.hashCode());
        System.out.println("Value of ro2: " + ro2.getId() + ", University Name: " + ro2.getUniversity().getUniversityName());
        System.out.println("Hash of ro2: " + ro2.hashCode() + ", Hash of university: " + university.hashCode());
        System.out.println("Value of ro1: " + ro1.getId() + ", University Name: " + ro1.getUniversity().getUniversityName());
        System.out.println("Hash of ro1: " + ro1.hashCode() + ", Hash of university: " + university.hashCode());
    }
}

আউটপুটঃ

Value of ro3: 22, University Name: Changed University Name
Hash of ro3: 1808253012, Hash of university: 589431969
Value of ro2: 11, University Name: Changed University Name
Hash of ro2: 1252169911, Hash of university: 589431969
Value of ro1: 11, University Name: Changed University Name
Hash of ro1: 1252169911, Hash of university: 589431969

উপরোক্ত আউটপুটে আমরা দেখতে পারছি 'ro1' এবং 'ro2' এর হ্যাশ ভ্যালু একই কিন্তু 'ro3' এর হ্যাশ ভ্যালু আলাদা। 'ro1', 'ro2', এবং 'ro3' এর সবার ক্ষেত্রেই university এর হ্যাশ ভ্যালু একই । আসলে এই হ্যাশ ভ্যালুই হল JVM এর মেমোরি অ্যাড্রেসের টোকেন যে টোকেন দিয়ে JVM প্রতিটি অবজেক্টকে সিস্টেম মেমোরির সাথে ম্যাপ করে । যখন মেথডের মাধ্যমে কোন অবজেক্টকে পাস করা হয় তখন সেখানে এই হ্যাশ ভ্যালুই পাস হয় মেমোরি রেফারেন্স হিসাবে । যেহেতু রেফারেন্স হিসাবে একটা ভ্যালুকে পাস করা হচ্ছে তাই এটাকে পাস বাই ভ্যালু বলা যেতে পারে ।

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

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

সবশেষে একটা প্রশ্ন, কাহলিল জিবরান সাহেবের গল্পের কথা মনে আছেতো ? পানি কি আপনারা খেয়েছেন নাকি আমি খেয়েছি ? আমি খেলে সমস্যা নাই , আপনারা খেলে আমাকেও খানিকটা দিবেন, খেয়ে আপনাদের কাতারে ভিড়ে যাব ।

সবাইকে ধন্যবাদ ।

Wednesday, November 2, 2016

Test Driven Development (TDD) - Part 2 ( Tools )

Test Driven Development (TDD) - Part 1 ( Introduction )


টেস্ট ড্রাইভেন ডেভলপমেন্ট



সফ্টওয়্যার ডেভলপমেন্ট বলতে কম বেশি আমরা সবাই সেটা বুঝি । সেই সফ্টওয়্যারের টেস্টিং বলতে যেটা বলা হয় সেটাও কম বেশি বুঝি । আজ আমরা যেটির সম্পর্কে জানবো সেটি হল টেস্ট ড্রাইভেন ডেভলপমেন্ট বা সংক্ষেপে TDD

 TDD Life Cycle

টেস্ট ড্রাইভেন ডেভলপমেন্ট বলতে সহজ কথায় বোঝায় কোন একটি কোড সেগমেন্ট লেখার সাথে সাথে সেটি টেস্ট করা যে এই কোড সেগমেন্টটি তার পূর্ন রিকোয়ারমেন্ট সম্পূর্ন করতে পারছে কি না । অর্থাৎ , আপনি কোন একটি কোড বা লজিক লিখলেন, সেই সঙ্গে সঙ্গে সেটি যত রকম ইনপুট ফেস করতে পারে সব রকম ইনপুটের জন্যই টেস্ট করলেন যে সেটি কাঙ্খিত রেজাল্ট বা আউটপুট দিতে পারছে কি না । যদি কোন এক বা একাধিক ইনপুটের জন্য সেটি ফেইল করে তবে সেটিকে আবার ডিবাগ করা এবং যতক্ষন পর্যন্ত না সেটি সম্পূর্ন বাগমুক্ত হচ্ছে ততক্ষন টেস্ট করে যাওয়া এবং কোড রিফ্যাক্টর করা । এই পদ্ধতিটি প্রথম ২০০৩ সালে Kent Beck সাহেব পরিচিত করান । টেস্ট ড্রাইভেন ডেভলপমেন্ট সম্পর্কে ভালো সঙ্গা পেতে উইকিপিডিয়ার এই লিংকটিতে ঘুরে আসতে পারেন ।

প্রশ্ন আসতেই পারে কেন টেস্ট ড্রাইভেন ডেভলপমেন্ট ? ওকে , মনে করুন আপনাকে একটা টেক্সট ফাইল দিয়ে বলা হল এই ফাইলের মাঝে সবচেয়ে বেশি ফ্রিকোয়েন্সির ১০ টি শব্দ নির্বাচন করে দিতে হবে । ব্যাস , আপনি ঝাপিয়ে পড়লেন কোড করতে । পুরা লজিক মাথার ভেরত ছক একে ফেল্লেন , বা খাতা কলম নিয়ে বসলেন ফ্লোচার্ট আকলেন বা অ্যালগোরিদমের সুডো কোড লিখে বসে গেলেন কোড করতে । কাজ করতে করতে শেষের দিকে যেয়ে মাথা গরম হয়ে গেল , এইরে উপরে কি যেন একটা চিন্তা করে এই অ্যাট্রিবিউটটা নিয়েছিলাম ? বা সব ঠিক ঠাক মত শেষ করলেন । যখন কোড রান করলেন দেখা গেল এক গাদা এক্সেপশন দিয়ে বসে আছে । আপনার তো মাথায় হাত । চুল ছিড়তে মত চাচ্ছে । শেষে দেখা গেল আপনি যে ফাইলটি থেকে ডাটা রিড করতে চাচ্ছেন সেই ফাইলটিই ওই ডিরেক্টরিতে ( ফোল্ডার ) এক্সিস্ট করেনা বা সেই ফাইলটির রিড পার্মিশনই নাই । সেটা ঘষে মেজে ঠিক করলেন , অবশ্য তার জন্য অনক কাঠখড় পোড়াতে হল কারন পুরা কোডের স্ট্রাকচারই হয়ত পরিবর্তন করতে হল । যাইহোক , তারপর দেখা গেল কাজ ঠিকই করছে তবে এই শব্দ দুইবার দেখাচ্ছে । অনেকটা এমন যে "good" শব্দটা সবচেয়ে বেশিবার এসেছিল আপনার প্রদত্ত ফাইলে এবং সেটাই প্রথমে দেখানোর কথা ছিল তবে আপনার কোড দেখাচ্ছে যে "Good" এর অবস্থান ৬ তম এবং "good" এর অবস্থান ৯ । বিষয়টা একটু ঝামেলার হয়ে গেল । আমার আপনি সেটা ঠিক করতে বসলেন । এবং যথারীতি নাজেহাল অবস্থা ।

ওকে অনেক হয়েছে । আমরা প্রোগ্রামার মানুষজন এমনিতে অনেক অলস আর মাথা গরম টাইপের । এত ঝামেলার মাঝে যেন পড়া না লাগে সেজন্য আমরা শর্টকাট খুজি । তেমন একটা শর্টকাট হল টেস্ট ড্রাইভেন ডেভলপমেন্ট । বুঝতে সমস্যা হলেও ভয় পাওয়ার কোন কারন নেই । উপরের প্রবলেমটা একটু সহজ ভাবে চিন্তা করি চলুন । কয়েকটা ধাপে আমরা কাজটা করিঃ

ধাপ ১ঃ আমাদের বলা হয়েছে একটা টেক্সট ফাইল পড়ে তার মধ্য থেকে সবচেয়ে বেশি ফ্রিকোয়েন্সির নির্দিষ্ট কিছু শব্দ বাছাই করতে । এসবকিছু করতে গেলে প্রথমেইতো আমাদের টেক্সট ফাইলটি পরথে হবে তাইনা ? ওকে আমরা যে মেথড ( ফাংশন ) টি লিখছি সেটাতে কেবল টেক্সট ফাইলটি রিড করার কোড লিখলাম । এবার আমরা এই ছোট্ট কোডটুকু নানা ভাবে টেস্ট করলাম । দেখলাম এটি সব রকম সিচুয়েশন মিট করতে পারে কি না । ফাইলটি থাকলে রিড করতে পারছে কি না বা না থাকলে কি করা উচিৎ । ফাইলে কোন কনটেন্ট থাকলে কি করা উচিৎ বা না থাকলে কি করা উচিৎ এমন । এটুকু করলে অন্তত আমরা নিশ্চিত যে ফাইল পড়া নিয়ে আর কোন ঝামেলা থাকলো না । এবার আগাই সামনের দিকে ।

ধাপ ২ঃ যেহেতু আমাদের ফাইল রিড করা নিয়ে কোন চিন্তা আর নাই তাই আমরা নিশ্চিন্তে এবার সেখান থেকে কনটেন্ট পড়তে পারি । এবার আমরা একটা একটা করে শব্দ নিয়ে খেলা শুরু করে দিতে পারি । এমন একটা ডাটা স্ট্রাকচার নিয়ে কাজ করতে পারি যেখানে ২ টা ভাগ থাকবে । সি++ এর পেয়ার বা জাভার ম্যাপ বা সি এর স্ট্রাকচার বা নিজ থেকেও কাষ্টম ডাটা স্ট্রাকচার ডিজাইন করে নিতে পারেন । উদাহরন হিসাবে "Map<String, Integer> wordMap" এমনটা ধরে নিতে পারেন । এবার টেক্সট ফাইল থেকে একটা একটা শব্দ পড়লেন আর টেস্ট করলেন সেটা এই ম্যাপে আছে কি না । যদি না থাকে তবে কি হিসাবে ওই শব্দটি এবং ভ্যালু হিসাবে ১ ইনসার্ট করুন । যদি থাকে তবে সেই কি এর ভ্যালুর মান এক বাড়িয়ে দিন । এভাবে আমরা ওই ফাইলে যত শব্দ আছে তাদের ফ্রিকোয়েন্সি পেয়ে গেলাম । এবার টেস্ট করুন , আপনার কোডটি কি কাজ করছে ? হ্যা কাজ ঠিকই করছে তবে "Good" আর "good" এর মাঝে পার্থক্য করে ফেলেছে । কি আর করা সামনে আগাই ।

ধাপ ৩ঃ যেহেতু আগের ২ টা ধাপই সফলভাবে কাজ করছে তাই ওদিকে মাথা না ঘামালেও চলবে । এবার আমরা "Good" আর "good" এর মাঝে ব্যাবধানটা কমাতে হবে । একটা কাজ করলে কেমন হয় ? যদি আমরা যেকোন শব্দকেই আপার কেস বা লোয়ার কেসে কনভার্ট করে ফেলি ? মানে করলাম আমরা সবই লোয়ার কেসে কনভার্ট করবো , তাহলে "Good" = "good" এবং "good" = "good" হয়ে গেল । থাকলো আর কোন পার্থক্য ? না থাকারই কথা । এই ছোট্ট কাজটি আমরা করবো নতুন একটা ওয়ার্ড পড়ে সেটাকে ম্যাপে ইনসার্ট করার সময় এবং চেকিং এর সময় । এবার আমরা টেস্ট করলে দেখতে পাবো, নাহ এবার আর "Good" আর "good" এ গোলমাল হচ্ছে না । ওকে সামনে আগাই ।

ধাপ ৪ঃ কাজতো প্রায় শেষ । এবার আমরা ম্যাপটিকে ভ্যালুর রেসপেক্টে সর্ট করে ফেলতে পারি । সেটা কিভাবে করা হয় সেটা যদি না জেনে থাকেন তবে গুগলে একটু ঘাটাঘাটি করতে পারেন । এই লিংকে খুব সুন্দরভাবে বোঝানো হয়েছে কিভাবে একটি ম্যাপ টাইপ কালেকশন সর্ট করা যায় । অথবা কাষ্টম ডাটা টাইপের জন্য Comparator বা Comparable ইন্টারফেস নিসেও একটু পড়াশোনা করতে পারেন । ব্যাস হয়ে গেল সর্টিং । মনে করলাম ডিসেন্ডিং অর্ডারে সর্ট করেছেন । তাহলে প্রথম দিকের ১০ টি ( বা যতগুলা দেখাতে বলা হয়েছে ) দেখিয়ে দিন । টেস্ট করুন । আরেব্বাহ কাজ করছে তো ! কিন্তু সমস্যা হল ভিন্ন ভিন্ন শব্দ সংখ্যা যদি হয় ৭ টি আর আপনাকে যদি দেখাতে বলা হয় ১০ টি তাহলে তো একটু এক্সেপশন দিচ্ছেই । তাইনা ? ওকে সামনে আগাই ।

ধাপ ৫ঃ যেহেতু সব কাজই শেষ তাই বাড়তি করার মত যেটি থাকতে পারে সেটি হল যদি লিস্টের সাইজ প্রদত্ত নাম্বারের চেয়ে বড় হয় তাহলে কোন সমস্যা নাই ওই নাম্বার সংখ্যক ইটারেশন দিয়েই শব্দগুলা পিক করতে পারবেন । যদি ছোট হয় তাহলে লিস্টের সাইজ পর্যন্ত যান বা বলে দিন কাঙ্খিত নাম্বারের সমান পর্যান্ত শব্দ ওই টেক্সট ফাইলেই নেই । অথবা আপনার কাজের উপর ভিত্তি করে সিদ্ধান্ত নিন । ব্যাস কাজ শেষ !

উপরের ধাপগুলা অনুসরন করে আমরা দেখলাম খুব সহজে এবং বাগফ্রী একটি কোড কিভাবে লেখা যায় । এখানে পুরা কোডটি একবারে লিখে শেষে টেস্ট করতে গিয়ে মাথায় চুড় ছেড়ার কোন প্রয়োজন হয়নি । বরং খুব ছোট্ট ছোট্ট কাজকে বার বার টেস্ট করার মাধ্যমে আমরা আমাদের কাঙ্খিত ফলাফল পেয়ে গিয়েছি । সংক্ষেপে (ব্যাখ্যা অনেক বড় :P ) এটিই হল টেস্ট ড্রাইভেন ডেভলপমেন্ট । যেখানে প্রতিটি ছোট ছোট কাজকে সব রকম ভাবে টেস্ট করে দেখা হয় যে এই অংশটুকু তার চাওয়া পূরন করতে পারছে কি না । যতক্ষন না ওই অংশটুকু সম্পুর্ন শুদ্ধ না হচ্ছে ততক্ষন তার পরবর্তী অংশে হাত দেওয়া হচ্ছে না ।

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

সবাইকে ধন্যবাদ ।