Bài 20 - Hướng đối tượng trong Go


  • Trùm cuối

    OOP

    Từ đầu những năm 1990, lập trình hướng đối tượng (Object-oriented programming - OOP) nở rộ và được hầu hết các ngôn ngữ lập trình ra đời từ đó hỗ trợ nó. Và Go cũng hỗ trợ lập trình hướng đối tượng theo cách riêng của Go.

    b3fecf82-ea9c-4ace-96e6-55272d18b1d9-image.png

    trong Go

    Theo nguyên lý hướng đối tượng được các ngôn ngữ hỗ trợ thì đối tượng gồm các thuộc tính (properties) là các biến kiểu dữ liệu khác nhau và các phương thức (methods) mà thực chất là các hàm có thể truy xuất các thuộc tính mà không cần truyền tham số. Các phương thức này sẽ làm thay đổi tính chất của các thuộc tính cũng như giúp đối tượng thể hiện các hành động của nó. Các đối tượng được khai báo qua các lớp (class).

    Go không xây dựng khái niệm class và cũng không có khái niệm đối tượng (object) nhưng Go cho phép tạo phương thức trên các kiểu dữ liệu như đã thấy ở bài 17. Như vậy mọi kiểu dữ liệu của Go có thể trở thành lớp (class) và các biến của kiểu dữ liệu đó có thể trở thành đối tượng (object). Gần giống nhất khái niệm lớp ở các ngôn ngữ hướng đối tượng có lẽ là kiểu cấu trúc trong Go. Chúng ta cùng xem ví dụ như sau:

    type Bike struct { 
        sound string 
    } 
    
    func (b Bike) SoundHorn() { 
        fmt.Println(b.sound) 
    } 
    func main() { 
        b := Bike{sound: "REN REN"} 
        b.SoundHorn()       // REN REN 
    }
    

    Với việc khai báo phương thức SoundHorn() cho cấu trúc Bike, chúng ta thấy nó gần như là một lớp với thuộc tính là sound kiểu string mô tả âm thanh phát ra khi nhấn còi và phương thức SoundHorn in ra chuỗi âm thanh mà thuộc tính sound đã lưu. Khi tạo biến b ở hàm main, b trở thành thực thể như là đối tượng và việc gọi phương thức s.SoundHorn() giống y như cách gọi phương thức ở các ngôn ngữ hướng đối tượng khác.

    trong Go

    Go không có khái niệm kế thừa cũng như khai báo việc kế thừa tường minh nhưng chúng ta vẫn có thể có thuộc tính và phương thức của một kiểu dữ liệu khác bằng cách sử dụng trường vô danh như đã đề cập ở bài 17. Việc sử dụng trường vô danh giống như nhúng kiểu dữ liệu này vào kiểu dữ liệu khác tương tự như khái niệm tụ hợp (composition) trong hướng đối tượng. Đặc điểm của trường vô danh là nó giúp cho kiểu dữ liệu tụ hợp có thể sử dụng thuộc tính và phương thức của kiểu dữ liệu nhúng trực tiếp y như của nó. Cách sử dụng này lại tương tự như kế thừa trong hướng đối tượng.

    trong Go

    Go không quy định các từ khóa mô tả cấp độ truy cập và che giấu thông tin như public, protected hay private như các ngôn ngữ hướng đối tượng khác. Trong Go khái niệm đóng gói khá đơn giản: chữ cái đầu tên viết hoa thì mở, còn viết thường thì đóng. Quy tắt này áp dụng cho hằng, biến, hàm, trường, phương thức, v.v... Có điều trong Go, khái niệm mở hay đóng chỉ áp dụng bên ngoài package. Trong package, mọi cái đều mở dù tên viết hoa hay viết thường.

    trong Go

    Trong ngôn ngữ hướng đối tượng, tính đa hình thể hiện khi các lớp kế thừa từ cùng một lớp. Trong Go không có khái niệm kế thừa nhưng với việc sử dụng interface, tính đa hình cũng được Go hỗ trợ dưới hình thức riêng.

    Như trong bài 18 về interface chúng ta đã tìm hiểu, bất kỳ kiểu dữ liệu nào cài đặt các phương thức y như phương thức của một interface thì Go ngầm định kiểu dữ liệu này thỏa mãn interface đó và có thể sử dụng ở các nơi interface này sử dụng.

    Quay trở lại ví dụ về cấu trúc Bike bên trên, chúng ta thấy các loại phương tiện sẽ phát ra tiếng riêng khi nhấn còi nên để thể hiện tiếng của các phương tiện khác nhau chúng ta tạo thêm cấu trúc Car và interface HornSounder như sau:

    type Car struct { 
        sound string 
    } 
    
    func (c Car) SoundHorn() { 
        fmt.Println(c.sound) 
    }
    
    type HornSounder interface {
        SoundHorn()
    }
    

    Chúng ta thấy cả BikeCar thỏa mãn HornSounder nên việc khai báo hàm main như bên dưới hoàn toàn hợp lệ:

    var vehicles = []HornSounder{Bike{"RING"}, Car{"BEEP"}}
    for _, v := range vehicles {
        v.SoundHorn()
    }
    

    Do BikeCar thỏa mãn HornSounder nên mặc dù vehicles là slice HornSounder nhưng được gán giá trị là các thực thể Bike và Car. Ở vòng for, biến v lần lượt được nhận giá trị thực thể Bike và Car và lần lượt gọi phương thức SoundHorn() của thực thể Bike và Car. Kết quả chúng ta có "RING" và "BEEP". Đây là cách Go đề nghị thể hiện tính đa hình của hướng đối tượng thông qua interface.

    Bài tiếp theo chúng ta sẽ cùng tìm hiểu về khái niệm quan trọng thể hiện sức mạnh của ngôn ngữ Go. Đó là xử lý đồng thời.

    Tóm tắt

    • Go không có khái niệm class nhưng cho phép tạo phương thức của bất kỳ kiểu dữ liệu nào (trừ kiểu con trỏ). Cấu trúc (struct) với các phương thức là cách thể hiện class gần giống nhất khái niệm class ở các ngôn ngữ khác.
    • Go hỗ trợ tính chất tụ hợp (composition) để tạo kế thừa chứ không hỗ trợ kế thừa (inheritance).
    • Go hỗ trợ tính bao đóng cho đối tượng là kiểu con trỏ và bao đóng chỉ có ý nghĩa ở ngoài package mà nó khai báo.


Có thể bạn cũng quan tâm

.
DMCA.com Protection Status