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 মেথডটি ওভাররাইড করতে হয়।

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

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

2 comments:

  1. বিস্তারিত জানতে পারলাম।ধন্যবাদ ভাই।

    ReplyDelete