1 Struct এর মৌলিক ধারণা

Go ভাষায়, struct টাইপটি একটি সংযোজিত ডেটা টাইপ, যা বিভিন্ন বা একই ধরণের ডেটা গুলি একটি একক এককোণে সংযোজন করার জন্য ব্যবহৃত হয়। Structs Go তে গুরুত্বপূর্ণ অবস্থান ধারণ করে কারণ এটি ধারণাগত অবজেক্ট-অরিয়েন্টেড প্রোগ্রামিং (OOP) এর মৌলিক দিশা প্রদান করে, তবে এটি প্রথাগত অবজেক্ট-অরিয়েন্টেড প্রোগ্রামিং ভাষাগুলি থেকে সামান্য পার্থক্য সহিত।

Struct এর প্রয়োজনীয়তা নিম্নলিখিত দিকগুলি থেকে উদ্ভবিত হয়:

  • তার পুনর্বিন্যাসযোগ্যতা বাড়াতে সমৃদ্ধ সম্পর্কে ভেগিতে ভেরিয়েবলগুলি বাছাই করা।
  • "ক্লাস" বোয়া অনুকরণ করার সুযোগ প্রদান করা, এনক্যাপসুলেশন এবং সংগ্রহ বৈশিষ্ট্য সহায়ক করার একটি উপায় প্রদান।
  • JSON, ডাটাবেস রেকর্ড ইত্যাদি উপরিদর্শন করার সময়, struct গুলি একটি সুবিধাজনক ম্যাপিং সরঞ্জাম প্রদান করে।

Struct দিয়ে ডাটা বিন্যাস পরিচিত প্রকাশে যায়, যেমন ব্যবহারকারী, অর্ডার ইত্যাদি।

2 Struct সংজ্ঞায়িত করা

Struct সংজ্ঞার বানানোর নির্দেশিকা হল:

type StructName struct {
    Field1 FieldType1
    Field2 FieldType2
    // ... অন্যান্য সদস্য চরণ
}
  • type শব্দটি স্ট্রাকচারের সংরক্ষণ প্রবেশ করিয়েছে।
  • StructName হল struct এর ধরণের নাম, যা গো এর নামকরণ অনুসরণ করে, সাধারণত ক্যাপিটালাইজড হয় এবং এর রপ্তানিমূল্য সূচিত করতে।
  • struct শব্দটি এটি একটি struct প্রকার তা।
  • ব্রেস এর ভিতরে {}, struct এর সদস্য চরণ সংজ্ঞায়িত হয়, যেটিতে প্রতিটি পরে তার ধরণের পরে।

Struct সদস্য গুলির প্রকার যে কোনো ধরণের হতে পারে, যথাযোগ্যতা ধারণার বিষয়টি (যেমন int, string, ইত্যাদি) এবং জটিল ধরণের ধরণের ধরণ (যেমন অ্যারে, স্লাইস, অন্যান্য struct ইত্যাদি) সহ।

উদাহরণস্বরূপ, একজন ব্যক্তি প্রতিনিধিত্ব করার জন্য একটি struct সংজ্ঞান করা:

type Person struct {
    Name   string
    Age    int
    Emails []string // য়থায় জটিল প্রকার অংশ হতে পারে
}

উপরের কোডে, Person struct এর তিনটি সদস্য চরণ রয়েছে: স্ট্রিং ধরণের Name, ইন্টিজার ধরণের Age, এবং স্ট্রিং স্লাইস ধরণের Emails, যাহা নির্দেশ করে যে একজন ব্যক্তির অনেক ইমেল ঠিকানা থাকতে পারে।

3 Struct তৈরি এবং উপযুক্ত করা

3.1 Struct ইন্সট্যান্স তৈরি

Struct এর দুটি উপায় আছে একটি struct ইন্সট্যান্স তৈরি করার: সরাসরি ঘোষণা বা new শব্দটি ব্যবহার করা।

সরাসরি ঘোষণা:

