Golang nhìn từ góc nhìn của hướng đối tượng
golang
38
Go
39
White

Hoàng Minh Trung viết ngày 04/06/2016

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.NameaRect.NamedObj.Name chỉ về cùng 1 field
  • aRect.coloraRect.Shape.color chỉ về cùng 1 field
  • aRect.nameaRect.NamedObj.name chỉ về cùng 1 field, nhưng aRect.NamedObj.nameaRect.Shape.NamedObj.name2 field khác nhau
  • aRect.NamedObjaRect.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"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

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Hoàng Minh Trung

23 bài viết.
65 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
22 1
Bài viết dịch từ http://arslan.io/tenusefultechniquesingo Sử dụng một GOPATH duy nhất Sử dụng đồng thời nhiều GOPATH sẽ không giúp cho hệ thống ...
Hoàng Minh Trung viết hơn 2 năm trước
22 1
White
19 15
(Ảnh) Mục đích của bài viết là hướng dẫn cơ bản nhất cho những ai chưa biết về docker, môi trường thực hiện là mac OS. Chuẩn bị Cài đặt virtua...
Hoàng Minh Trung viết hơn 3 năm trước
19 15
White
14 3
Giới thiệu (Ảnh) Về cơ bản kafka là hệ thống message pub/sub phân tán mà có khả năng scale rất tốt. Message của kafka được lưu trên đĩa cứng, đồ...
Hoàng Minh Trung viết hơn 3 năm trước
14 3
Bài viết liên quan
White
16 0
Crawl dữ liệu Crawl là một vấn đề hay gặp trong quá trình làm software. Ví dụ lấy tin tức, tin giảm giá, vé xem phim... là những dạng của crawl. Mộ...
Thach Le viết hơn 2 năm trước
16 0
White
44 7
Là một người thường xuyên đọc Quora, có một điểm cá nhân tôi thấy rất ấn tượng ở quora chính là khả năng autocomplete với tốc độ ánh sáng. (Ảnh) ...
huydx viết 10 tháng trước
44 7
White
10 2
Makefile thực hiện một số thao tác thường dùng trong Go Khi làm project Go mình thường tạo một file Makefile dạng này: Lưu ý nhớ thay thành tên m...
Huy Trần viết hơn 2 năm trước
10 2
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
23 bài viết.
65 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!