I. Xuất thân

Go hay Golang là ngôn ngữ lập trình được tạo bởi Google (bao gồm các lập trình viên của Google và các lập trình viên khác). Ngôn ngữ lập trình này là miễn phí và là mã nguồn mở và hiện đang được Google duy trì. Một trong những thành viên sáng lập của Go là Ken Thompson, người nổi tiếng với công việc phát triển hệ điều hành Unix. Trình biên dịch Go ban đầu được viết bằng C nhưng bây giờ, nó được viết bằng chính Go.

II. Vì sao Go được sinh ra?

Bốn điều quan trọng mà một ngôn ngữ phải phát triển để đạt được là tốc độ, độ tin cậy, khả năng mở rộng và sự đơn giản. Nếu chúng ta xem xét các ngôn ngữ như C hoặc C ++, chúng rất tuyệt vời về tốc độ, khả năng mở rộng và độ tin cậy nhưng về mặt đơn giản, chúng không quá tuyệt vời.

Mặt khác, Java rất đáng tin cậy và có khả năng mở rộng cao, nhưng đơn giản ở mức độ vừa phải và không quá hiệu quả so với các ngôn ngữ cấp thấp khác. Python là một ngôn ngữ được sử dụng rộng rãi và rất đơn giản để viết nhưng không quá hiệu quả và đáng tin cậy.

Google vào năm 2008-2010 tìm kiếm một ngôn ngữ có thể tuyệt vời ở cả 4 điều này. Với sự giúp đỡ của Ken Thompson, Rob Pike và Robert Griesemer, họ đã tạo ra một ngôn ngữ Go có thể mang đến cho các nhà phát triển trải nghiệm viết ngôn ngữ interpreted như Python hoặc JavaScript nhưng đồng thời có được những thuận lợi của các ngôn ngữ complied bậc thấp. Trọng tâm chính của họ là sự đơn giản, khả năng mở rộng, tốc độ và độ tin cậy. Vì Go là một từ tiếng Anh thường được sử dụng, gây khó khăn cho việc tìm kiếm trên internet nên nhiều người sử dụng từ khóa GoLang thay vì Go.

III. Go có hướng đối tượng không?

Câu trả lời là ... có và không. Mặc dù Go có các kiểu và phương thức cho phép viết code theo kiểu lập trình hướng đối tượng, nhưng lại không có type phân cấp (tức là không có kế thừa). Ngoài ra trong Go còn có các cách để nhúng 1 loại dữ liệu này vào trong 1 kiểu dữ liệu khác (tương tự như kiểu dữ liệu này là con của kiểu dữ liệu kia). Hơn nữa, các phương thức trong Go là tổng quát hơn so với trong C ++ hoặc Java.

IV. Các khái niệm nền tảng trong Go

1. Struct

Go không có lớp, nhưng nó có các Struct. Struct là  loại dữ liệu do người dùng định nghĩa. Các Struct (kết hợp với method) phục vụ các mục đích tương tự cho các lớp trong các ngôn ngữ khác.

Một Struct xác định trạng thái. Dưới đây là một Struct Creature. Nó có thuộc tính NameReal, cho chúng ta biết đó là sinh vật đó tên gì và có thật hay do tưởng tượng. Struct chỉ có trạng thái (thuộc tính) và không có method.

type Creature struct {
 
  Name string
 
  Real bool
 
}

2. Method

Method là hành vi trên Struct cụ thể nào đó. Dưới đây là phương thức Dump() cho phép in ra thuộc tính Name và và thuộc tính Real của Struct Creature.

func (c Creature) Dump() {
  fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real)

3. Embedding

Go cho phép định nghĩa các Struct có các field mà những field này chỉ có kiểu dữ liệu chứ không có tên biến. Những field như vậy được gọi là anonymous field hay unnamed field. Nếu ta nhúng một unnamed field (có kiểu dữ liệu là 1 Struct) vào 1 Struct khác thì nó cung cấp trạng thái (và phương thức) của nó cho Struct kia. Ví dụ, FlyingCreatureStruct Creature được nhúng vào nó, điều này có nghĩa là FlyingCreature là 1 loại Creature.

type FlyingCreature struct {
  Creature
  WingSpan int
}

Bây giờ, nếu ta có một instance của FlyingCreature, ta có thể truy cập trực tiếp các thuộc tính của nó như đoạn code bên dưới.

dragon := &FlyingCreature{
    Creature{"Dragon", false, },
    15,
}
 
fmt.Println(dragon.Name)
fmt.Println(dragon.Real)
fmt.Println(dragon.WingSpan)

4. Interface

