[৩.২] মেথড (Method)

এই পর্যায়ে আমরা পরিচিত হবো Go মেথড এর সাথে। এর মাধ্যমেই Go, Object Oriented কনসেপ্টের সাথে নিজেকে সংযুক্ত করেছে। মেথড সম্পর্কে আমাদের অবশ্যই পরিষ্কার ধারণা থাকতে হবে।

[৩.২.১] মেথড কী ? 

মেথড একটি ফাংশন ছাড়া আর কিছুই নয়, কিন্তু এটি একটি নির্দিষ্ট Struct বা Non-struct এর সাথে সংযুক্ত। মেথডকে একটি সাধারণ ফাংশন থেকে সামান্য ভিন্ন সিনট্যাক্সে সংজ্ঞায়িত করা হয়। মেথডে ফাংশনের থেকে অতিরিক্ত প্যারামিটার টাইপ থাকে যা  func  কিওয়ার্ড এবং মেথড নামের মাঝখানে থাকে।  এই টাইপ প্যারামিটারকে রিসিভার বলা হয়। Go- তে সাধারণত রিসিভার ব্যবহার করা হয় OOP কনসেপ্ট ইমপ্লিমেন্ট করার জন্য যেখানে অন্য ভাষা যেমন পাইথন এবং জাভা তে ক্লাস ব্যবহার করে থাকে। Go- তে সাধারণত ক্লাস নেই , তবে Go- তে মেথডকে বিভিন্ন ধরণের Struct এবং Non-struct টাইপের সাথে সংযুক্ত করে সংজ্ঞায়িত করা হয়।

 এখন আমরা কিভাবে মেথড লিখতে হয় সেটার সিনট্যাক্স শিখব –

				
					 func (receiverName receiverType) MethodName (parameter List)(returnTypes){
       method body
   }

				
			

একটা সিম্পল মেথড দেখতে অনেকটা এরকম হয় ঃ

methods Method

একটি মেথডের শুরুতে func কিওয়ার্ড থাকে এবং এরপরে ফাস্ট ব্র্যাকেট থাকে যেখানে রিসিভার টাইপ থাকে এবং এরপর মেথডের নাম থাকে এবং ফাস্ট ব্র্যাকেট থাকে। এর পরে মেথডের এর ব্লক থাকে যেখানে আমরা কোড করতে পারি । একটি মেথডের ব্লক শুরু এবং শেষ হয় সেকেন্ড ব্র্যাকেট দিয়ে ।

 

বিশেষ নোট: আমরা জানি রিসিভার দুই ধরণের হয়ে থাকে ভ্যালু রিসিভার এবং পয়েন্টার রিসিভার। ভ্যালু রিসিভারের সাহায্যে Type এর মানের একটি কপি পাঠানো হয় যার ফলে আমরা চাইলেও Type এর আসল মানের কোন পরিবর্তন করতে পারি না। এজন্য যখন আসল মান পরিবর্তনের প্রয়োজন হয় তখন আমরা পয়েন্টার রিসিভার ব্যবহার করে মানের পরিবর্তন করে থাকি। কারণ পয়েন্টারের মাধ্যমে *Type এর অ্যাড্রেস পাঠানো হয়। যখন আমরা Type এবং *Type এর জন্য একটি মেথড ডিক্লেয়ার করব তখন আমাদের অবশ্যই নিচের বিষয়গুলো খেয়াল রাখতে হবে – 

  1. Type অবশ্যই ডিফাইন করতে হবে ।
  2. Type এবং মেথড অবশ্যই একই প্যাকেজে ডিফাইন করতে হবে।
  3. Type অবশ্যই ইন্টারফেস টাইপ হওয়া যাবেনা।

[৩.২.২] মেথড এর সাথে Struct টাইপ রিসিভারের ব্যবহার

মেথড ডিফাইনের সময় আমরা Sturct অথবা Non-Struct টাইপের রিসিভার ব্যবহার করতে পারি। এখন আমরা উদাহরণের মাধ্যমে দেখবো কীভাবে মেথডের সাথে Struct টাইপ রিসিভার ব্যবহার করা হয়।

   উদাহরণ –

				
					  package main

   import "fmt"

   type User struct {
       Name  string
       Email string
   }

   func (u User) userDetails() string {
       return fmt.Sprintf("User name is : %s and email is: %s", u.Name, u.Email)
   }

   func main() {
       user1 := User{Name: "John Doe", Email: "johndoe@Golinuxcloud.com"}
       fmt.Println(user1.userDetails())
   }

   // Output:
   // User name is : John Doe and email is: johndoe@Golinuxcloud.com

				
			

 উপরের উদাহরণে –

  • আমরা User Struct তৈরি করেছি যার Name এবং Email দুটি ফিল্ড রয়েছে। 
  • এরপর আমরা (u User) Struct রিসিভারের সাহায্যে userDetails() মেথড এসোসিয়েট করেছি। 
  • রিসিভার ভ্যারিয়েবল u এর সাথে ডট নোটেশনের মাধ্যমে Struct এর ফিল্ড ভ্যালু u.Name এবং u.Email অ্যাক্সেস করা হয়েছে। 
  • তারপর main() ফাংশনে User এর একটা ইন্সট্যান্স user1 তৈরি করা হয়েছে যার সাহায্যে userDetails() মেথডকে কল করা হয়েছে। 

 

[৩.২.৩] মেথড এর সাথে Non-Struct টাইপ রিসিভারের ব্যবহার

