Closure trong Golang

Trong bài viết này, chúng ta sẽ tìm hiểu về Closure, cách thức và lý do tại sao Closure được giới thiệu ở Golang.
Giới thiệu
Trước khi tìm hiểu về Closure, trước tiên chúng ta hãy cố gắng hiểu hàm ẩn danh (Anonymous Functions) là gì bằng cách đi qua vài ví dụ.
Anonymous Functions là gì ?
Anonymous Functions là những hàm không có tên hàm hoặc hàm cần tên.
Ví dụ: Trước tiên chúng ta hãy xem 1 Regular function:
func send(message string) {
fmt.Println(message)
}Bạn có thể gọi nó như thế này:
send("hii,welcome to scaler")Output:
hii, welcome to scalerBây giờ, hãy thử chuyển đổi Regular function này thành Anonymous Function:
package main
import "fmt"
// Main function
func main() {
// Display the message
// with method calling
func(message string) {
fmt.Println(message)
}("Hi, it's Scaler!")
}Output:
Hi, it's Scaler!Bạn có thể thấy rằng cả hai đều hoạt động hoàn toàn tốt ! Anonymous Function rất hữu ích khi bạn muốn định nghĩa một hàm inline mà không cần phải đặt tên cho nó. Lợi ích của Anonymous Function là nó không cần phải được lưu giữ trong một tệp riêng biệt. Việc sử dụng các Anonymous Function cũng giảm thiểu số lượng file code cần thiết cho một chương trình, điều này có thể đơn giản hóa đáng kể chương trình của bạn.
Closure trong Golang
Closure, là một dạng của Anonymous Function đề cập đến việc các biến được chỉ định bên ngoài hàm đó. Nó tương đương với việc truy cập các biến toàn cục tồn tại trước khi hàm được khai báo. Nó hoạt động tương tự như một regular function và thực hiện tất cả những việc mà bất kỳ function nào thực hiện.
Following Syntax:
func(data data-type) data-type {
//expression 1
//expression 2
//expression 3
}
Kiểm tra một vài ví dụ bên dưới
package main
import "fmt"
// outer function
func message() func() string {
// variable defined
text := "Scaler"
// return a nested anonymous function without a name
return func() string {
text = "Hi, " + text
return text
}
}
func main() {
// call the outer function
mssg := message()
// call the inner function
fmt.Println(mssg())
}Lưu ý: Closure hoạt động như một hàm lồng nhau (nested function), giúp truy cập vào các biến nằm bên ngoài function bất kể hàm bên ngoài chứa nó đã kết thúc hay chưa.
- Trong đoạn code trên, chúng ta đã tạo function có tên message() trả về một hàm ẩn danh . Sau đó, chúng ta gán nó cho một biến nào đó trong trường hợp của chúng ta là msg và bất cứ khi nào chúng ta cố gắng gọi hàm này trong main function bằng cách gọi msg(). Lưu ý vào lúc này, biến text đã bị hủy và function message đã hoàn thành.
- Tuy nhiên, khi chúng ta gọi fmt.Println(mssg()) chúng ta vẫn có thể truy cập vào biến text.
- Do nested function hiện đóng vai trò là một closure, nên nó sẽ đóng các biến external scope vào scope của nó ngay cả sau khi hàm bọc bên ngoài được hoàn thành.
Output:
Hi, scalerVí dụ cơ bản để in ra số chẵn:
package main
import "fmt"
func calculate() func() int {
// declare variable initially with 0
num := 0
// returns inner function
return func() int {
// add the num + 2 everytime to get even numbers only
num = num + 2
return num
}
}
func main() {
// call the outer function
even := calculate()
// call the inner function
fmt.Println(even())
fmt.Println(even())
fmt.Println(even())
fmt.Println(even())
fmt.Println(even())
// call the outer function again
even2 := calculate()
fmt.Println(even2())
}
Output:
2
4
6
8
10
2Chạy đoạn code trên! Trong đoạn code trên, hàm bên ngoài thực thi calculate() và trả về một closure và gán vào biến even. Điều này cho phép chúng ta truy cập vào biến num của hàm calculate() ngay cả sau khi hàm bên ngoài hoàn thành.
Một ví dụ khác với Counter:
// Golang program to illustrate how
// to create data isolation
package main
import "fmt"
// newCounter function to
// isolate the global variable
func newCounter() func() int {
Scaler := 0
//inner function
return func() int {
Scaler++
return Scaler
}
}
func main() {
// newCounter function is
// assigned to a variable
counter := newCounter()
// invoke counter
fmt.Println(counter())
// invoke counter
fmt.Println(counter())
fmt.Println(counter())
fmt.Println(counter())
}
Output:
1
2
3
4- Trong đoạn code trên, Closure có thể truy cập đến biến Scaler ngay cả sau khi hàm newCounter() đã chạy xong, và không có đoạn code nào khác ngoài method newCounter() có quyền truy cập vào biến này.
- Đó là cách mà data persistency (được duy trì) giữa các lệnh gọi hàm đồng thời và giúp tách biệt dữ liệu khỏi các đoạn code khác.
Closures giúp data được cách ly
- Các Closure độc lập với nhauc và không ảnh hưởng lẫn nhau.
- Tính năng này giúp chúng ta thực hiện việc tách biệt dữ liệu hoặc làm cho chúng có trạng thái riêng.
For Example:
package main
import (
"fmt"
)
func show() func() int {
i := 0
// inner function
return func() int {
i += 10
return i
}
}
func main() {
// returns a closure
a := show()
fmt.Println(a()) // 10
// returns a new closure
b := show()
fmt.Println(b()) // 10
b() // call a again
// b had an extra call that made it 30 while a is still 20
fmt.Println(a()) // prints 20
fmt.Println(b()) // prints 30
}
Output:
10
10
20
30Trong đoạn code trên, a và b tách biệt về trạng thái. Điều này trở nên rõ ràng khi chúng ta gọi một function để làm cho trạng thái thay đổi. Đây là lý do tại sao closure rất hữu ích.
Another Example:
package main
import "fmt"
func shownum() func() int {
number := 0
// inner function
return func() int {
number++
return number
}
}
func main() {
// returns a closure
num1 := shownum()
fmt.Println(num1())
fmt.Println(num1())
fmt.Println(num1())
// returns a new closure
num2 := shownum()
fmt.Println(num2())
fmt.Println(num2())
}
Output:
1
2
3
1
2- Trong đoạn đoạn code trên, hàm showsum trả về một anonymous function làm tăng number lên 1 đơn vị.
- Trong hàm chính ,chúng ta gọi showum bằng num1 và num2, trước tiên chúng ta gọi closure bằng num1, tăng 3 lần 1,2,3, sau đó gọi lại closure bằng num2, tăng 1,2 và điểm chính cần lưu ý là số không tiếp tục tăng từ 3 mà nó lại bắt đầu từ 1, cho thấy tính năng cách ly về mặt dữ liệu được triển khai trong code bằng cách sử dụng một closure.
Ưu điểm của closure
Dưới đây là một số lợi ích của closure
- Chúng cho phép bạn tương tác với các biến nằm trong execution contex.
- Các biến trong closure có thể hỗ trợ bạn trong việc duy trì trạng thái để sử dụng sau này
- Chịu trách nhiệm đóng gói dữ liệu.
- Chúng tạo điều kiện thuận lợi cho việc loại bỏ đoạn code trùng lặp.
- Chúng cũng giúp tạo ra các đoạn generic code.
Nhược điểm của closure
- Nhược điểm có thể được kể đến là nếu có quá nhiều closure trong code của bạn, nó có thể làm chậm ứng dụng của bạn.
- Một vấn đề khác với closure là không có cơ chế dọn rác (garbage collection) cho các biến được reference trong closure.
Khi nào nên sử dụng clousure ?
- Sẽ rất tốt nếu phép tính, các biến được thêm vào hoặc định nghĩa bên trong hàm và nó sẽ giúp tiết kiệm bộ nhớ cho chúng ta vì mỗi biến đều chiếm một lượng tài nguyên nhất định trong ngôn ngữ lập trình nhưng như chúng ta có thể thấy, chúng ta không đặt bất kỳ tên nào cho closure. Do đó, nó sẽ không chiếm bất kỳ lượng tài nguyên nào cho đến khi nhận được lệnh gọi để thực hiện chức năng và tính toán của mình, nếu muốn thì chúng ta có thể phân bổ closure cho bất kỳ biến nào và gọi hàm bằng tên biến.
- Go closure cũng sẽ có ích khi chúng ta muốn tạo ra 1 middleware để ghi nhật ký, xử lý lỗi hoặc nén dữ liệu.
Phần kết luận
- Trong bài viết này, chúng ta đã thấy anonymous function là hàm không có tên.
- Chúng ta cũng đã biết về closure trong golang là gì. Closure là một hàm refers đến các biến nằm trong scope của hàm định nghĩa ra nó.
- Nó cũng giúp bạn viết clean code và generic code.