Tương tự như các interface trong các ngôn ngữ khác, chúng không có cài đặt.  Các đối tượng implement tất cả các phương thức của interface sẽ tự động implement interface đó. Lưu ý là trong Go không có kế thừa hoặc phân lớp hoặc từ khóa implements! Trong đoạn code bên dưới, kiểu Foo sẽ cài đặt interface Fooer (theo quy ước, tên interface trong Go kết thúc bằng er).

type Fooer interface {
  Foo1()
  Foo2()
  Foo3()
}
 
type Foo struct {
}
 
func (f Foo) Foo1() {
    fmt.Println("Foo1() here")
}
 
func (f Foo) Foo2() {
    fmt.Println("Foo2() here")
}
 
func (f Foo) Foo3() {
    fmt.Println("Foo3() here")
}

V. Cách Go hướng đối tượng

1. Tính đóng gói

Go đóng gói mọi thứ ở cấp độ package. Với cách đặt tên  bắt đầu bằng một chữ cái viết thường thì chỉ truy cập được trong package đó (tức là private). Tên bắt đầu bằng chữ hoa sẽ là public. Vậy còn protected thì sao nhỉ? Vì Go không có kế thừa nên sẽ không có `protected`!

Ví dụ: ở đây để ẩn đi type Foo ở trên và chỉ show ra interface ta chỉ cần  đổi tên thành foo (chữ thường) và cung cấp hàm NewFoo () trả về public Fooer interface

type foo struct {
}
 
func (f foo) Foo1() {
    fmt.Println("Foo1() here")
}
 
func (f foo) Foo2() {
    fmt.Println("Foo2() here")
}
 
func (f foo) Foo3() {
    fmt.Println("Foo3() here")
}
 
func NewFoo() Fooer {
    return &Foo{}
}

Sau đó, code từ package khác có thể sử dụng NewFoo () và có quyền truy cập vào interface Fooer được triển khai bởi loại foo nội bộ:

f := NewFoo()
 
f.Foo1()
 
f.Foo2()
 
f.Foo3()

2. Tính kế thừa

Kế thừa hoặc phân lớp luôn là một vấn đề gây tranh cãi . Đa kế thừa của C ++ và Python và các ngôn ngữ khác phải chịu đựng vấn đề deadly diamond of death, nhưng ngay cả đơn thừa kế cũng có vấn đề của nó là fragile base-class . Do vậy trong Go không có kế thừa!

Ngôn ngữ hiện đại và tư duy hướng đối tượng hiện nay sử dụng composition hơn là kế thừa. Go cho phép nhúng 1 type vào 1 type khác.

package main

import (
	"fmt"
)

type Dog struct {
	Animal
}
type Animal struct {
	Age int
}

func (a *Animal) Move() {
	fmt.Println("Animal moved")
}
func (a *Animal) SayAge() {
	fmt.Printf("Animal age: %d\n", a.Age)
}
func main() {
	d := Dog{}
	d.Age = 3
	d.Move()
	d.SayAge()
}

3. Tính đa hình

Bản chất của đa hình của lập trình hướng đối tượng là khả năng xử lý các đối tượng thuộc các loại khác nhau miễn là chúng tuân thủ cùng một interface.

Trong  vòng lặp for của đoạn code bên dưới, ta có thể thấy cùng là human nhưng nhận được các phản hồi khác nhau dựa trên type là Man hay Woman.

package main

import "fmt"

type Human interface {
    myStereotype() string
}

type Man struct {
}

func (m Man) myStereotype() string {
    return "I'm going fishing."
}

type Woman struct {
}

func (m Woman) myStereotype() string {
    return "I'm going shopping."
}
func main() {
    m := new (Man)
    w := new (Woman)

    //an array of Humans - we don’t know whether Man or Woman
    hArr := [...]Human{m, w} //array of 2 Humans. One is the type Man, one is the type Woman.
    for n, _ := range (hArr) {

        fmt.Println("I'm a human, and my stereotype is: ", hArr[n].myStereotype())   //appears as human type, but behavior changes depending on actual instance
        
    }

}

Output:

I'm a human, and my stereotype is: I'm going fishing.
I'm a human, and my stereotype is: I'm going shopping.

https://www.golang-book.com/books/intro

https://flaviocopes.com/golang-is-go-object-oriented/

https://yourbasic.org/golang/inheritance-object-oriented/

https://medium.com/rungo/introduction-to-go-programming-language-golang-89d16ca72bbf

https://code.tutsplus.com/tutorials/lets-go-object-oriented-programming-in-golang--cms-26540

https://golang.org/doc/faq#Is_Go_an_object-oriented_language

http://golangtutorials.blogspot.com/2011/06/polymorphism-in-go.html