var p Person

উপরের কোডটি Person ধরণের p নামের একটি ইন্স্ট্যান্স তৈরি করে, যেখানে struct এর প্রতিটি সদস্য চরণ এর মান প্রত্যেকের ধরণের শূন্য মান।

new শব্দ ব্যবহার করা:

p := new(Person)

new কীওয়ার্ড ব্যবহার করে একটি struct ইউজার পয়েন্ট তৈরি করা হয়। এই সময়ে ভেরিয়েবল p এই ক্ষেত্রে প্রকার করা হয় *Person এ, যা একটি Person ধরণের নতুন বরাদ্দিকৃত চরণ গুলি ধরন করে, যার সদস্য চরণগুলি শূন্য মানে অধিগ্রহণ করে।

3.2 Struct ইন্সট্যান্স শুরু করা

Struct ইন্স্ট্যান্স গুলি অনুমোদিত হতে পারে একই সময়ে এবং প্রয়োজন হলে, দুটি উপায়ে: ফ

উদাহরণ:

```go
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    // একটি Person এর ধরনের ভেরিয়েবল তৈরি করুন
    p := Person{"Alice", 30}

    // স্ট্রাক্টের সদস্য অ্যাক্সেস করুন
    fmt.Println("নাম:", p.Name)
    fmt.Println("বয়স:", p.Age)

    // সদস্যের মান পরিবর্তন করুন
    p.Name = "Bob"
    p.Age = 25

    // আবার পরিবর্তিত সদস্যের মান অ্যাক্সেস করুন
    fmt.Println("\nনতুন নাম:", p.Name)
    fmt.Println("নতুন বয়স:", p.Age)
}

এই উদাহরণে, আমরা প্রথমে Person স্ট্রাক্ট ডিফাইন করি যার দুটি সদস্য চলচ্চিত্র Name এবং Age আছে। তারপরে আমরা এই স্ট্রাক্টের একটি ইনস্ট্যান্স তৈরি করি এবং দেখানো হয়েছে যেভাবে এই সদস্যদের মেম্বারগুলি পড়া এবং পরিবর্তন করা হয়।

5 স্ট্রাক্ট কম্পোজিশন এবং এম্বেডিং

স্ট্রাক্টগুলি শুধুমাত্র একত্রিত হতে পারে না, বরং আরও জটিল ডেটা গঠন তৈরি করতে এবং পাল্টা করতে এগিয়ে যেতে পারে।

5.1 অজানা স্ট্রাক্ট

একটি অজানা স্ট্রাক্ট যখন একটি নতুন ধরনের সৃষ্টি করা ছাড়া বরাদ্দ স্ট্রাক্ট নির্দেশিত নয়, বরং সরাসরি স্ট্রাক্ট সংজ্ঞা ব্যবহার করে। এটা যখন প্রয়োজন হয় একবার স্ট্রাক্ট তৈরি এবং এটি সহজে ব্যবহার করার জন্য, অপ্রয়োজনীয় প্রকারের তৈরি না করতে।

উদাহরণ:

package main

import "fmt"

func main() {
    // অজানা স্ট্রাক্ট সংজ্ঞা এবং তৈরি করুন
    person := struct {
        Name string
        Age  int
    }{
        Name: "Eve",
        Age:  40,
    }

    // অজানা স্ট্রাক্টের সদস্য অ্যাক্সেস করুন
    fmt.Println("নাম:", person.Name)
    fmt.Println("বয়স:", person.Age)
}

এই উদাহরণে, আমরা একটি নতুন ধরনের বরাদ্দ ছাড়া সরাসরি একটি স্ট্রাক্ট সংজ্ঞা করি এবং এর একটি ইনস্ট্যান্স তৈরি করি। এই উদাহরণে দেখা হয় যে কিভাবে একটি অজানা স্ট্রাক্ট করা এবং এর সদস্যদের অ্যাক্সেস করা হয়।

5.2 স্ট্রাক্ট এম্বেডিং

স্ট্রাক্ট এম্বেডিং একটি স্ট্রাক্ট কে অন্য একটি স্ট্রাক্টের সদস্য হিসাবে নেস্ট করার ব্যাপার। এটা আমাদেরকে আরও জটিল ডেটা মডেল তৈরি করতে সাহায্য করে।

উদাহরণ:

package main

import "fmt"

// ঠিকানা স্ট্রাক্ট সংজ্ঞা করুন
type Address struct {
    City    string
    Country string
}

// ঠিকানа স্ট্রাক্টে স্বয়ংক্রিয় থাকা হবে
type Person struct {
    Name    string
    Age     int
    Address Address
}

func main() {
    // একটি Person ইনস্ট্যান্স তৈরি করুন
    p := Person{
        Name: "Charlie",
        Age:  28,
        Address: Address{
            City:    "New York",
            Country: "USA",
        },
    }

    // এম্বেডেড স্ট্রাক্টের মেম্বার অ্যাক্সেস করুন
    fmt.Println("নাম:", p.Name)
    fmt.Println("বয়স:", p.Age)
    // ঠিকানা স্ট্রাক্টের সদস্য অ্যাক্সেস করুন
    fmt.Println("শহর:", p.Address.City)
    fmt.Println("দেশ:", p.Address.Country)
}

এই উদাহারণে, আমরা Address স্ট্রাক্ট সংজ্ঞা করি এবং এটির সদস্য হিসাবে Person স্ট্রাক্টে এম্বেড করি। Person এর একটি ইনস্ট্যান্স তৈরি করার সময়, আমরা সময়ই Address এর একটি ইনস্ট্যান্স তৈরি করি। ডট নোটেশন ব্যবহার করে আমরা এম্বেড স্ট্রাক্টের সদস্য অ্যাক্সেস করতে পারি।

6 স্ট্রাক্ট মেথড

অবজেক্ট-উপাদানিক প্রোগ্রামিং (OOP) বৈশিষ্ট্য স্ট্রাক্ট মেথড মাধ্যমে প্রযোজ্য হতে পারে।

6.1 মেথডের মৌলিক ধারণা

Go ভাষার মধ্যে, যদিও কোনও পারম্পরিক ধরনের ক্লাস এবং অবজেক্টের পরম্পরাগত ধারণা নেই, কিন্তু সদৃশ OOP বৈশিষ্ট্যগুলি স্ট্রাক্টের মেথড কে বাইন্ড করে সম্পাদন করা যেতে পারে। একটি স্ট্রাক্ট মেথড হলো একটি বিশেষ ধরনের ফাংশন যা নির্দিষ্ট ধরনের স্ট্রাক্ট (বা একটি স্ট্রাক্টের পয়েন্টার) এর সাথে সংযুক্ত করা।

### 6.2 মান রিসিভার এবং পয়েন্টার রিসিভার

মেথডগুলি রিসিভারের ধরণের ভিত্তিতে মান রিসিভার এবং পয়েন্টার রিসিভার হিসেবে বিভক্ত করা যেতে পারে। মান রিসিভার ব্যবহার করে মেথড কল করতে স্ট্রাক্টের অনুলিপি ব্যবহার করা হয়, যখন পয়েন্টার রিসিভার ব্যবহার করা হয় তখন স্ট্রাক্টের পয়েন্টার ব্যবহার করা হয় এবং মূল স্ট্রাক্টটি সম্পাদনা করা যায়।

```go
// মান রিসিভার সহ একটি মেথড সংজ্ঞায়িত করুন
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.length + r.width)
}

