Inheritance vs Embedding in Go
در Go مفهومی به نام وراثت کلاسها وجود ندارد. به جای آن از struct embedding استفاده میکنیم.
مثال ساده وراثت در Python
# Base class
class Animal:
def speak(self):
return "Animal speaks"
# Child class inheriting from Animal
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
# Usage
my_dog = Dog()
print(my_dog.speak()) # Output: Woof!
my_cat = Cat()
print(my_cat.speak()) # Output: Meow!در این مثال کلاسهای فرزند، متد کلاس پدر را overwrite کردند.
Embedding در Go
package main
import "fmt"
// Base struct
type Animal struct{}
func (a Animal) Speak() string {
return "Animal speaks"
}
// Dog struct embedding Animal
type Dog struct {
Animal
}
func (d Dog) Speak() string {
return "Woof!"
}
// Cat struct embedding Animal
type Cat struct {
Animal
}
func (c Cat) Speak() string {
return "Meow!"
}
func main() {
myDog := Dog{}
fmt.Println(myDog.Speak()) // Output: Woof!
myCat := Cat{}
fmt.Println(myCat.Speak()) // Output: Meow!
} محدودیتها: Multiple Inheritance
Go از وراثت چندگانه پشتیبانی نمیکند.
برای شبیهسازی، چند struct را در یک struct جدید embedding میکنیم:
package main
import "fmt"
// Base struct 1
type Flyer struct{}
func (f Flyer) Fly() string { return "Flying" }
func (f Flyer) Action() string { return "Flying Action" }
// Base struct 2
type Swimmer struct{}
func (s Swimmer) Swim() string { return "Swimming" }
func (s Swimmer) Action() string { return "Swimming Action" }
// Duck embedding Flyer and Swimmer
type Duck struct {
Flyer
Swimmer
}
func (d Duck) Quack() string { return "Quacking" }
func main() {
duck := Duck{}
fmt.Println(duck.Fly()) // Output: Flying
fmt.Println(duck.Swim()) // Output: Swimming
fmt.Println(duck.Quack()) // Output: Quacking
// Ambiguous method call:
// fmt.Println(duck.Action()) // ERROR: ambiguous selector
}اگر متدی با نام یکسان در دو struct والد وجود داشته باشد، دسترسی مستقیم به آن در struct فرزند امکانپذیر نیست (
ambiguous selector).
Polymorphism در Go
چندریختی (Polymorphism) یعنی چند کلاس میتوانند متدهای همنام داشته باشند اما رفتار متفاوتی ارائه دهند.
در Go با استفاده از Interface قابل پیادهسازی است.
package main
import "fmt"
type Shape interface {
Area() float64
}
type Rectangle struct {
width, height float64
}
func (r Rectangle) Area() float64 { return r.width * r.height }
type Circle struct {
radius float64
}
func (c Circle) Area() float64 { return 3.14 * c.radius * c.radius }
func CalcArea(shapes ...Shape) {
for _, shape := range shapes {
fmt.Println(shape.Area())
}
}
func main() {
r := Rectangle{10, 5}
c := Circle{5}
CalcArea(r, c)
}Encapsulation (Public vs Private)
در Go میتوان با capitalization دسترسی به دادهها را محدود کرد:
- نام فیلد یا متد با حرف بزرگ → Public
- نام فیلد یا متد با حرف کوچک → Private
هدف این است که کاربران از بیرون پکیج به دادهها دسترسی مستقیم نداشته باشند، نه اینکه دسترسی کاملاً قطع شود.
Response Envelope و Wrapper
برای پاسخدهی استاندارد (مثلاً status, code) میتوان از wrapper struct با فیلد interface برای body استفاده کرد:
func (x *PushDataV3ApiWrapper) GetPublicLimitDepths() *PublicLimitDepthsV3Api {
if x != nil {
if x, ok := x.Body.(*PushDataV3ApiWrapper_PublicLimitDepths); ok {
return x.PublicLimitDepths
}
}
return nil
}روش مشابه برای دیگر پاسخها نیز قابل استفاده است.
Generics
استفاده از Generics باعث میشود Unmarshal JSON راحتتر انجام شود:
type APIResponse[T any] struct {
Code string `json:"code"`
Data T `json:"data"`
}
func ParseDepositHistory(jsonBytes []byte) (*APIResponse[DepositHistoryData], error) {
var resp APIResponse[DepositHistoryData]
err := json.Unmarshal(jsonBytes, &resp)
if err != nil { return nil, err }
return &resp, nil
}خیلی مسخرست اما جنریک ها نمی تونن تا پارامتر های ریسیور باشن اما می تونن پارامتر متد باشن
func ListContains[T comparable](needle T, haystack []T) bool {
}
// compiler error: method must have no type parameters
func (u Utils) ListContains[T comparable](needle T, haystack []T) bool {
}Overriding (Shadow Method)
با embedding و پیادهسازی متد یک اینترفیس، میتوان تکنیک Overriding را انجام داد:
package main
import "fmt"
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * 3.14 * c.Radius }
func (c Circle) Action() string { return "Circle Action" }
func main() {
c := Circle{Radius: 5}
fmt.Println(c.Action()) // Output: Circle Action
}اگر متد موردنظر در Interface تعریف نشده باشد، دسترسی به آن با استفاده از نوع Interface امکانپذیر نیست.
Abstraction
Abstraction در Go از طریق Interface پیادهسازی میشود و امکان جداسازی رفتار و پیادهسازی فراهم است.
Decorator
Go از runtime function modification یا first-class decorator support پشتیبانی نمیکند، ولی میتوان الگوی Decorator را با توابع پیاده کرد:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Request received")
next.ServeHTTP(w, r)
})
}یا برای توابع ساده:
type Operation func(int, int) int
func logDecorator(fn Operation) Operation {
return func(a, b int) int {
start := time.Now()
defer fmt.Printf("Execution time: %v\n", time.Since(start))
fmt.Printf("Calling function with (%d, %d)\n", a, b)
return fn(a, b)
}
}سایر محدودیتها در Go
- Type Inheritance: وراثت مستقیم وجود ندارد، باید از composition و اینترفیسها استفاده کرد.
- Operator Overloading: تغییر عملگرهای محاسباتی (
+,-,*, …) پشتیبانی نمیشود. - Pointer Arithmetic: عملیات ریاضی مستقیم روی pointerها امکانپذیر نیست.
- Struct Type in consts: نمیتوان struct را بهصورت ثابت تعریف کرد (برخلاف basic types).