---
title: "Golang Interface - Những lỗi sai thường gặp và giải pháp"
description: "Liệu bạn đã dùng Golang Interface đúng cách và hiệu quả? Bài viết này mình sẽ hướng dẫn chi tiết để các bạn dễ dàng sử dụng trong các service Golang nhé"
canonical: "https://200lab.io/blog/interface-trong-golang-cach-dung-chinh-xac"
published: "2023-06-13T03:35:00Z"
updated: "2026-08-21T07:17:54Z"
authors: ["Việt Trần"]
tags: ["Golang"]
reading_minutes: 8
access: "public"
---

Một trong những chủ đề hấp dẫn và làm đau đầu các **Gopher** nhập môn có lẽ chính là **Interface trong Golang**. **Interface** là một khái niệm không mới trong các ngôn ngữ lập trình phổ biến. Nhưng trong **Go** thì có cứ lạ làm sau ấy!

Liệu bạn đã dùng **Golang Interface** đúng cách và hiệu quả? Bài viết này **200Lab** sẽ hướng dẫn chi tiết để các bạn dễ dàng sử dụng trong các service Golang nhé.

## Định nghĩa Interface trong Golang

> An _interface type_ is defined as a set of method signatures. A value of interface type can hold any value that implements those methods - Nguyên văn từ [đây](<https://go.dev/tour/methods/9>).

**Interface** trong **Golang** là một kiểu được định nghĩa bởi tập hợp của các method (hàm trong Golang). **Interface** có thể chứa bất kỳ giá trị gì miễn là nó có implement các method này.

```Go
type Speaker interface {
    Speak() string
}
```

_Định nghĩa Stringer Interface trong Golang_

Đoạn code trên có thể được diễn giải rằng: Bất kỳ kiểu dữ liệu nào miễn là có method `Speak() string` (không tham số và trả về `string`) thì đều được gọi là `Speaker`.

## Cách sử dụng Interface trong Golang

### Implemented ngầm định

Interface trong Golang được implement một cách ngầm định (**implicitly**), nghĩa là không cần khai báo tường minh (**explicitly**) bằng cách từ khoá như "`implements`" hoặc dấu "`:`".

Ví dụ với interface `Speaker` ở trên, chúng ta sẽ khai báo một struct bất kỳ để implement Speaker như sau:

```Go
package main

import (
	"fmt"
)

type Speaker interface {
    Speak() string
}

type Foo struct{}

func (Foo) Speak() string {
	return "Hello, I am Foo"
}

func main() {
	var someSpeaker Speaker = Foo{}
	fmt.Println(someSpeaker.Speak())
}
```

_Run thử tại đây: https://play.golang.com/p/Fm7QaE6KgHd_

Đặc tính này khiến Interface trong Go rất dễ dùng nhưng lại cũng rắc rối cho các bạn mới, đặc biệt những bạn mới từ các ngôn ngữ hỗ trợ mạnh hướng đối tượng (**OOP**). Mình sẽ nói rõ hơn vấn đề này ở phần sau của bài viết.

### Empty Interface thay thế cho mọi kiểu dữ liệu

Hầu hết những người mới tìm hiểu ngôn ngữ Golang đều thắc mắc liệu có kiểu dữ liệu `any` để đại diện cho bất kỳ giá trị nào không?! Dù Golang không có keyword `any` tuy nhiên các bạn có thể dùng một Interface rỗng (hay Empty Interface) để làm được điều tương đương. Cú pháp: `interface{}`:

```Go
package main

import "fmt"

func main() {
	var i interface{}
	describe(i)

	i = 42
	describe(i)

	i = "hello"
	describe(i)
}

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}
```

_Nguồn: https://go.dev/tour/methods/14_

Trong ví dụ trên biến `i` là một `interface{}`. Vì interface này không hề định nghĩa và yêu cầu bất kỳ một method nào thành ra nó được dùng cho mọi kiểu. Cũng rất dễ hiểu và đảm bảo nguyên tắc implement ngầm định ở trên.

Một ứng dụng rất phổ biến với `interface{}` là sử dụng làm value cho kiểu dữ liệu `map` của **Golang**.

```Go
package main

import "fmt"

func main() {
    product := make(map[string]interface{}, 0)

    product["name"] = "Iphone 13 Pro Max"
    product["price"] = 31000000
    product["quantity"] = 40

    fmt.Println(product)
}
```

_Ứng dụng Interface trong map\[string\]interface{}_

Như vậy ta sẽ có một kiểu `map` có key luôn là `string` và value là bất kỳ kiểu dữ liệu nào cũng được.

### Ép kiểu dữ liệu Interface

Đôi lúc chúng ta sẽ có nhu cầu truy xuất về kiểu dữ liệu gốc để thực hiện các nghiệp vụ tương ứng. Điều này có thể thực hiện thông qua cơ chế `Type assertions` trong Golang:

```Go
package main

import "fmt"

func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)

	s, ok := i.(string)
	fmt.Println(s, ok)

	f, ok := i.(float64)
	fmt.Println(f, ok)

	f = i.(float64) // panic
	fmt.Println(f)
}
```

_Nguồn: https://go.dev/tour/methods/15_

`Type Assertions` trong Golang có thể vừa dùng để đưa interface về kiểu dữ liệu bên dưới nó (hoặc một Interface khác nếu thoả điều kiện) vừa dùng để check xem liệu đúng là kiểu dữ liệu muốn ép về hay không. Vì thế, nó có thể trả về một cặp kết quả theo thứ tự: kiểu dữ liệu, giá trị `true/false` thể hiện cho việc ép kiểu thành công hoặc thất bại.

Trong trường hợp bạn luôn dùng 2 biến trả về trong Type Assertions thì chương trình vẫn thực thi bình thường bất kể có thành công hay không. Tuy nhiên nếu chỉ dùng một biến sẽ bị **panic** ngay nếu thất bại.

## Những vấn đề thường gặp trong Interface Golang và cách giải quyết

Thường sẽ có 2 vấn đề mà các Gopher thường gặp:

### Nhầm lẫn giữa tham trị và tham chiếu (value type và pointer type)

Khi chúng ta khai báo method trên value type và pointer type, các method đó sẽ được gọi tường minh như bình thường. Nhưng với **Interface** thì sẽ có sự khác biệt:

```Go
package main

import (
	"fmt"
)

type Speaker interface {
	Speak() string
}

type Foo struct{}

func (Foo) Speak() string {
	return "Hello, I am Foo"
}

type Bar struct{}

func (*Bar) Speak() string {
	return "Hello, I am Bar"
}

func main() {
	var someSpeaker Speaker = Foo{}

	fmt.Println(someSpeaker.Speak())

	someSpeaker = &Foo{} // will be OK

	fmt.Println(someSpeaker.Speak())

	someSpeaker = Bar{} // PANIC HERE

	fmt.Println(someSpeaker.Speak())
}
```

_Run thử tại: https://play.golang.com/p/YX5Zghh2Ab4_

Các bạn thực thi code trên sẽ báo lỗi: "**Bar does not implement Speaker (Speak method has pointer receiver)**"

Trong ví dụ trên, struct `Foo` implement Speaker với một receiver là `value type`. Nhưng struct `Bar` lại implement Speaker với một receiver là `pointer type`. Kết quả là biến `someSpeaker` (có kiểu Speaker Interface) nhận được cho cả value và pointer cho `Foo` nhưng lại chỉ dùng với pointer `Bar` mà thôi.

### Interface nên được khai báo ở tại nơi sử dụng nó

Vấn đề này, 200Lab thấy ở nhiều bạn đến từ các ngôn ngữ OOP khác. Ở các ngôn ngữ này các bạn sẽ thường khai báo các Interfaces trong một package/folder nào đó rồi thực hiệm import để implement. Thói quen này sẽ ảnh hưởng không nhỏ và có thể dẫn đến lỗi `cycle import`.

#### Vấn đề:

Giả sử ở package a các bạn khai báo một Interface:

```
package a

type Speaker interface {
    Speak() string
}
```

Sau đó ở package b các bạn muốn dùng Interface này thì sẽ phải import A:

```
package b

import "a"

func someFunc(spk a.Speaker) {
	spk.Speak()
}
```

Thật ra khi chạy chương trình các bạn sẽ không gặp vấn đề gì cả. Nhưng nếu sau này package a có nhu cầu import package b thì chắc chắn không thể compile được do dính lỗi `cycle import` (các pakage import lẫn nhau).

#### Giải pháp

Để làm việc này tốt hơn, chúng ta chỉ cần thay đổi thói quen lại một tí: khai báo interface ngay tại nơi dùng nó. Việc này giúp code không cần import các package khác chỉ để lấy khai báo Interface và code sẽ dễ đọc hơn rất nhiều.

```Go
package b

type Speaker interface {
    Speak() string
}

func someFunc(spk Speaker) {
	spk.Speak()
}
```

_Khai báo Interface tại nơi sử dụng_

Dù `Speaker` ở `package a` giống y chang `package b` thì các bạn cũng đừng quan tâm nhé. Hãy nhớ rằng Golang không cần implement tường minh, chỉ cần có method `Speak() string` thì đã là `Speaker` ở cả 2 package.

### Các hàm/method (có thể) không nên trả về tường minh Interface

Ngoài ra các bạn có thể không cần phải return tường minh một Interface nếu không thực sự cần thiết.

```
package b

import "a"

type foo struct{} // mình cố tình private foo luôn nha

func (*foo) Speak() string {
	return "Hello, I am Foo"
}

// return tường minh Speaker Interface
func NewFoo() a.Speaker {
	return &foo{}
}
```

Như đoạn code trên các bạn sẽ thấy vì trả về tường minh Interface Speaker nên buộc phải import package có định nghĩa Speaker.

Các bạn có thể trả về như thế này nhé:

```
package b

type foo struct{}

func (*foo) Speak() string {
	return "Hello, I am Foo"
}

func NewFoo() *foo {
	return &foo{}
}
```

Và ở chỗ nào cần dùng đến `Speaker`, hay chính xác hơn là type nào có `Speak() string`, thì sẽ code như sau:

```
package main

import (
	"b"
	"fmt"
)

type Speaker interface {
	Speak() string
}

func demoSpeaker(spk Speaker) {
	fmt.Println(spk.Speak())
}

func main() {
	demoSpeaker(b.NewFoo()) // will be OK
}

```

## Lời kết

Hy vọng qua bài viết trên, 200Lab đã giúp các bạn hiểu được Interface Golang và cách dùng tốt hơn nhé. 200Lab sẽ có những demo Interface thực tế trong các service Golang: cho kiến trúc và Unit Test.

Nếu các bạn thực sự cảm thấy khó khăn trong việc tự học. Thậm chí đã làm được những service cơ bản nhưng vẫn chưa tự tin cho những phần nâng cao thì có thể tham khảo [khoá học Golang for Scalable Backend](<https://200lab.io/khoa-hoc/khoa-hoc-golang-food-delivery-backend>) tại **200Lab** nhé!

**Bài viết cùng chủ đề:**

- [Clean Architecture là gì](</blog/clean-architecture-uu-nhuoc-va-cach-dung-hop-ly>) - Ưu nhược và cách dùng hợp lý
- [Ứng dụng Clean Architecture](</blog/ung-dung-clean-architecture-service-golang-rest-api>)cho service Golang REST API
- [Lập trình REST API TODO List với Golang](</blog/lap-trinh-rest-api-todo-list-voi-golang>) - Từ UI tới triển khai