// একটি পয়েন্টার রিসিভার সহ একটি মেথড সংজ্ঞায়িত করুন, যা মূল স্ট্রাক্ট সম্পাদনা করতে পারে
func (r *Rectangle) SetLength(newLength float64) {
    r.length = newLength // মূল স্ট্রাক্টের মান সম্পাদনা করতে পারে
}

উপরের উদাহরণে, Perimeter একটি মান রিসিভার মেথড, এটি কল করলে Rectangle এর মান পরিবর্তন হয় না। তবে, SetLength একটি পয়েন্টার রিসিভার মেথড, এবং এই মেথড কল করা মূল Rectangle ইন্সট্যান্সে প্রভাব ফেলবে।

6.3 মেথড আবারণ

আপনি একটি স্ট্রাক্টের মেথড কল করতে স্ট্রাক্ট ভ্যারিয়েবল এবং এর পয়েন্টার ব্যবহার করে এটি করতে পারেন।

func main() {
    rect := Rectangle{length: 10, width: 5}

    // মান রিসিভার সহ মেথড কল
    fmt.Println("Area:", rect.Area())

    // মান রিসিভার সহ মেথড কল
    fmt.Println("Perimeter:", rect.Perimeter())

    // পয়েন্টার রিসিভার সহ মেথড কল
    rect.SetLength(20)

    // ফিরে মান রিসিভার সহ মেথড কল, লম্বার্ধ পরিবর্তন হয়েছে নোট করুন
    fmt.Println("After modification, Area:", rect.Area())
}

