Interface
Trong Go interface là một loại trừu tượng dùng để định nghĩa một nhóm chữ ký phương thức mà không cung cấp triển khai phương thức. Triết lý cốt lõi của interface là mô tả hành vi còn triển khai hành vi cụ thể do loại thực hiện interface cung cấp. Interface trong Go được sử dụng rộng rãi để thực hiện đa hình ghép nối lỏng và tái sử dụng mã.
Khái niệm
Lịch sử phát triển của Go về interface có một bước ngoặt ở Go1.17 và trước đó định nghĩa của Go về interface trong sổ tay tham khảo là một tập hợp các phương thức.
An interface type specifies a method set called its interface.
Định nghĩa của việc thực hiện interface là
A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface
Dịch ra là khi tập phương thức của một loại là tập cha của tập phương thức của một interface và giá trị của loại đó có thể được lưu trữ bởi biến của loại interface đó thì gọi là loại đó thực hiện interface đó.
Tuy nhiên ở Go1.18 định nghĩa về interface đã thay đổi interface được định nghĩa là một tập hợp các loại.
An interface type defines a type set.
Định nghĩa của việc thực hiện interface là
A variable of interface type can store a value of any type that is in the type set of the interface. Such a type is said to implement the interface
Dịch ra là khi một loại nằm trong tập loại của một interface và giá trị của loại đó có thể được lưu trữ bởi biến của loại interface đó thì gọi là loại đó thực hiện interface đó. Và còn đưa ra định nghĩa bổ sung như sau.
Khi các tình huống sau có thể nói là loại T thực hiện interface I
- T không phải là interface và là một phần tử trong tập loại của interface I
- T là một interface và tập loại của T là tập con của tập loại của interface I
Nếu T thực hiện một interface thì giá trị của T cũng thực hiện interface đó.
Thay đổi lớn nhất của Go ở 1.18 là thêm generic định nghĩa interface mới là để phục vụ cho generic nhưng không ảnh hưởng gì đến việc sử dụng interface trước đó đồng thời interface cũng được chia thành hai loại
- Interface cơ bản (
Basic Interface) chỉ chứa tập phương thức là interface cơ bản - Interface chung (
General Interface) chỉ cần chứa tập loại là interface chung
Tập phương thức là gì tập phương thức là một tập hợp các phương thức tương tự tập loại là một tập hợp các loại.
TIP
Bạn có thể cảm thấy đoạn khái niệm này rất khó hiểu nhưng thực tế bạn hoàn toàn không cần hiểu những thứ trên.
Interface cơ bản
Đã đề cập ở trên interface cơ bản là tập phương thức là một tập hợp các phương thức.
Khai báo
Trước tiên xem interface trông như thế nào.
type Person interface {
Say(string) string
Walk(int)
}Đây là một interface Person có hai phương thức được phơi bày ra ngoài là Walk và Say trong interface tên tham số của hàm không còn quan trọng nữa tất nhiên nếu muốn thêm tên tham số và tên giá trị trả về cũng được phép.
Khởi tạo
Chỉ có interface thì không thể khởi tạo được vì nó chỉ là một bộ quy phạm không có triển khai cụ thể nhưng có thể được khai báo.
func main() {
var person Person
fmt.Println(person)
}Kết quả xuất
<nil>Thực hiện
Trước tiên xem một ví dụ một công ty xây dựng muốn một loại cần cẩu đặc biệt nên đưa ra quy phạm đặc biệt và bản vẽ của cần cẩu và chỉ rõ cần cẩu nên có chức năng nâng và cẩu hàng công ty xây dựng không phụ trách chế tạo cần cẩu chỉ đưa ra một bộ quy phạm đây gọi là interface nên công ty A nhận đơn hàng dựa trên kỹ thuật độc quyền của công ty mình chế tạo ra cần cẩu tuyệt thế và giao cho công ty xây dựng công ty xây dựng không quan tâm dùng kỹ thuật gì để thực hiện cũng không quan tâm cần cẩu tuyệt thế là gì chỉ cần có thể nâng và cẩu hàng là được chỉ coi như một máy cẩu bình thường để dùng cung cấp chức năng cụ thể theo quy phạm của interface đây gọi là thực hiện.Sử dụng chức năng theo quy phạm của interface che giấu triển khai bên trong đây gọi là lập trình hướng interface. Qua một thời gian cần cẩu tuyệt thế gặp sự cố công ty A cũng chạy mất nên công ty B dựa trên quy phạm chế tạo một máy cẩu vô địch khủng hơn do cũng có chức năng nâng và cẩu hàng có thể kết nối liền mạch với cần cẩu tuyệt thế không ảnh hưởng đến tiến độ xây dựng công trình được hoàn thành suôn sẻ triển khai bên trong thay đổi nhưng chức năng không đổi không ảnh hưởng đến việc sử dụng trước đó có thể thay thế tùy ý đây là lợi ích của lập trình hướng interface.
Tiếp theo sẽ dùng Go để mô tả tình huống trên
// Interface cần cẩu
type Crane interface {
JackUp() string
Hoist() string
}
// Cần cẩu A
type CraneA struct {
work int // các trường bên trong khác nhau đại diện cho chi tiết bên trong khác nhau
}
func (c CraneA) Work() {
fmt.Println("Sử dụng kỹ thuật A")
}
func (c CraneA) JackUp() string {
c.Work()
return "jackup"
}
func (c CraneA) Hoist() string {
c.Work()
return "hoist"
}
// Cần cẩu B
type CraneB struct {
boot string
}
func (c CraneB) Boot() {
fmt.Println("Sử dụng kỹ thuật B")
}
func (c CraneB) JackUp() string {
c.Boot()
return "jackup"
}
func (c CraneB) Hoist() string {
c.Boot()
return "hoist"
}
type ConstructionCompany struct {
Crane Crane // Chỉ lưu trữ cần cẩu theo loại Crane
}
func (c *ConstructionCompany) Build() {
fmt.Println(c.Crane.JackUp())
fmt.Println(c.Crane.Hoist())
fmt.Println("Hoàn thành xây dựng")
}
func main() {
// Sử dụng cần cẩu A
company := ConstructionCompany{CraneA{}}
company.Build()
fmt.Println()
// Thay đổi cần cẩu B
company.Crane = CraneB{}
company.Build()
}Kết quả xuất
Sử dụng kỹ thuật A
jackup
Sử dụng kỹ thuật A
hoist
Hoàn thành xây dựng
Sử dụng kỹ thuật B
jackup
Sử dụng kỹ thuật B
hoist
Hoàn thành xây dựngTrong ví dụ trên có thể quan sát thấy việc thực hiện interface là ẩn tương ứng với định nghĩa của Go về việc thực hiện interface cơ bản tập phương thức là tập cha của tập phương thức của interface nên trong Go để thực hiện một interface không cần từ khóa implements để chỉ định rõ ràng cần thực hiện interface nào chỉ cần thực hiện tất cả các phương thức của một interface đó là thực hiện interface đó. Sau khi có thực hiện có thể khởi tạo interface bên trong struct công ty xây dựng khai báo một biến thành viên loại Crane có thể lưu trữ tất cả các giá trị thực hiện interface Crane do là biến loại Crane nên các phương thức có thể truy cập chỉ có JackUp và Hoist các phương thức khác bên trong như Work và Boot đều không thể truy cập.
Đã đề cập trước đó bất kỳ loại tùy chỉnh nào cũng có thể có phương thức nên theo định nghĩa của việc thực hiện bất kỳ loại tùy chỉnh nào cũng có thể thực hiện interface dưới đây đưa ra vài ví dụ đặc biệt.
type Person interface {
Say(string) string
Walk(int)
}
type Man interface {
Exercise()
Person
}Tập phương thức của interface Man là tập cha của Person nên Man cũng thực hiện interface Person nhưng điều này giống như "kế thừa" hơn.
type Number int
func (n Number) Say(s string) string {
return "bibibibibi"
}
func (n Number) Walk(i int) {
fmt.Println("can not walk")
}Loại cơ sở của loại Number là int mặc dù điều này trông rất kinh dị trong các ngôn ngữ khác nhưng tập phương thức của Number quả thực là tập cha của Person nên cũng tính là thực hiện.
type Func func()
func (f Func) Say(s string) string {
f()
return "bibibibibi"
}
func (f Func) Walk(i int) {
f()
fmt.Println("can not walk")
}
func main() {
var function Func
function = func() {
fmt.Println("do somthing")
}
function()
}Tương tự loại hàm cũng có thể thực hiện interface.
Interface rỗng
type Any interface{
}Interface Any không có tập phương thức bên trong theo định nghĩa của việc thực hiện tất cả các loại đều là thực hiện của interface Any vì tập phương thức của tất cả các loại đều là tập cha của tập rỗng nên interface Any có thể lưu trữ giá trị của bất kỳ loại nào.
func main() {
var anything Any
anything = 1
println(anything)
fmt.Println(anything)
anything = "something"
println(anything)
fmt.Println(anything)
anything = complex(1, 2)
println(anything)
fmt.Println(anything)
anything = 1.2
println(anything)
fmt.Println(anything)
anything = []int{}
println(anything)
fmt.Println(anything)
anything = map[string]int{}
println(anything)
fmt.Println(anything)
}Kết quả xuất
(0xe63580,0xeb8b08)
1
(0xe63d80,0xeb8c48)
something
(0xe62ac0,0xeb8c58)
(1+2i)
(0xe62e00,0xeb8b00)
1.2
(0xe61a00,0xc0000080d8)
[]
(0xe69720,0xc00007a7b0)
map[]Thông qua kết quả xuất sẽ phát hiện kết quả của hai loại xuất không giống nhau thực tế bên trong interface có thể coi là một tuple gồm (val,type) type là loại cụ thể khi gọi phương thức sẽ gọi giá trị cụ thể của loại cụ thể.
interface{}Đây cũng là một interface rỗng nhưng là một interface rỗng ẩn danh trong phát triển thường sử dụng interface rỗng ẩn danh để biểu thị nhận giá trị của bất kỳ loại nào ví dụ như sau
func main() {
DoSomething(map[int]string{})
}
func DoSomething(anything interface{}) interface{} {
return anything
}Trong các cập nhật sau này Go đã đưa ra một cách giải quyết khác để thuận tiện có thể sử dụng any để thay thế interface{} cả hai hoàn toàn tương đương vì cái trước chỉ là một bí danh loại như sau
type any = interface{}Khi so sánh interface rỗng sẽ so sánh loại cơ sở của nó nếu loại không khớp thì là false sau đó mới là so sánh giá trị ví dụ
func main() {
var a interface{}
var b interface{}
a = 1
b = "1"
fmt.Println(a == b)
a = 1
b = 1
fmt.Println(a == b)
}Kết quả xuất là
false
trueNếu loại cơ sở không thể so sánh được thì sẽ panic đối với Go tình trạng có thể so sánh được của loại dữ liệu built-in như sau
| Loại | Có thể so sánh | Cơ sở |
|---|---|---|
| Loại số | Có | Giá trị có bằng nhau không |
| Loại chuỗi | Có | Giá trị có bằng nhau không |
| Loại mảng | Có | Tất cả các phần tử của mảng có bằng nhau không |
| Loại slice | Không | Không thể so sánh |
| Struct | Có | Giá trị các trường có bằng nhau không |
| Loại map | Không | Không thể so sánh |
| Channel | Có | Địa chỉ có bằng nhau không |
| Con trỏ | Có | Địa chỉ lưu trữ trong con trỏ có bằng nhau không |
| Interface | Có | Dữ liệu lưu trữ bên dưới có bằng nhau không |
Trong Go có một loại interface chuyên dụng để đại diện cho tất cả các loại có thể so sánh được tức comparable
type comparable interface{ comparable }TIP
Nếu cố gắng so sánh loại không thể so sánh được thì sẽ panic
Interface chung
Interface chung là để phục vụ cho generic chỉ cần nắm vững generic là nắm vững interface chung vui lòng chuyển đến Generic