এখন আমরা দেখবো Non-Struct টাইপ রিসিভারের সাথে কীভাবে মেথড ডিফাইন করতে হয়। আমরা [৩.২.১] এ তিনটি পয়েন্টে দেখেছিলাম টাইপ এবং মেথড একই প্যাকেজে ডিক্লেয়ার করতে হবে তা না হলে কম্পাইলার এরর দিবে।  

নিম্নের উদাহরণটি লক্ষ করি ঃ

				
					package main

   import "fmt"

    type myNumber int

   func (num myNumber) square() myNumber {
       if num == 0 {
           return 1
       }
       return num * num
   }

   func main() {
       num := myNumber(25)
       sq := num.square()
       fmt.Printf("The square of %d is %d \n", num, sq)
   }


   // Output : The square of 25 is 625

				
			
উপরের উদাহরণে-
  • আমরা myNumber , Non-Struct একটি টাইপ তৈরি করেছি । 
  • এরপর আমরা (num myNumber) Non-Struct রিসিভারের সাহায্যে square() মেথড এসোসিয়েট করেছি যার রিটার্ন টাইপ myNumber। 
  • তারপর main() ফাংশনে myNumber এর একটা ইন্সট্যান্স num তৈরি করা হয়েছে যার সাহায্যে square() মেথডকে কল করা হয়েছে যেটা sq ভ্যারিয়েবলে ষ্টোর করা হয়েছে।

 [3.2.3] মেথড এর সাথে Interface টাইপের ব্যবহার

 Go – তে ইন্টারফেস হল একটি টাইপ যেখানে বিভিন্ন প্যারামিটার ও রিটার্ন টাইপের মেথড সিগনেচার থাকে। মেথডগুলো ইমপ্লিমেন্ট করার জন্য একটি Struct টাইপের রিসিভারের সাথে মেথড এসোসিয়েট থাকে।

 একটি উদাহরণের মাধ্যমে দেখা যাক –

				
					 package main

   import "fmt"

   type Animal interface {
       walk() string
       bark() string
   }

   type Dog struct {
       w, b string
   }

   func (d Dog) walk() string {
       return d.w
   }

   func (d Dog) bark() string {
       return d.b
   }

   func main() {
       var a Animal = Dog{"Dog is walking.....!!!", "Dog is barking....!!!"}
       fmt.Println("!.....Dog......!")
       fmt.Println(a.walk())
       fmt.Println(a.bark())
   }

   // Output :
   // !.....Dog......!
   // Dog is walking.....!!!
   // Dog is barking....!!!

				
			

  উপরের উদাহরণে, আমরা Animal  ইন্টারফেস  তৈরি করেছি যার walk() এবং bark() নামে দুটি মেথড রয়েছে । এরপর  Animal ইন্টারফেসকে  ইমপ্লিমেন্ট করার জন্য Dog টাইপের Struct তৈরি করেছি যার w ও b নামে দুটি ফিল্ড আছে। এরপর  (d Dog) Struct রিসিভারের সাহায্যে walk() এবং bark() মেথডকে এসোসিয়েট করেছি। রিসিভার ভ্যারিয়েবল d এর সাথে ডট নোটেশনের মাধ্যমে Struct এর ফিল্ড ভ্যালু d.w এবং d.b অ্যাক্সেস করা হয়েছে। তারপর main() ফাংশনে ইন্তারফেস Animal টাইপের ভ্যারিয়েবল a তে  Dog Struct আসাইন করা হয়েছে। এরপর ইন্টারফেস ভ্যারিয়েবলের সাথে a.walk() এবং a.bark() মেথড কল করা হয়েছে।

[৩.২.৪] মেথড এর সাথে Pointer এবং value রিসিভারের ব্যবহার

Go মেথড পয়েন্টার এবং ভ্যালু উভয় রিসিভারেই কাজ করতে পারে। Go তে পয়েন্টার ভ্যারিয়েবল সেই ভ্যারিয়েবলের মেমোরি অ্যাড্রেস ষ্টোর করে রাখে তারপর সেই ভ্যারিয়েবল নিয়ে কাজ করে। 

Go তে যখন পাস বাই ভ্যালু পাঠানো হয় এর অর্থ দাঁড়ায়, ভ্যারিয়েবলের একটা কপি পাস করা হচ্ছে। যদি আমরা  সেই ভ্যারিয়েবলের মান পরিবর্তন করি তাহলে যেখান থেকে ভ্যারিয়েবলটি পাস করা হয়েছে, তার মানের কোন পরিবর্তন হবে না। 

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

[৩.২.২] এর উদাহরণটা আবারও দেখা যাক – 

				
					

   package main

   import "fmt"

   type User struct {
       Name  string
       Email string
   }

   func (u User) userDetails() string {
       return fmt.Sprintf("User name is :%s and email is: %s", u.Name, u.Email)
   }

   func (u *User) changeDetails(newName, newEmail string) {
       u.Name = newName
       u.Email = newEmail
   }

   func main() {
       user1 := User{Name: "John Doe", Email: "johndoe@Golinuxcloud.com"}
       fmt.Println(user1.userDetails())
       user1.changeDetails("Mary Doe", "marydoe@Golinuxcloud.com")
       fmt.Println(user1.userDetails())
   }

   // Output :
   // User name is :John Doe and email is: johndoe@Golinuxcloud.com
   // User name is :Mary Doe and email is: marydoe@Golinuxcloud.com

				
			

উপরের উদাহরণে, [৩.২.২] এর উদাহরণের সাথে শুধু pointer রিসিভার টাইপের সাথে changeDetails() মেথড এড করা হয়েছে। যার মাধ্যমে user এর ডিটেইলস পরিবর্তন করা হয়েছে।