আপনি যখন পয়েন্টার ব্যবহার করে মেথড কল করেন, তখন Go স্বয়ংক্রিয়ভাবে মান এবং পয়েন্টার মধ্যে রূপান্তর করে, আপনার মেথডটি মান রিসিভার বা পয়েন্টার রিসিভার দিয়ে সংজ্ঞায়িত করা হয় কিনা তার পর্যায়েক্ষণ না করে।

6.4 রিসিভার প্রকার নির্বাচন

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

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

7 স্ট্রাক্চার এবং JSON সিরিয়ালাইজেশন

Go-তে, নেটওয়ার্ক অবতরণ বা কনফিগারেশন ফাইল হিসেবে স্ট্রাক্ট কে JSON ফরম্যাটে বাসাবার সময় সিরিয়ালাইজেশন প্রয়োজন। অনুরূপভাবে, আমাদের একােডিং/জেএসওএন প্যাকেজ আমাদেরকে এই সুবিধা উপলব্ধ করে।

আগে বর্ণিত উদাহরণের মতো আমরা JSON এবং স্ট্রাক্চার মধ্যে রূপান্তর করতে এক্সপোর্ট করা করতে পারি:

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

// পার্সন স্ট্রাকচার সংজ্ঞা করুন, এবং স্ট্রাকচারের ক্ষেত্রগুলির মধ্যে ম্যাপিং সংরক্ষণ করার জন্য json ট্যাগগুলি ব্যবহার করুন
type Person struct {
	Name   string   `json:"name"`
	Age    int      `json:"age"`
	Emails []string `json:"emails,omitempty"`
}

func main() {
	// একটি পার্সনের নতুন উদাহরণ তৈরি করুন
	p := Person{
		Name:   "John Doe",
		Age:    30,
		Emails: []string{"[email protected]", "[email protected]"},
	}

	// JSON এ সিরিয়ালাইজ করুন
	jsonData, err := json.Marshal(p)
	if err != nil {
		log.Fatalf("JSON marshaling failed: %s", err)
	}
	fmt.Printf("JSON format: %s\n", jsonData)

	// স্ট্রাকচারে ডিসিরিয়ালাইজেশন করুন
	var p2 Person
	if err := json.Unmarshal(jsonData, &p2); err != nil {
		log.Fatalf("JSON unmarshaling failed: %s", err)
	}
	fmt.Printf("Recovered Struct: %#v\n", p2)
}

উপরের কোডে, আমরা একটি Person স্ট্রাকচার সংজ্ঞা করেছি, যা "omitempty" অপশন সহ একটি স্লাইস টাইপ ফিল্ড সহ একতা লিখেছে। এই অপশন উল্লেখ করে যে যদি ক্ষেত্রটি খালি বা অনুপস্থিত হয়, তবে এটি JSON এ অন্তর্ভুক্ত হবে না।

