Go – Phương thức

Go không phải là ngôn ngữ lập trình hướng đối tượng. Nó không có class, object và kế thừa. Tuy nhiên, Go có kiểu dữ liệu và có thể định nghĩa phương thức trong kiểu dữ liệu. Điều này cho phép lập trình hướng đối tượng trong Go.

Phương thức chỉ là một hàm với một kiểu receiver đặc biệt giữa từ khoá func và tên phương thức. Tham số recevier có tên và kiểu.

func (recevier Kiểu) TênPhươngThức(ThamSố) (KiểuTrảVề) {
}

Tham số receiver có thể là kiểu struct hoặc không.

Hãy cùng xem ví dụ sau để hiểu về cách định nghĩa một phương thức trên một kiểu và làm thế nào để gọi phương thức như vậy

package main

import (
  "fmt"
)

// Kiểu Struct - `Point`
type Point struct {
	X, Y float64
}

// Phương thức với receiver `Point`
func (p Point) IsAbove(y float64) bool {
	return p.Y > y
}

func main() {
	p := Point{2.0, 4.0}

	fmt.Println("Point : ", p)

	fmt.Println("Is Point p located above the line y = 1.0 ? : ", p.IsAbove(1))
}

Kết quả

Point :  {2 4}
Is Point p located above the line y = 1.0 ? :  true

Chú ý cách chúng ta gọi phương thức IsAbove() trên instance Point p. Nó chính xác giống như cách bạn gọi các phương thức trong các ngôn ngữ lập trình hướng đối tượng.

Phương thức là hàm

Do phương thức chỉ là một hàm với một tham số receiver. Chúng ta có thể viết ví dụ trên sử dụng một hàm bình thường như sau

package main

import (
  "fmt"
)

// Kiểu Struct - `Point`
type Point struct {
	X, Y float64
}

func IsAboveFunc(p Point, y float64) bool {
	return p.Y > y
}

/*
So sánh hàm bên trên với phương thức tương tứng -
func (p Point) IsAbove(y float64) bool {
	return p.Y > y
}
*/

func main() {
	p := Point{2.5, -3.0}

	fmt.Println("Point : ", p)

	fmt.Println("Is Point p located above the line y = 1.0 ? : ", IsAboveFunc(p, 1))
}

Tại sao sử dụng phương thức thay cho hàm

Phương thức giúp cho chúng ta viết code theo kiểu hướng đối tượng. Gọi các phương thức là dễ đọc và dễ hiểu hơn gọi hàm.

Ngoài ra, giúp chúng ta tránh bị xung đột trong cách đặt tên. Bởi vì một phương thức sẽ liên quan đến receiver cụ thể. Chúng ta có thể có các phương thức trùng tên nhưng khác kiểu receiver.

package main

import (
  "fmt"
  "math"
)

type ArithmeticProgression struct {
	A, D float64
}

// Phương thức với receiver `ArithmeticProgression`
func (ap ArithmeticProgression) NthTerm(n int) float64 {
	return ap.A + float64(n-1)*ap.D
}

type GeometricProgression struct {
	A, R float64
}

// Phương thức với receiver `GeometricProgression`
func (gp GeometricProgression) NthTerm(n int) float64 {
	return gp.A * math.Pow(gp.R, float64(n-1))
}

func main() {
	ap := ArithmeticProgression{1, 2} // AP: 1 3 5 7 9 ...
	gp := GeometricProgression{1, 2}  // GP: 1 2 4 8 16 ...

	fmt.Println("5th Term of the Arithmetic series = ", ap.NthTerm(5))
	fmt.Println("5th Term of the Geometric series = ", gp.NthTerm(5))
}

Kết quả

5th Term of the Arithmetic series =  9
5th Term of the Geometric series =  16

Phương thức với con trỏ receiver.

Tất cả các ví dụ trước chúng ta đều nhìn thấy receiver giá trị.

Với receiver giá trị, phương thức hoạt động dựa trên bản sao của giá trị được truyền vào cho nó. Do đó bất kỳ thay đổi nào bên trong receiver đều không thấy được ở phía gọi.

Go cho phép chúng ta định nghĩa phương thức với  receiver con trỏ.

// Phương thức với receiver con trỏ
func (receiver *Kiểu) TênPhươngThức(ThamSố) (KiểuTrảVề) {
}

Phương thức với receiver con trỏ có thể thay đổi giá trị mà trỏ đến receiver. Những thay đổi được thấy phía bên gọi của phương thức.

package main

import (
  "fmt"
)

type Point struct {
	X, Y float64
}

/* 
  Dịch Point hiện tại, tại vị trí (X,Y), bằng dx dọc theo trục x và dy dọc theo trục y 
  Nên vị trí hiện tại (X+dx,Y+dy).
*/
func (p *Point) Translate(dx, dy float64) {
	p.X = p.X + dx
	p.Y = p.Y + dy
}

func main() {
	p := Point{3, 4}
	fmt.Println("Point p = ", p)

	p.Translate(7, 9)
	fmt.Println("After Translate, p = ", p)
}

Kết quả

Point p =  {3 4}
After Translate, p =  {10 13}

Phương thức với  receiver con trỏ bằng hàm

