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).
مزایا
گولنگ از وراثت پرهیز کرده تا وابستگی کم شود در عوض امبدینگ باعث میشه reuse داشته باشیم و چند ار استفاده کنیم و همچنین پلی مورفیسم رو با اینترفیس داشته باشیم
Polymorphism در Go
چندریختی (Polymorphism) یعنی چند کلاس میتوانند متدهای همنام داشته باشند اما رفتار متفاوتی ارائه دهند.
در Go با استفاده از Interface قابل پیادهسازی است.
در زبان های کلاسیک به دلیل اینکه فرزندان ، والدین خود را overwrite میکنند ، میشه از اون مفهوم استفاده کرد اما در گولنگ فرزندان تنها می توانند shadow کنند در نتیجه باید از اینترفیس استفاده کرد
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)
}حالا کی از embed استفاده کنیم و کیا از field
-
وقتی تنها از فیلد های سک استراکت می خوایم استفاده کنیم باید از field استفاده کنیم
-
اما اگر بخواهیم از متد هاشم استفاده کنیم باید از embed استفاده کنیم
-
در مجموع امبدینگ انکپسولیشن و اینترنال متد رو از بین میبره
سوال is-a و has-a چیان؟
- is-a:
رابطه نوعی «این یکِ آن است» — معمولاً در زبانهای OOP با وراثت نمایش داده میشود. مثال: Car is-a Vehicle.
- has-a:
رابطهٔ مالکیت/ترکیب — یک شی شامل یا نگهدارندهٔ شیء دیگری است. مثال: Car has-a Engine (در Go با composition/embedding یا معمولاً با فیلد نشان داده میشود).
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 را انجام داد:
There is no override in Go — only shadowing.
Interface + embedding = Go-style polymorphism
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).