আমরা json.Marshal ফাংশন ব্যবহার করেছি যতেটা একটি স্ট্রাকচার ইনস্ট্যান্সকে JSON এ সিরিয়ালাইজ করতে হয় এবং json.Unmarshal ফাংশনটি ব্যবহার করেছি যতেটা জেসওন ডাটা স্ট্রাকচার এ ডিসিরিয়ালাইজ করতে হয়।

8 স্ট্রাক্চারে উন্নত বিষয়াবলি

8.1 স্ট্রাক্ট সমূলনীর তুলনা

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

নিচে ডেমো হিসাবে স্ট্রাক্ট তুলনার একটি উদাহরণ দেওয়া হলো:

package main

import "fmt"

type Point struct {
	X, Y int
}

func main() {
	p1 := Point{1, 2}
	p2 := Point{1, 2}
	p3 := Point{1, 3}

fmt.Println("p1 == p2:", p1 == p2) // আউটপুট: p1 == p2: true
fmt.Println("p1 == p3:", p1 == p3) // আউটপুট: p1 == p3: false
}

উদাহরণে, p1 এবং p2 একই বিচিত্র মানের জন্য সমান ধরা হয়েছে। এবং p3 সমান p1 নয় কারন Y এর মান ভিন্ন।

8.2 স্ট্রাক্ট সমূলনীর অনুলিপি

গোতে, স্ট্রাক্ট ইনস্ট্যান্সগুলি অ্যাসাইনমেন্ট দ্বারা কপি করা যেতে পারে। এই কপি গভীর কপি হবা না হবা এটা স্ট্রাক্টের ভিতরের ফিল্ড এর টাইপের উপর নির্ভর করে।

যদি স্ট্রাক্টটি শুধুমাত্র মৌলিক ধরনের মান (যেমন int, string, ইত্যাদি) ধারণ করে, তা হবে গভীর কপি। যদি স্ট্রাক্টটি রেফারেন্স ধরনের মান (যেমন স্লাইস, ম্যাপ, ইত্যাদি) ধারণ করে, তা কপি হবে প্রশাল কপি, এবং মূল ইনস্ট্যান্স এবং নতুন কপি ইন্সট্যান্সগুলি রেফারেন্স ধরনের মানের মেমরি ভাগ করবে।

নিম্নলিখিত একটি স্ট্রাক্ট কপির উদাহরণ:

package main

import "fmt"

type Data struct {
Numbers []int
}

func main() {
// একটি Data স্ট্রাক্টের ইনস্ট্যান্স আরম্ভ করা
original := Data{Numbers: []int{1, 2, 3}}

// স্ট্রাক্ট কপি করা
copied := original

// কপিড স্লাইসের এলিমেন্ট গুলো পরিবর্তন করা
copied.Numbers[0] = 100

// মূল এবং কপিড ইন্সট্যান্সের এলিমেন্ট দেখা
fmt.Println("Original:", original.Numbers) // আউটপুট: Original: [100 2 3]
fmt.Println("Copied:", copied.Numbers) // আউটপুট: Copied: [100 2 3]
}

যেমন উপরের উদাহরণে, original এবং copied ইন্স্ট্যান্সগুলি একই স্লাইস ভাগ করে তা জানানো হয়েছে, তারাই স্লাইস ডেটা পরিবর্তন করলে তা ওপরিলোকিত ইন্স্ট্যান্সে আপডেট হবে।

এই সমস্যা থেকে বাঁচার জন্য, আপনি নতুন স্লাইসে কন্টেন্ট গুলি পুনরায় চিরকোনে করে গভীর কপি পেতে পারেন:

newNumbers := make([]int, len(original.Numbers))
copy(newNumbers, original.Numbers)
copied := Data{Numbers: newNumbers}

এভাবে, copied এ যে কোন পরিবর্তন original এ কোনও প্রভাব ফেলবে না।