Cũng giống như phương thức với các  receiver giá trị, chúng ta cũng có thể viết các phương thức với các  receiver con trỏ bằng các hàm. Hãy cùng xem ví dụ sau để hiểu về cách viết lại ví dụ trên sử dụng hàm

package main

import (
  "fmt"
)

type Point struct {
	X, Y float64
}

/* Dịch Point hiện tại, tại vị trí (X,Y), bằng dx dọc theo trục x và dy dọc theo trục y 
   Nên vị trí hiện tại (X+dx,Y+dy). 
*/
func TranslateFunc(p *Point, dx, dy float64) {
	p.X = p.X + dx
	p.Y = p.Y + dy
}

func main() {
	p := Point{3, 4}	
	fmt.Println("Point p = ", p)

	TranslateFunc(&p, 7, 9)
	fmt.Println("After Translate, p = ", p)
}

Phương thức và con trỏ gián tiếp

Phương thức với receiver con trỏ vs Hàm với tham số con trỏ

Một phương thức với receiver con trỏ có thể nhận cả con trỏ và giá trị là tham số recevier. Nhưng một hàm với tham số truyền vào là con trỏ thì chỉ nhận một con trỏ duy nhất.

package main

import (
  "fmt"
)

type Point struct {
	X, Y float64
}

// Phương thức với receiver con trỏ
func (p *Point) Translate(dx, dy float64) {
	p.X = p.X + dx
	p.Y = p.Y + dy
}

// Hàm với tham số con trỏ
func TranslateFunc(p *Point, dx, dy float64) {
	p.X = p.X + dx
	p.Y = p.Y + dy
}

func main() {
	p := Point{3, 4}	
	ptr := &p
	fmt.Println("Point p = ", p)

	// Gọi một phương thức với receiver con trỏ
	p.Translate(2, 6)		// Hợp lệ
	ptr.Translate(5, 10)	// Hợp lệ

	// Gọi một hàm với tham số con trỏ
	TranslateFunc(ptr, 20, 30)  // Hợp lệ
	TranslateFunc(p, 20, 30)   // Không hợp lệ
}

Chú ý rằng cách gọi các p.Translate() và ptr.Translate() hợp lệ. Do Go hiểu rằng phương thức Translate() có một receiver con trỏ. Nó thông dịch câu lệnh p.Translate() thành (&p).Translate().

Phương thức với receiver giá trị vs Hàm với tham số giá trị

Một phương thức với một receiver giá trị có thể nhận cả con trỏ và giá trị như tham số receiver. Nhưng một hàm với tham số giá trị có thể chỉ nhận một giá trị

package main

import (
  "fmt"
)

// Kiểu Struct - `Point`
type Point struct {
	X, Y float64
}

func (p Point) IsAbove(y float64) bool {
	return p.Y > y
}

func IsAboveFunc(p Point, y float64) bool {
	return p.Y > y
}

func main() {
	p := Point{0, 4}
	ptr := &p

	fmt.Println("Point p = ", p)

	// Gọi phương thức với receiver giá trị
	p.IsAbove(1)   // Hợp lệ
	ptr.IsAbove(1) // Hợp lệ

	// Gọi hàm với tham số giá trị
	IsAboveFunc(p, 1)   // Hợp lệ
	IsAboveFunc(ptr, 1) // Không hợp lệ
}

Cả 2 câu lệnh p.IsAbove() và ptr.IsAbove() đều hợp lệ. Go hiểu rằng phương thức IsAbove() có receiver giá trị. Do đó, để cho thuận lợi nó thông dịch ptr.IsAbove() thành (*ptr).IsAbove()

Hạn chế định nghĩa phương thức

Để có thể định nghĩa một phương thức trên receiver, kiểu receiver phải được định nghĩ trong cùng package.

Go không cho phép bạn định nghĩa một phương thức trên kiểu recevier được định nghĩa trên package khác nhau (cái này bao gồm cả những kiểu tích hợp sẵn như là int)

Trong các ví dụ trước chúng ta đã định nghĩa trên cùng package main, do đó hoạt động tốt. Tuy nhiên, nếu chúng ta thử định nghĩa phương thức trên kiểu được định nghĩa trên các package khác nhau, trình biên dịch sẽ báo lỗi.

Hãy cùng xem ví dụ với cấu trúc package như sau

src/example
	main
		main.go
    model
        person.go

person.go

package model

type Person struct {
	FirstName string
	LastName string
	Age int
}

Hãy thử định nghĩa một phương thức trên struct Person.

package main

import (
  "example/model"
)

// ERROR: cannot define new methods on non-local types model.Person
func (p model.Person) getFullName() string {
	return p.FirstName + " " + p.LastName
}

func main() {	
}

Định nghĩa phương thức trên kiểu không phải struct

Go cũng cho phép chúng ta định nghĩa kiểu không phải struct. Trong ví dụ sau đây định nghĩa một phương thức được gọi là reverse() trên kiểu MyString

package main

import (
  "fmt"
)

type MyString string

func (myStr MyString) reverse() string {
	s := string(myStr)
	runes := []rune(s)

	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	return string(runes)
}

func main() {
	myStr := MyString("OLLEH")
	fmt.Println(myStr.reverse())
}

Kết quả

HELLO

 

 

 

You May Also Like

About the Author: Nguyen Dinh Thuc

3 Comments

Leave a Reply

Your email address will not be published.