Chào các bạn. Trong bài viết này mình sẽ cùng nhau tìm hiểu về interface trong ngôn ngữ lập trình Go. Các nội dung tìm hiểu gồm có:

  1. Cơ bản về interface trong Go.
  2. Demo interface trong Go.
  3. So sánh Pointer Receivers với Value Receivers.

Interface là gì?

Trước khi nói về code, chúng ta cùng tìm hiểu xem interface là gì? Nó mang lại cho chúng ta cái gì.

Có thể hiểu đơn giản: interface như là 1 thỏa thuận (contract) đặt ra để code của chúng ta phải tuân theo. Nó rất hiệu quả để định nghĩa ra những phụ thuộc trong code của chúng ta, giúp chúng ta rất dễ hình dung code sẽ như thế nào.

Ví dụ: Nếu tôi triển khai 1 Comment Service để giao tiếp với 1 database, tôi mong muốn rằng Comment Service này sẽ chứa 1 interface mô tả những hành động (method) cần có để thao tác với DB và sẽ làm việc với 1 loại database bất kì. Có thể hiểu đơn giản, khi thay đổi loại database khác nhau thì tôi không phải sửa nhiều những đoạn code phụ thuộc.

package comment

type Comment struct {
    ID string
    Slug string
    Author string
    Body string
}

// Interface mô tả hành động cần có của 1 comment store
type CommentStore interface {
    SaveComment(ctx context.Context, cmt Comment) error    //lưu comment xuống DB
}

// Comment Service chứa interface CommentStore định nghĩa bên trên
type Service struct {
    Store *CommentStore
}

func New(cmtStore *CommentStore) *Service {
    return &Service{
        Store: cmtStore,
    }
}

// Service có method AddComment cho phép chúng ta thêm comment mới.
func (s *Service) AddComment(ctx context.Context, cmt Comment) error {

    //Nghiệp vụ cần làm để thêm comment mới

    // Gọi method SaveComment của 1 triển khai cụ thể interface CommentStore
    return s.Store.SaveComment(ctx, cmt)
}

Cùng phân tích đoạn code trên nhé.

Hàm New trả về 1 new Service xử lý việc thêm comment vào trang. Service này sẽ xử lý tất cả những logic về nghiệp vụ để thêm những comment. Nhưng đặc biệt nó không chứa 1 logic cụ thể nào liên quan đến việc lưu comment vào database, chú ý điều này rất quan trọng!!!

Nhiệm vụ xử lý liên quan đến database sẽ được giao hẳn cho 1 package khác. Và được truyền vào Service qua hàm New (constructor).

Để có thể compile được code, chúng ta bắt buộc phải implement cụ thể 1 database mà interface CommentStore đã định nghĩa.

Chú ý: Đoạn code trên là 1 ví dụ mà chúng ta ưu tiên sử dụng 1 thuộc tính trừu tượng (interface) hơn là 1 triển khai cụ thể (kế thừa). Tư duy này nên có ở toàn bộ hệ thống Go của chúng ta.

Định nghĩa 1 interface

Cú pháp:

type NAME interface{}
type Employee interface {}

Chúng ta định nghĩa method GetName() để trả về name của Employee.

type Employee interface {
    GetName() string
}

Tiếp theo, nếu implement interface này chúng ta có thể tạo 1 'struct' và triển khai method GetName

type Engineer struct {
    Name string
}

func (e *Engineer) GetName() string {
    return e.Name
}

Tiếp theo hãy viết 1 func để in thông tin name của Employee

func PrintDetails(e Employee) {
    fmt.Println(e.GetName())
}

Bây giờ chúng ta có thể truyền Engineer vào tham số của hàm PrintDetails (vì Engineer đã implement Employee)

package main

import "fmt"

type Employee interface {
    GetName() string
}

type Engineer struct {
    Name string
}

func (e *Engineer) GetName() string {
    return e.Name
}

func PrintDetails(e Employee) {
    fmt.Println(e.GetName())
}

func main() {
    en := Engineer{"Bob"}
    PrintDetails(&en) // GetName method has pointer receiver
}

Phân biệt Pointer Receiver với Value Receiver

Để phân biệt pointer receivers với value receivers, ban đầu có thể gây khó khăn cho việc hiểu 2 khái niệm này. Nhưng phân biệt 2 khái niệm này là điều quan trọng.

Trước khi triển khai 1 method, chúng ta cần định nghĩa type receiver trước tên của method.

func (p *Pointer) GetName() string {
    fmt.Println("this is a pointer receiver method")
    return "Elliot"
}

Pointer receiver sẽ sử dụng con trỏ để trỏ đến giá trị gốc P. Do đó, nó dùng để thay đổi giá trị của đối tượng P.

Nếu là value receiver, nó sẽ làm việc trên đối tượng được copy từ Value, do đó không thay đổi giá trị của đối tượng Value.

func (v Value) GetName() string {
    fmt.Println("this is a value receiver method")
    return "Elliot"
}

Dựa theo bài viết gốc: https://tutorialedge.net/courses/go-beginners-guide/30-interfaces-in-go/