Bạn có chắc chắn muốn xóa bài viết này không ?
Bạn có chắc chắn muốn xóa bình luận này không ?
Golang nhìn từ góc nhìn của hướng đối tượng
Bài viết dịch từ https://github.com/luciotato/golang-notes/blob/master/OOP.md
Mục đích bài viết
- Học golang dễ dàng hơn với những kiến thức bạn đã có sẵn khi học OOP.
- Khuyến khích việc sử dụng golang từ những người đã có nền tảng OOP
Golang concept
Golang đưa vào một số khái niệm mà chỉ có golang mới có như là struct
hay là interface
. Những khái niệm đó cũng hoàn toàn ok, không quá khó hiểu nhưng sẽ tốt hơn nếu chúng ta "dịch" những khái niệm đó sang những khái niệm đã có từ trước tới nay trên OOP.
Cheetsheet
Golang | Classic OOP |
---|---|
struct | class với fields, và có các method không phải method ảo |
interface | class không có fields, chỉ có các method ảo |
embedding | Kế thừa / kết hợp |
receiver | Tương đương với this trong OOP |
Golang struct là class (non-virtual)
Golang Struct là class với fields, và có các method không phải method ảo. Ví dụ
type Rectangle struct {
Name string
Width, Height float64
}
func (r Rectangle) Area() float64{
return r.Width * r.Height
}
Đoạn code trên có thể hiểu như dưới đây trên OOP
class Rectangle
field name: string
field Width: float64
field Height: float64
method Area() //non-virtual
return this.Width * this.Height
Constructor
- Mỗi field nếu không được cung cấp giá trị default thì đều có giá trị zero (zero-value) (zero-value tuỳ vào từng loại type như string, int lại có giá trị khác nhau)
- Chỉ có một constructor duy nhất cho tất cả mọi loại class, với syntax na ná như json, và sử dụng reflection để gán giá trị cho các field cần thiết.
Pseudocode cho constructor
function construct ( class, literal)
helper function assignFields ( object, literal) //recursive
if type-of object is "object"
if literal has field-names
for each field in object.fields
if literal has-field field.name
assignFields(field.value, literal.fields[field.name].value) //recurse
else
//literal without names, assign by position
for n=0 to object.fields.length
assignFields(object.fields[n].value, literal.fields[n].value) //recurse
else
//atom type
set object.value = literal.value
// generic constructor main body
var classInstance = new class
assignFields(classInstance, literal)
return classInstance
Ví dụ về constructor của golang
package main
import . "fmt"
type Rectangle struct {
Name string
Width, Height float64
}
func main() {
var a Rectangle
var b = Rectangle{"I'm b.", 10, 20}
var c = Rectangle{Height: 12, Width: 14}
Println(a)
Println(b)
Println(c)
}
=>
{ 0 0}
{I'm b. 10 20}
{ 14 12}
Golang embeded giống như là Multiple Inheritance mà không có virtual method
Bằng việc nhúng một struct vào struct khác, chúng ta có thể làm một thứ gần giống như multiple inheritance.
Struct được nhúng vào có thể coi là based class, còn struct thực hiện việc nhúng sẽ là derived class (class kế thừa). Method và field của cha có thể bị che (shadowed) nếu nó cũng được định nghĩa trong con. Một khi bị che mất thì chỉ có thể access vào field/method thông qua hidden field chính là tên của based struct
Ví dụ
type base struct {
a string
b int
}
type derived struct {
base // embedding
d int
a float32 //-SHADOWED
}
func main() {
var x derived;
fmt.Printf("%T\n",x.a) //=> x.a, float32 (derived.a shadows base.a)
fmt.Printf("%T\n",x.base.a) //=> x.base.a, string (accessing shadowed member)
}
Một điều quan trọng các bạn cần nhớ là tất cả các method kế thừa đều được gọi qua hidden field mà nói ở trên, do đó mà based method sẽ không có ý thức về field và method của struct kế thừa.
Một điều nữa cần nhớ là: Khi làm việc với struct và embedding, mọi thứ là STATICALLY LINKED, mọi referenced đều được resolved tại compile time
Multiple embedding
type NamedObj struct {
Name string
}
type Shape struct {
NamedObj //inheritance
color int32
isRegular bool
}
type Point struct {
x, y float64
}
type Rectangle struct {
NamedObj //multiple inheritance
Shape //^^
center Point //standard composition
Width, Height float64
}
func main() {
var aRect Rectangle = Rectangle{NamedObj{"name1"},
Shape{NamedObj{"name2"}, 0, true},
Point{0, 0},
20, 2.5}
fmt.Println(aRect.Name)
fmt.Println(aRect.Shape)
fmt.Println(aRect.Shape.Name)
}
Đoạn code trên có thể diễn giải bằng pseudo như sau:
class NamedObj
field Name: string
class Shape
inherits NamedObj
field color: int32
field isRegular: bool
class Rectangle
inherits NamedObj
inherits Shape
field center: Point
field Width: float64
field Height: float64
Ví dụ
Ở var aRect Rectangle
thì
-
aRect.Name
vàaRect.NamedObj.Name
chỉ về cùng 1 field -
aRect.color
vàaRect.Shape.color
chỉ về cùng 1 field -
aRect.name
vàaRect.NamedObj.name
chỉ về cùng 1 field, nhưngaRect.NamedObj.name
vàaRect.Shape.NamedObj.name
là 2 field khác nhau -
aRect.NamedObj
vàaRect.Shape.NamedObj
là cùng 1 type, nhưng thuộc về 2 object khác nhau
Method shadowing
Khi mà golang-struct methods là non-virtual, bạn không thể override method (Bạn cần interfaces cho điều đó)
Ví dụ nếu bạn có method show() trong class/struct NamedObj , đồng thời bạn cũng định nghĩa method show() trong class/struct Rectangle, Rectangle/show() sẽ SHADOW class cha NamedObj/Show()
Với fields của based class field kế thừa có cùng tên với base class sẽ dùng để access base implementation thông qua dot-notation, ví dụ :
type base struct {
a string
b int
}
//method xyz
func (this base) xyz() {
fmt.Println("xyz, a is:", this.a)
}
//method display
func (this base) display() {
fmt.Println("base, a is:",this.a)
}
type derived struct {
base // embedding
d int
a float32 //-SHADOWED
}
//method display -SHADOWED
func (this derived) display() {
fmt.Println("derived a is:",this.a)
}
func main() {
var a derived = derived{base{"base-a",10},20,2.5}
a.display() // calls Derived/display(a)
// => "derived, a is: 2.5"
a.base.display() // calls Base/display(a.base), the base implementation
// => "base, a is: base-a"
a.xyz() // "xyz" was not shadowed, calls Base/xyz(a.base)
// => "xyz, a is: base-a"
}
Multiple inheritance và vấn đề kế thừa kim cương
Golang giải quyết vấn đề kế thừa kim cương ( the diamond problem) bằng việc không cho phép điều đó. Nếu bạn cố thử làm điều đó thì golang compiler sẽ phàn nàn khi mà bị collide name space.
Golang methods và "receivers" (this)
A golang struct-method về cơ bản giông như class non-virtual method , tuy nhiên:
- Nó được nằm bên ngoài của class(struct) body
- Do bị nằm bên ngoài của struct body, thế nên nó cần một phần phụ trợ nằm phía trước method name để định nghĩa "receiver" (this).
- Phần phụ trợ này định nghĩa this như là một explicit parameter
Ví dụ
//class NamedObj
type NamedObj struct {
Name string
}
//method show
func (n NamedObj) show() {
Println(n.Name) // "n" is "this"
}
//class Rectangle
type Rectangle struct {
NamedObj //inheritance
Width, Height float64
}
//override method show
func (r Rectangle) show() {
Println("Rectangle ",r.name) // "r" is "this"
}
Pseudo code
class NamedObj
Name: string
method show
print this.Name
class Rectangle
inherits NamedObj
field Width: float64
field Height: float64
method show //override
print "Rectangle", this.Name
Cách sử dụng
func main() {
var a = NamedObj{"Joe"}
var b = Rectangle{NamedObj{"Richard"}, 10, 20}
a.show("Hello")
b.show("Hello")
}
=>
Hello I'm Joe
Hello I'm Richard
- I'm a Rectangle named Richard
Struct vs Interface
golang-Interface là một class mà không có fields , mà CHỈ CÓ VIRTUAL methods.
interface trong golang được coi như phần bù cho structs. Bằng việc giới hạn struct chỉ cho non-virtual method, và interfaces cho virtual method only , thì kết hợp struct và interface chúng ta đã có một bộ đôi hoàn hảo cho OOP.
Interfaces
Sử dụng interface bạn có thể
- Định nghĩa một var hay parameter với type interface
- implement interface, thông qua việc định nghĩa tất cả virtual methods của interface, trong một struct
- inherit (embeded) golang-interface trong một golang-interface khác
Khi bạn định nghĩa var hay parameter với tyle interface.
- var/parameter không có field nào
- var/parameter có một set các methods
- Khi bạn gọi một method trên var/parameter, một method đã được định nghĩa (concrete method) sẽ được gọi thông qua method dispatch, từ jmp-table.
Note: Itable sử dụng trong method-dispatch của golang sẽ được tạo ra một cách động (dynamically) và được cached. Mỗi *class (struct) * sẽ có một ITable cho mỗi interface mà class(struct) đó implement. Xem thêm Go Data Structures: Interfaces
Ví dụ tại How to use interfaces in Go , và pseudo code
package main
import (
"fmt"
)
/*
class Animal
virtual abstract Speak() string
*/
type Animal interface {
Speak() string
}
/*
class Dog
method Speak() string //non-virtual
return "Woof!"
*/
type Dog struct {
}
func (d Dog) Speak() string {
return "Woof!"
}
/*
class Cat
method Speak() string //non-virtual
return "Meow!"
*/
type Cat struct {
}
func (c Cat) Speak() string {
return "Meow!"
}
/*
class Llama
method Speak() string //non-virtual
return "LaLLamaQueLLama!"
*/
type Llama struct {
}
func (l Llama) Speak() string {
return "LaLLamaQueLLama!"
}
/*
func main
var animals = [ Dog{}, Cat{}, Llama{} ]
for each animal in animals
print animal.Speak() // method dispatch via jmp-table
*/
func main() {
animals := []Animal{Dog{}, Cat{}, Llama{}}
for _, animal := range animals {
fmt.Println(animal.Speak()) // method dispatch via jmp-table
}
}
Empty interface
Bằng việc định nghĩa : interface là class không có field, chỉ có virtual method, bạn có thể hiểu golang empty interface là gì: Empty Interface: "Interface{}"
Interface{} trong golang là một class mà không fields và không có methods
Tuy nhiên bằng việc định nghĩa tất cả classes(structs) implement Interface{} , nó có nghĩa là
một var x Interface{}
có thể chứa bất kì giá trị nào
Bạn có thể làm gì với var x Interface{}
? Ừm, mới đầu thì chả có gì cả, bởi bạn không hiểu type của value store trong var x Interface{}
.
Để thật sự sử dụng value bên trong var x Interface{}
bạn phải sử dụng Type Switch, type assertion, hoặc là reflection






